Merge tag '13.8.1' into io

This commit is contained in:
Cookie Ramen
2023-03-01 02:33:14 +09:00
665 changed files with 6860 additions and 9757 deletions

View File

@@ -1,4 +1,4 @@
import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
import { utils, values } from '@syuilo/aiscript';
import { v4 as uuid } from 'uuid';
import { ref, Ref } from 'vue';

View File

@@ -1,6 +1,5 @@
import { defineAsyncComponent, Ref, inject } from 'vue';
import { defineAsyncComponent, Ref } from 'vue';
import * as misskey from 'misskey-js';
import { pleaseLogin } from './please-login';
import { claimAchievement } from './achievements';
import { $i } from '@/account';
import { i18n } from '@/i18n';
@@ -9,8 +8,8 @@ import * as os from '@/os';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { url } from '@/config';
import { noteActions } from '@/store';
import { notePage } from '@/filters/note';
import { miLocalStorage } from '@/local-storage';
import { getUserMenu } from '@/scripts/get-user-menu';
export function getNoteMenu(props: {
note: misskey.entities.Note;
@@ -101,66 +100,6 @@ export function getNoteMenu(props: {
});
}
async function clip(): Promise<void> {
const clips = await os.api('clips/list');
os.popupMenu([{
icon: 'ti ti-plus',
text: i18n.ts.createNew,
action: async () => {
const { canceled, result } = await os.form(i18n.ts.createNewClip, {
name: {
type: 'string',
label: i18n.ts.name,
},
description: {
type: 'string',
required: false,
multiline: true,
label: i18n.ts.description,
},
isPublic: {
type: 'boolean',
label: i18n.ts.public,
default: false,
},
});
if (canceled) return;
const clip = await os.apiWithDialog('clips/create', result);
claimAchievement('noteClipped1');
os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
},
}, null, ...clips.map(clip => ({
text: clip.name,
action: () => {
claimAchievement('noteClipped1');
os.promiseDialog(
os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
null,
async (err) => {
if (err.id === '734806c4-542c-463a-9311-15c512803965') {
const confirm = await os.confirm({
type: 'warning',
text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
});
if (!confirm.canceled) {
os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true;
}
} else {
os.alert({
type: 'error',
text: err.message + '\n' + err.id,
});
}
},
);
},
}))], props.menuButton.value, {
}).then(focus);
}
async function unclip(): Promise<void> {
os.apiWithDialog('clips/remove-note', { clipId: props.currentClipPage.value.id, noteId: appearNote.id });
props.isDeleted.value = true;
@@ -202,7 +141,7 @@ export function getNoteMenu(props: {
props.translating.value = true;
const res = await os.api('notes/translate', {
noteId: appearNote.id,
targetLang: miLocalStorage.getItem('lang') || navigator.language,
targetLang: miLocalStorage.getItem('lang') ?? navigator.language,
});
props.translating.value = false;
props.translation.value = res;
@@ -242,7 +181,7 @@ export function getNoteMenu(props: {
icon: 'ti ti-external-link',
text: i18n.ts.showOnRemote,
action: () => {
window.open(appearNote.url || appearNote.uri, '_blank');
window.open(appearNote.url ?? appearNote.uri, '_blank');
},
} : undefined,
{
@@ -266,9 +205,67 @@ export function getNoteMenu(props: {
action: () => toggleFavorite(true),
}),
{
type: 'parent',
icon: 'ti ti-paperclip',
text: i18n.ts.clip,
action: () => clip(),
children: async () => {
const clips = await os.api('clips/list');
return [{
icon: 'ti ti-plus',
text: i18n.ts.createNew,
action: async () => {
const { canceled, result } = await os.form(i18n.ts.createNewClip, {
name: {
type: 'string',
label: i18n.ts.name,
},
description: {
type: 'string',
required: false,
multiline: true,
label: i18n.ts.description,
},
isPublic: {
type: 'boolean',
label: i18n.ts.public,
default: false,
},
});
if (canceled) return;
const clip = await os.apiWithDialog('clips/create', result);
claimAchievement('noteClipped1');
os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
},
}, null, ...clips.map(clip => ({
text: clip.name,
action: () => {
claimAchievement('noteClipped1');
os.promiseDialog(
os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
null,
async (err) => {
if (err.id === '734806c4-542c-463a-9311-15c512803965') {
const confirm = await os.confirm({
type: 'warning',
text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
});
if (!confirm.canceled) {
os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true;
}
} else {
os.alert({
type: 'error',
text: err.message + '\n' + err.id,
});
}
},
);
},
}))];
},
},
statePromise.then(state => state.isMutedThread ? {
icon: 'ti ti-message-off',
@@ -288,11 +285,20 @@ export function getNoteMenu(props: {
text: i18n.ts.pin,
action: () => togglePin(true),
} : undefined,
appearNote.userId !== $i.id ? {
type: 'parent',
icon: 'ti ti-user',
text: i18n.ts.user,
children: async () => {
const user = await os.api('users/show', { userId: appearNote.userId });
return getUserMenu(user);
},
} : undefined,
/*
...($i.isModerator || $i.isAdmin ? [
null,
{
icon: 'fas fa-bullhorn',
icon: 'ti ti-speakerphone',
text: i18n.ts.promote,
action: promote
}]
@@ -304,7 +310,7 @@ export function getNoteMenu(props: {
icon: 'ti ti-exclamation-circle',
text: i18n.ts.reportAbuse,
action: () => {
const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`;
const u = appearNote.url ?? appearNote.uri ?? `${url}/notes/${appearNote.id}`;
os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), {
user: appearNote.user,
initialComment: `Note: ${u}\n-----\n`,
@@ -346,7 +352,7 @@ export function getNoteMenu(props: {
icon: 'ti ti-external-link',
text: i18n.ts.showOnRemote,
action: () => {
window.open(appearNote.url || appearNote.uri, '_blank');
window.open(appearNote.url ?? appearNote.uri, '_blank');
},
} : undefined]
.filter(x => x !== undefined);

View File

@@ -1,5 +1,5 @@
import * as Acct from 'misskey-js/built/acct';
import { defineAsyncComponent } from 'vue';
import * as misskey from 'misskey-js';
import { i18n } from '@/i18n';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { host } from '@/config';
@@ -9,54 +9,9 @@ import { $i, iAmModerator } from '@/account';
import { mainRouter } from '@/router';
import { Router } from '@/nirax';
export function getUserMenu(user, router: Router = mainRouter) {
export function getUserMenu(user: misskey.entities.UserDetailed, router: Router = mainRouter) {
const meId = $i ? $i.id : null;
async function pushList() {
const t = i18n.ts.selectList; // なぜか後で参照すると null になるので最初にメモリに確保しておく
const lists = await os.api('users/lists/list');
if (lists.length === 0) {
os.alert({
type: 'error',
text: i18n.ts.youHaveNoLists,
});
return;
}
const { canceled, result: listId } = await os.select({
title: t,
items: lists.map(list => ({
value: list.id, text: list.name,
})),
});
if (canceled) return;
os.apiWithDialog('users/lists/push', {
listId: listId,
userId: user.id,
});
}
async function inviteGroup() {
const groups = await os.api('users/groups/owned');
if (groups.length === 0) {
os.alert({
type: 'error',
text: i18n.ts.youHaveNoGroups,
});
return;
}
const { canceled, result: groupId } = await os.select({
title: i18n.ts.group,
items: groups.map(group => ({
value: group.id, text: group.name,
})),
});
if (canceled) return;
os.apiWithDialog('users/groups/invite', {
groupId: groupId,
userId: user.id,
});
}
async function toggleMute() {
if (user.isMuted) {
os.apiWithDialog('mute/delete', {
@@ -125,6 +80,8 @@ export function getUserMenu(user, router: Router = mainRouter) {
}
async function invalidateFollow() {
if (!await getConfirmed(i18n.ts.breakFollowConfirm)) return;
os.apiWithDialog('following/invalidate', {
userId: user.id,
}).then(() => {
@@ -136,7 +93,7 @@ export function getUserMenu(user, router: Router = mainRouter) {
icon: 'ti ti-at',
text: i18n.ts.copyUsername,
action: () => {
copyToClipboard(`@${user.username}@${user.host || host}`);
copyToClipboard(`@${user.username}@${user.host ?? host}`);
},
}, {
icon: 'ti ti-info-circle',
@@ -156,22 +113,44 @@ export function getUserMenu(user, router: Router = mainRouter) {
action: () => {
os.post({ specified: user });
},
}, meId !== user.id ? {
type: 'link',
icon: 'ti ti-messages',
text: i18n.ts.startMessaging,
to: '/my/messaging/' + Acct.toString(user),
} : undefined, null, {
}, null, {
type: 'parent',
icon: 'ti ti-list',
text: i18n.ts.addToList,
action: pushList,
}, meId !== user.id ? {
icon: 'ti ti-users',
text: i18n.ts.inviteToGroup,
action: inviteGroup,
} : undefined] as any;
children: async () => {
const lists = await os.api('users/lists/list');
return lists.map(list => ({
text: list.name,
action: () => {
os.apiWithDialog('users/lists/push', {
listId: list.id,
userId: user.id,
});
},
}));
},
}] as any;
if ($i && meId !== user.id) {
if (iAmModerator) {
menu = menu.concat([{
type: 'parent',
icon: 'ti ti-badges',
text: i18n.ts.roles,
children: async () => {
const roles = await os.api('admin/roles/list');
return roles.filter(r => r.target === 'manual').map(r => ({
text: r.name,
action: () => {
os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id });
},
}));
},
}]);
}
menu = menu.concat([null, {
icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off',
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
@@ -195,30 +174,6 @@ export function getUserMenu(user, router: Router = mainRouter) {
text: i18n.ts.reportAbuse,
action: reportAbuse,
}]);
if (iAmModerator) {
menu = menu.concat([null, {
icon: 'ti ti-user-exclamation',
text: i18n.ts.moderation,
action: () => {
router.push('/user-info/' + user.id + '#moderation');
},
}, {
icon: 'ti ti-badges',
text: i18n.ts.roles,
action: async () => {
const roles = await os.api('admin/roles/list');
const { canceled, result: roleId } = await os.select({
title: i18n.ts._role.chooseRoleToAssign,
items: roles.map(r => ({ text: r.name, value: r.id })),
});
if (canceled) return;
await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.id });
},
}]);
}
}
if ($i && meId === user.id) {

View File

@@ -1,3 +1,3 @@
export default function(user: { name?: string | null, username: string }): string {
return user.name || user.username;
return user.name === '' ? user.username : user.name ?? user.username;
}

View File

@@ -1,11 +1,10 @@
import autobind from 'autobind-decorator';
import { markRaw, ref, Ref, unref } from 'vue';
import { ref, Ref, unref } from 'vue';
import { collectPageVars } from '../collect-page-vars';
import { initHpmlLib, initAiLib } from './lib';
import { initHpmlLib } from './lib';
import { Expr, isLiteralValue, Variable } from './expr';
import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from '.';
import { version } from '@/config';
import * as os from '@/os';
/**
* Hpml evaluator

View File

@@ -15,12 +15,12 @@ export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null;
export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = {
text: { out: 'string', category: 'value', icon: 'ti ti-quote' },
multiLineText: { out: 'string', category: 'value', icon: 'fas fa-align-left' },
textList: { out: 'stringArray', category: 'value', icon: 'fas fa-list' },
number: { out: 'number', category: 'value', icon: 'fas fa-sort-numeric-up' },
ref: { out: null, category: 'value', icon: 'fas fa-magic' },
aiScriptVar: { out: null, category: 'value', icon: 'fas fa-magic' },
fn: { out: 'function', category: 'value', icon: 'fas fa-square-root-alt' },
multiLineText: { out: 'string', category: 'value', icon: 'ti ti-align-left' },
textList: { out: 'stringArray', category: 'value', icon: 'ti ti-list' },
number: { out: 'number', category: 'value', icon: 'ti ti-list-numbers' },
ref: { out: null, category: 'value', icon: 'ti ti-wand' },
aiScriptVar: { out: null, category: 'value', icon: 'ti ti-wand' },
fn: { out: 'function', category: 'value', icon: 'ti ti-math-function' },
};
export const blockDefs = [
@@ -58,7 +58,7 @@ export class HpmlScope {
constructor(layerdStates: HpmlScope['layerdStates'], name?: HpmlScope['name']) {
this.layerdStates = layerdStates;
this.name = name || 'anonymous';
this.name = name ?? 'anonymous';
}
@autobind

View File

@@ -1,4 +1,3 @@
import tinycolor from 'tinycolor2';
import seedrandom from 'seedrandom';
import { Hpml } from './evaluator';
import { Expr } from './expr';
@@ -130,42 +129,42 @@ export function initAiLib(hpml: Hpml) {
export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = {
if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'ti ti-share' },
for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'fas fa-recycle' },
not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'ti ti-recycle' },
not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' },
or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' },
and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' },
add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-plus' },
subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-minus' },
multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-x' },
divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
round: { in: ['number'], out: 'number', category: 'operation', icon: 'fas fa-calculator' },
eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-equals' },
notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-not-equal' },
gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than' },
lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than' },
gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than-equal' },
ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than-equal' },
divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-divide' },
mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-divide' },
round: { in: ['number'], out: 'number', category: 'operation', icon: 'ti ti-calculator' },
eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'ti ti-equal' },
notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'ti ti-equal-not' },
gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-greater' },
lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-lower' },
gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-equal-greater' },
ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-equal-lower' },
strLen: { in: ['string'], out: 'number', category: 'text', icon: 'ti ti-quote' },
strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'ti ti-quote' },
strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'ti ti-quote' },
strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'ti ti-quote' },
join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'ti ti-quote' },
stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'fas fa-exchange-alt' },
numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'fas fa-exchange-alt' },
splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'fas fa-exchange-alt' },
pick: { in: [null, 'number'], out: null, category: 'list', icon: 'fas fa-indent' },
listLen: { in: [null], out: 'number', category: 'list', icon: 'fas fa-indent' },
rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
random: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
randomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'fas fa-dice' },
DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'fas fa-dice' }, // dailyRandomPickWithProbabilityMapping
stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'ti ti-arrows-right-left' },
numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'ti ti-arrows-right-left' },
splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'ti ti-arrows-right-left' },
pick: { in: [null, 'number'], out: null, category: 'list', icon: 'ti ti-indent-increase' },
listLen: { in: [null], out: 'number', category: 'list', icon: 'ti ti-indent-increase' },
rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' },
dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' },
seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' },
random: { in: ['number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' },
dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' },
seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' },
randomPick: { in: [0], out: 0, category: 'random', icon: 'ti ti-dice' },
dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'ti ti-dice' },
seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'ti ti-dice' },
DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'ti ti-dice' }, // dailyRandomPickWithProbabilityMapping
};
export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, visitor?: any) {

View File

@@ -63,7 +63,7 @@ export class HpmlTypeChecker {
@autobind
public getExpectedType(v: Expr, slot: number): Type {
const def = funcDefs[v.type || ''];
const def = funcDefs[v.type ?? ''];
if (def == null) {
throw new Error('Unknown type: ' + v.type);
}
@@ -107,7 +107,7 @@ export class HpmlTypeChecker {
return pageVar.type;
}
const envVar = envVarsDef[v.value || ''];
const envVar = envVarsDef[v.value ?? ''];
if (envVar !== undefined) {
return envVar;
}

View File

@@ -1,4 +1,3 @@
import { Ref } from 'vue';
export function calcPopupPosition(el: HTMLElement, props: {
anchorElement: HTMLElement | null;

View File

@@ -10,7 +10,7 @@ export function getScrollContainer(el: HTMLElement | null): HTMLElement | null {
}
}
export function getStickyTop(el: HTMLElement, container: HTMLElement | null = null, top: number = 0) {
export function getStickyTop(el: HTMLElement, container: HTMLElement | null = null, top = 0) {
if (!el.parentElement) return top;
const data = el.dataset.stickyContainerHeaderHeight;
const newTop = data ? Number(data) + top : top;
@@ -23,14 +23,14 @@ export function getScrollPosition(el: HTMLElement | null): number {
return container == null ? window.scrollY : container.scrollTop;
}
export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance: number = 1, once: boolean = false) {
export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) {
// とりあえず評価してみる
if (isTopVisible(el)) {
cb();
if (once) return null;
}
const container = getScrollContainer(el) || window;
const container = getScrollContainer(el) ?? window;
const onScroll = ev => {
if (!document.body.contains(el)) return;
@@ -45,7 +45,7 @@ export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance: numbe
return removeListener;
}
export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance: number = 1, once: boolean = false) {
export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) {
const container = getScrollContainer(el);
// とりあえず評価してみる
@@ -54,7 +54,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance: nu
if (once) return null;
}
const containerOrWindow = container || window;
const containerOrWindow = container ?? window;
const onScroll = ev => {
if (!document.body.contains(el)) return;
if (isBottomVisible(el, 1, container)) {
@@ -104,12 +104,12 @@ export function scrollToBottom(
} else {
window.scroll({
top: (el.scrollHeight - window.innerHeight + getStickyTop(el, container) + (window.innerWidth <= 500 ? 96 : 0)) || 0,
...options
...options,
});
}
}
export function isTopVisible(el: HTMLElement, tolerance: number = 1): boolean {
export function isTopVisible(el: HTMLElement, tolerance = 1): boolean {
const scrollTop = getScrollPosition(el);
return scrollTop <= tolerance;
}
@@ -124,6 +124,6 @@ export function getBodyScrollHeight() {
return Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
document.body.clientHeight, document.documentElement.clientHeight,
);
}

View File

@@ -1,63 +0,0 @@
import * as os from '@/os';
import { i18n } from '@/i18n';
import { mainRouter } from '@/router';
export async function search() {
const { canceled, result: query } = await os.inputText({
title: i18n.ts.search,
});
if (canceled || query == null || query === '') return;
const q = query.trim();
if (q.startsWith('@') && !q.includes(' ')) {
mainRouter.push(`/${q}`);
return;
}
if (q.startsWith('#')) {
mainRouter.push(`/tags/${encodeURIComponent(q.substr(1))}`);
return;
}
// like 2018/03/12
if (/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/.test(q.replace(/-/g, '/'))) {
const date = new Date(q.replace(/-/g, '/'));
// 日付しか指定されてない場合、例えば 2018/03/12 ならユーザーは
// 2018/03/12 のコンテンツを「含む」結果になることを期待するはずなので
// 23時間59分進める(そのままだと 2018/03/12 00:00:00 「まで」の
// 結果になってしまい、2018/03/12 のコンテンツは含まれない)
if (q.replace(/-/g, '/').match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/)) {
date.setHours(23, 59, 59, 999);
}
// TODO
//v.$root.$emit('warp', date);
os.alert({
icon: 'fas fa-history',
iconOnly: true, autoClose: true,
});
return;
}
if (q.startsWith('https://')) {
const promise = os.api('ap/show', {
uri: q,
});
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
const res = await promise;
if (res.type === 'User') {
mainRouter.push(`/@${res.object.username}@${res.object.host}`);
} else if (res.type === 'Note') {
mainRouter.push(`/notes/${res.object.id}`);
}
return;
}
mainRouter.push(`/search?q=${encodeURIComponent(q)}`);
}

View File

@@ -0,0 +1,19 @@
import { onMounted, onUnmounted, ref, Ref } from 'vue';
export function useDocumentVisibility(): Ref<DocumentVisibilityState> {
const visibility = ref(document.visibilityState);
const onChange = (): void => {
visibility.value = document.visibilityState;
};
onMounted(() => {
document.addEventListener('visibilitychange', onChange);
});
onUnmounted(() => {
document.removeEventListener('visibilitychange', onChange);
});
return visibility;
}

View File

@@ -1,6 +1,4 @@
import { inject, onUnmounted, Ref } from 'vue';
import { i18n } from '@/i18n';
import * as os from '@/os';
import { Ref } from 'vue';
export function useLeaveGuard(enabled: Ref<boolean>) {
/* TODO