diff options
Diffstat (limited to 'packages/frontend/src/scripts')
110 files changed, 0 insertions, 10358 deletions
diff --git a/packages/frontend/src/scripts/achievements.ts b/packages/frontend/src/scripts/achievements.ts deleted file mode 100644 index f5d0ab559f..0000000000 --- a/packages/frontend/src/scripts/achievements.ts +++ /dev/null @@ -1,513 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { $i } from '@/account.js'; - -export const ACHIEVEMENT_TYPES = [ - 'notes1', - 'notes10', - 'notes100', - 'notes500', - 'notes1000', - 'notes5000', - 'notes10000', - 'notes20000', - 'notes30000', - 'notes40000', - 'notes50000', - 'notes60000', - 'notes70000', - 'notes80000', - 'notes90000', - 'notes100000', - 'login3', - 'login7', - 'login15', - 'login30', - 'login60', - 'login100', - 'login200', - 'login300', - 'login400', - 'login500', - 'login600', - 'login700', - 'login800', - 'login900', - 'login1000', - 'passedSinceAccountCreated1', - 'passedSinceAccountCreated2', - 'passedSinceAccountCreated3', - 'loggedInOnBirthday', - 'loggedInOnNewYearsDay', - 'noteClipped1', - 'noteFavorited1', - 'myNoteFavorited1', - 'profileFilled', - 'markedAsCat', - 'following1', - 'following10', - 'following50', - 'following100', - 'following300', - 'followers1', - 'followers10', - 'followers50', - 'followers100', - 'followers300', - 'followers500', - 'followers1000', - 'collectAchievements30', - 'viewAchievements3min', - 'iLoveMisskey', - 'foundTreasure', - 'client30min', - 'client60min', - 'noteDeletedWithin1min', - 'postedAtLateNight', - 'postedAt0min0sec', - 'selfQuote', - 'htl20npm', - 'viewInstanceChart', - 'outputHelloWorldOnScratchpad', - 'open3windows', - 'driveFolderCircularReference', - 'reactWithoutRead', - 'clickedClickHere', - 'justPlainLucky', - 'setNameToSyuilo', - 'cookieClicked', - 'brainDiver', - 'smashTestNotificationButton', - 'tutorialCompleted', - 'bubbleGameExplodingHead', - 'bubbleGameDoubleExplodingHead', -] as const; - -export const ACHIEVEMENT_BADGES = { - 'notes1': { - img: '/fluent-emoji/1f4dd.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'notes10': { - img: '/fluent-emoji/1f4d1.png', - bg: null, - frame: 'bronze', - }, - 'notes100': { - img: '/fluent-emoji/1f4d2.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'notes500': { - img: '/fluent-emoji/1f4da.png', - bg: null, - frame: 'bronze', - }, - 'notes1000': { - img: '/fluent-emoji/1f5c3.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'notes5000': { - img: '/fluent-emoji/1f304.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'notes10000': { - img: '/fluent-emoji/1f3d9.png', - bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', - frame: 'silver', - }, - 'notes20000': { - img: '/fluent-emoji/1f307.png', - bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', - frame: 'silver', - }, - 'notes30000': { - img: '/fluent-emoji/1f306.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'silver', - }, - 'notes40000': { - img: '/fluent-emoji/1f303.png', - bg: 'linear-gradient(0deg, rgb(197 69 192), rgb(2 112 155))', - frame: 'silver', - }, - 'notes50000': { - img: '/fluent-emoji/1fa90.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'gold', - }, - 'notes60000': { - img: '/fluent-emoji/2604.png', - bg: 'linear-gradient(0deg, rgb(197 69 192), rgb(2 112 155))', - frame: 'gold', - }, - 'notes70000': { - img: '/fluent-emoji/1f30c.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'gold', - }, - 'notes80000': { - img: '/fluent-emoji/1f30c.png', - bg: 'linear-gradient(0deg, rgb(197 69 192), rgb(2 112 155))', - frame: 'gold', - }, - 'notes90000': { - img: '/fluent-emoji/1f30c.png', - bg: 'linear-gradient(0deg, rgb(255 232 119), rgb(255 140 41))', - frame: 'gold', - }, - 'notes100000': { - img: '/fluent-emoji/267e.png', - bg: 'linear-gradient(0deg, rgb(255 232 119), rgb(255 140 41))', - frame: 'platinum', - }, - 'login3': { - img: '/fluent-emoji/1f331.png', - bg: null, - frame: 'bronze', - }, - 'login7': { - img: '/fluent-emoji/1f331.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'login15': { - img: '/fluent-emoji/1f331.png', - bg: 'linear-gradient(0deg, rgb(144, 224, 255), rgb(255, 168, 252))', - frame: 'bronze', - }, - 'login30': { - img: '/fluent-emoji/1fab4.png', - bg: null, - frame: 'bronze', - }, - 'login60': { - img: '/fluent-emoji/1fab4.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'login100': { - img: '/fluent-emoji/1fab4.png', - bg: 'linear-gradient(0deg, rgb(144, 224, 255), rgb(255, 168, 252))', - frame: 'silver', - }, - 'login200': { - img: '/fluent-emoji/1f333.png', - bg: null, - frame: 'silver', - }, - 'login300': { - img: '/fluent-emoji/1f333.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'silver', - }, - 'login400': { - img: '/fluent-emoji/1f333.png', - bg: 'linear-gradient(0deg, rgb(144, 224, 255), rgb(255, 168, 252))', - frame: 'silver', - }, - 'login500': { - img: '/fluent-emoji/1f304.png', - bg: null, - frame: 'silver', - }, - 'login600': { - img: '/fluent-emoji/1f304.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'gold', - }, - 'login700': { - img: '/fluent-emoji/1f304.png', - bg: 'linear-gradient(0deg, rgb(144, 224, 255), rgb(255, 168, 252))', - frame: 'gold', - }, - 'login800': { - img: '/fluent-emoji/1f307.png', - bg: null, - frame: 'gold', - }, - 'login900': { - img: '/fluent-emoji/1f307.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'gold', - }, - 'login1000': { - img: '/fluent-emoji/1f307.png', - bg: 'linear-gradient(0deg, rgb(144, 224, 255), rgb(255, 168, 252))', - frame: 'platinum', - }, - 'noteClipped1': { - img: '/fluent-emoji/1f587.png', - bg: null, - frame: 'bronze', - }, - 'noteFavorited1': { - img: '/fluent-emoji/1f31f.png', - bg: null, - frame: 'bronze', - }, - 'myNoteFavorited1': { - img: '/fluent-emoji/1f320.png', - bg: null, - frame: 'silver', - }, - 'profileFilled': { - img: '/fluent-emoji/1f44c.png', - bg: 'linear-gradient(0deg, rgb(187 183 59), rgb(255 143 77))', - frame: 'bronze', - }, - 'markedAsCat': { - img: '/fluent-emoji/1f408.png', - bg: 'linear-gradient(0deg, rgb(187 183 59), rgb(255 143 77))', - frame: 'bronze', - }, - 'following1': { - img: '/fluent-emoji/2618.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'following10': { - img: '/fluent-emoji/1f6b8.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'following50': { - img: '/fluent-emoji/1f91d.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'following100': { - img: '/fluent-emoji/1f4af.png', - bg: 'linear-gradient(0deg, rgb(255 53 184), rgb(255 206 69))', - frame: 'silver', - }, - 'following300': { - img: '/fluent-emoji/1f970.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'silver', - }, - 'followers1': { - img: '/fluent-emoji/2618.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'followers10': { - img: '/fluent-emoji/1f44b.png', - bg: 'linear-gradient(0deg, rgb(59 187 116), rgb(199 211 102))', - frame: 'bronze', - }, - 'followers50': { - img: '/fluent-emoji/1f411.png', - bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', - frame: 'bronze', - }, - 'followers100': { - img: '/fluent-emoji/1f60e.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'silver', - }, - 'followers300': { - img: '/fluent-emoji/1f3c6.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'silver', - }, - 'followers500': { - img: '/fluent-emoji/1f4e1.png', - bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', - frame: 'gold', - }, - 'followers1000': { - img: '/fluent-emoji/1f451.png', - bg: 'linear-gradient(0deg, rgb(255 232 119), rgb(255 140 41))', - frame: 'platinum', - }, - 'collectAchievements30': { - img: '/fluent-emoji/1f3c5.png', - bg: 'linear-gradient(0deg, rgb(255 77 77), rgb(247 155 214))', - frame: 'silver', - }, - 'viewAchievements3min': { - img: '/fluent-emoji/1f3c5.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'bronze', - }, - 'iLoveMisskey': { - img: '/fluent-emoji/2764.png', - bg: 'linear-gradient(0deg, rgb(255 77 77), rgb(247 155 214))', - frame: 'silver', - }, - 'foundTreasure': { - img: '/fluent-emoji/1f3c6.png', - bg: 'linear-gradient(0deg, rgb(197 69 192), rgb(2 112 155))', - frame: 'gold', - }, - 'client30min': { - img: '/fluent-emoji/1f552.png', - bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', - frame: 'bronze', - }, - 'client60min': { - img: '/fluent-emoji/1f552.png', - bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', - frame: 'silver', - }, - 'noteDeletedWithin1min': { - img: '/fluent-emoji/1f5d1.png', - bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', - frame: 'bronze', - }, - 'postedAtLateNight': { - img: '/fluent-emoji/1f319.png', - bg: 'linear-gradient(0deg, rgb(197 69 192), rgb(2 112 155))', - frame: 'bronze', - }, - 'postedAt0min0sec': { - img: '/fluent-emoji/1f55b.png', - bg: 'linear-gradient(0deg, rgb(58 231 198), rgb(37 194 255))', - frame: 'bronze', - }, - 'selfQuote': { - img: '/fluent-emoji/1f4dd.png', - bg: null, - frame: 'bronze', - }, - 'htl20npm': { - img: '/fluent-emoji/1f30a.png', - bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', - frame: 'bronze', - }, - 'viewInstanceChart': { - img: '/fluent-emoji/1f4ca.png', - bg: 'linear-gradient(0deg, rgb(58 231 198), rgb(37 194 255))', - frame: 'bronze', - }, - 'outputHelloWorldOnScratchpad': { - img: '/fluent-emoji/1f530.png', - bg: 'linear-gradient(0deg, rgb(58 231 198), rgb(37 194 255))', - frame: 'bronze', - }, - 'open3windows': { - img: '/fluent-emoji/1f5a5.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'bronze', - }, - 'driveFolderCircularReference': { - img: '/fluent-emoji/1f4c2.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'bronze', - }, - 'reactWithoutRead': { - img: '/fluent-emoji/2753.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'bronze', - }, - 'clickedClickHere': { - img: '/fluent-emoji/2757.png', - bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', - frame: 'bronze', - }, - 'justPlainLucky': { - img: '/fluent-emoji/1f340.png', - bg: 'linear-gradient(0deg, rgb(187 183 59), rgb(255 143 77))', - frame: 'silver', - }, - 'setNameToSyuilo': { - img: '/fluent-emoji/1f36e.png', - bg: 'linear-gradient(0deg, rgb(187 183 59), rgb(255 143 77))', - frame: 'bronze', - }, - 'passedSinceAccountCreated1': { - img: '/fluent-emoji/0031-20e3.png', - bg: null, - frame: 'bronze', - }, - 'passedSinceAccountCreated2': { - img: '/fluent-emoji/0032-20e3.png', - bg: null, - frame: 'silver', - }, - 'passedSinceAccountCreated3': { - img: '/fluent-emoji/0033-20e3.png', - bg: null, - frame: 'gold', - }, - 'loggedInOnBirthday': { - img: '/fluent-emoji/1f382.png', - bg: 'linear-gradient(0deg, rgb(255 77 77), rgb(247 155 214))', - frame: 'silver', - }, - 'loggedInOnNewYearsDay': { - img: '/fluent-emoji/1f38d.png', - bg: 'linear-gradient(0deg, rgb(255 144 144), rgb(255 232 168))', - frame: 'silver', - }, - 'cookieClicked': { - img: '/fluent-emoji/1f36a.png', - bg: 'linear-gradient(0deg, rgb(187 183 59), rgb(255 143 77))', - frame: 'bronze', - }, - 'brainDiver': { - img: '/fluent-emoji/1f9e0.png', - bg: 'linear-gradient(0deg, rgb(144, 224, 255), rgb(255, 168, 252))', - frame: 'bronze', - }, - 'smashTestNotificationButton': { - img: '/fluent-emoji/1f514.png', - bg: 'linear-gradient(0deg, rgb(187 183 59), rgb(255 143 77))', - frame: 'bronze', - }, - 'tutorialCompleted': { - img: '/fluent-emoji/1f393.png', - bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', - frame: 'bronze', - }, - 'bubbleGameExplodingHead': { - img: '/fluent-emoji/1f92f.png', - bg: 'linear-gradient(0deg, rgb(255 77 77), rgb(247 155 214))', - frame: 'bronze', - }, - 'bubbleGameDoubleExplodingHead': { - img: '/fluent-emoji/1f92f.png', - bg: 'linear-gradient(0deg, rgb(255 77 77), rgb(247 155 214))', - frame: 'silver', - }, -/* @see <https://github.com/misskey-dev/misskey/pull/10365#discussion_r1155511107> -} as const satisfies Record<typeof ACHIEVEMENT_TYPES[number], { - img: string; - bg: string | null; - frame: 'bronze' | 'silver' | 'gold' | 'platinum'; -}>; - */ -} as const; - -export const claimedAchievements: typeof ACHIEVEMENT_TYPES[number][] = ($i && $i.achievements) ? $i.achievements.map(x => x.name) : []; - -const claimingQueue = new Set<string>(); - -export async function claimAchievement(type: typeof ACHIEVEMENT_TYPES[number]) { - if ($i == null) return; - if ($i.movedTo) return; - if (claimedAchievements.includes(type)) return; - claimingQueue.add(type); - claimedAchievements.push(type); - await new Promise(resolve => setTimeout(resolve, (claimingQueue.size - 1) * 500)); - window.setTimeout(() => { - claimingQueue.delete(type); - }, 500); - misskeyApi('i/claim-achievement', { name: type }); -} - -if (_DEV_) { - (window as any).unlockAllAchievements = () => { - for (const t of ACHIEVEMENT_TYPES) { - claimAchievement(t); - } - }; -} diff --git a/packages/frontend/src/scripts/admin-lookup.ts b/packages/frontend/src/scripts/admin-lookup.ts deleted file mode 100644 index 1b57b853c9..0000000000 --- a/packages/frontend/src/scripts/admin-lookup.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; -import { i18n } from '@/i18n.js'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; - -export async function lookupUser() { - const { canceled, result } = await os.inputText({ - title: i18n.ts.usernameOrUserId, - }); - if (canceled) return; - - const show = (user) => { - os.pageWindow(`/admin/user/${user.id}`); - }; - - const usernamePromise = misskeyApi('users/show', Misskey.acct.parse(result)); - const idPromise = misskeyApi('users/show', { userId: result }); - let _notFound = false; - const notFound = () => { - if (_notFound) { - os.alert({ - type: 'error', - text: i18n.ts.noSuchUser, - }); - } else { - _notFound = true; - } - }; - usernamePromise.then(show).catch(err => { - if (err.code === 'NO_SUCH_USER') { - notFound(); - } - }); - idPromise.then(show).catch(err => { - notFound(); - }); -} - -export async function lookupUserByEmail() { - const { canceled, result } = await os.inputText({ - title: i18n.ts.emailAddress, - type: 'email', - }); - if (canceled) return; - - try { - const user = await os.apiWithDialog('admin/accounts/find-by-email', { email: result }); - - os.pageWindow(`/admin/user/${user.id}`); - } catch (err) { - if (err.code === 'USER_NOT_FOUND') { - os.alert({ - type: 'error', - text: i18n.ts.noSuchUser, - }); - } else { - throw err; - } - } -} - -export async function lookupFile() { - const { canceled, result: q } = await os.inputText({ - title: i18n.ts.fileIdOrUrl, - minLength: 1, - }); - if (canceled) return; - - const show = (file) => { - os.pageWindow(`/admin/file/${file.id}`); - }; - - misskeyApi('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => { - show(file); - }).catch(err => { - if (err.code === 'NO_SUCH_FILE') { - os.alert({ - type: 'error', - text: i18n.ts.notFound, - }); - } - }); -} diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts deleted file mode 100644 index e203c51bba..0000000000 --- a/packages/frontend/src/scripts/aiscript/api.ts +++ /dev/null @@ -1,125 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { errors, utils, values } from '@syuilo/aiscript'; -import * as Misskey from 'misskey-js'; -import { url, lang } from '@@/js/config.js'; -import { assertStringAndIsIn } from './common.js'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { $i } from '@/account.js'; -import { miLocalStorage } from '@/local-storage.js'; -import { customEmojis } from '@/custom-emojis.js'; - -const DIALOG_TYPES = [ - 'error', - 'info', - 'success', - 'warning', - 'waiting', - 'question', -] as const; - -export function aiScriptReadline(q: string): Promise<string> { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ result: a }) => { - ok(a ?? ''); - }); - }); -} - -export function createAiScriptEnv(opts: { storageKey: string, token?: string }) { - return { - USER_ID: $i ? values.STR($i.id) : values.NULL, - USER_NAME: $i?.name ? values.STR($i.name) : values.NULL, - USER_USERNAME: $i ? values.STR($i.username) : values.NULL, - CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value), - LOCALE: values.STR(lang), - SERVER_URL: values.STR(url), - 'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => { - utils.assertString(title); - utils.assertString(text); - if (type != null) { - assertStringAndIsIn(type, DIALOG_TYPES); - } - await os.alert({ - type: type ? type.value : 'info', - title: title.value, - text: text.value, - }); - return values.NULL; - }), - 'Mk:confirm': values.FN_NATIVE(async ([title, text, type]) => { - utils.assertString(title); - utils.assertString(text); - if (type != null) { - assertStringAndIsIn(type, DIALOG_TYPES); - } - const confirm = await os.confirm({ - type: type ? type.value : 'question', - title: title.value, - text: text.value, - }); - return confirm.canceled ? values.FALSE : values.TRUE; - }), - 'Mk:api': values.FN_NATIVE(async ([ep, param, token]) => { - utils.assertString(ep); - if (ep.value.includes('://')) { - throw new errors.AiScriptRuntimeError('invalid endpoint'); - } - if (token) { - utils.assertString(token); - // バグがあればundefinedもあり得るため念のため - if (typeof token.value !== 'string') throw new Error('invalid token'); - } - const actualToken: string|null = token?.value ?? opts.token ?? null; - if (param == null) { - throw new errors.AiScriptRuntimeError('expected param'); - } - utils.assertObject(param); - return misskeyApi(ep.value, utils.valToJs(param) as object, actualToken).then(res => { - return utils.jsToVal(res); - }, err => { - return values.ERROR('request_failed', utils.jsToVal(err)); - }); - }), - /* セキュリティ上の問題があるため無効化 - 'Mk:apiExternal': values.FN_NATIVE(async ([host, ep, param, token]) => { - utils.assertString(host); - utils.assertString(ep); - if (token) utils.assertString(token); - return os.apiExternal(host.value, ep.value, utils.valToJs(param), token?.value).then(res => { - return utils.jsToVal(res); - }, err => { - return values.ERROR('request_failed', utils.jsToVal(err)); - }); - }), - */ - 'Mk:save': values.FN_NATIVE(([key, value]) => { - utils.assertString(key); - utils.expectAny(value); - miLocalStorage.setItem(`aiscript:${opts.storageKey}:${key.value}`, JSON.stringify(utils.valToJs(value))); - return values.NULL; - }), - 'Mk:load': values.FN_NATIVE(([key]) => { - utils.assertString(key); - return utils.jsToVal(miLocalStorage.getItemAsJson(`aiscript:${opts.storageKey}:${key.value}`) ?? null); - }), - 'Mk:remove': values.FN_NATIVE(([key]) => { - utils.assertString(key); - miLocalStorage.removeItem(`aiscript:${opts.storageKey}:${key.value}`); - return values.NULL; - }), - 'Mk:url': values.FN_NATIVE(() => { - return values.STR(window.location.href); - }), - 'Mk:nyaize': values.FN_NATIVE(([text]) => { - utils.assertString(text); - return values.STR(Misskey.nyaize(text.value)); - }), - }; -} diff --git a/packages/frontend/src/scripts/aiscript/common.ts b/packages/frontend/src/scripts/aiscript/common.ts deleted file mode 100644 index de6fa1d633..0000000000 --- a/packages/frontend/src/scripts/aiscript/common.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { errors, utils, type values } from '@syuilo/aiscript'; - -export function assertStringAndIsIn<A extends readonly string[]>(value: values.Value | undefined, expects: A): asserts value is values.VStr & { value: A[number] } { - utils.assertString(value); - const str = value.value; - if (!expects.includes(str)) { - const expected = expects.map((expect) => `"${expect}"`).join(', '); - throw new errors.AiScriptRuntimeError(`"${value.value}" is not in ${expected}`); - } -} diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts deleted file mode 100644 index ca92b27ff5..0000000000 --- a/packages/frontend/src/scripts/aiscript/ui.ts +++ /dev/null @@ -1,654 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { utils, values } from '@syuilo/aiscript'; -import { v4 as uuid } from 'uuid'; -import { ref, Ref } from 'vue'; -import * as Misskey from 'misskey-js'; -import { assertStringAndIsIn } from './common.js'; - -const ALIGNS = ['left', 'center', 'right'] as const; -const FONTS = ['serif', 'sans-serif', 'monospace'] as const; -const BORDER_STYLES = ['hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'] as const; - -type Align = (typeof ALIGNS)[number]; -type Font = (typeof FONTS)[number]; -type BorderStyle = (typeof BORDER_STYLES)[number]; - -export type AsUiComponentBase = { - id: string; - hidden?: boolean; -}; - -export type AsUiRoot = AsUiComponentBase & { - type: 'root'; - children: AsUiComponent['id'][]; -}; - -export type AsUiContainer = AsUiComponentBase & { - type: 'container'; - children?: AsUiComponent['id'][]; - align?: Align; - bgColor?: string; - fgColor?: string; - font?: Font; - borderWidth?: number; - borderColor?: string; - borderStyle?: BorderStyle; - borderRadius?: number; - padding?: number; - rounded?: boolean; - hidden?: boolean; -}; - -export type AsUiText = AsUiComponentBase & { - type: 'text'; - text?: string; - size?: number; - bold?: boolean; - color?: string; - font?: Font; -}; - -export type AsUiMfm = AsUiComponentBase & { - type: 'mfm'; - text?: string; - size?: number; - bold?: boolean; - color?: string; - font?: Font; - onClickEv?: (evId: string) => Promise<void>; -}; - -export type AsUiButton = AsUiComponentBase & { - type: 'button'; - text?: string; - onClick?: () => Promise<void>; - primary?: boolean; - rounded?: boolean; - disabled?: boolean; -}; - -export type AsUiButtons = AsUiComponentBase & { - type: 'buttons'; - buttons?: AsUiButton[]; -}; - -export type AsUiSwitch = AsUiComponentBase & { - type: 'switch'; - onChange?: (v: boolean) => Promise<void>; - default?: boolean; - label?: string; - caption?: string; -}; - -export type AsUiTextarea = AsUiComponentBase & { - type: 'textarea'; - onInput?: (v: string) => Promise<void>; - default?: string; - label?: string; - caption?: string; -}; - -export type AsUiTextInput = AsUiComponentBase & { - type: 'textInput'; - onInput?: (v: string) => Promise<void>; - default?: string; - label?: string; - caption?: string; -}; - -export type AsUiNumberInput = AsUiComponentBase & { - type: 'numberInput'; - onInput?: (v: number) => Promise<void>; - default?: number; - label?: string; - caption?: string; -}; - -export type AsUiSelect = AsUiComponentBase & { - type: 'select'; - items?: { - text: string; - value: string; - }[]; - onChange?: (v: string) => Promise<void>; - default?: string; - label?: string; - caption?: string; -}; - -export type AsUiFolder = AsUiComponentBase & { - type: 'folder'; - children?: AsUiComponent['id'][]; - title?: string; - opened?: boolean; -}; - -type PostFormPropsForAsUi = { - text: string; - cw?: string; - visibility?: (typeof Misskey.noteVisibilities)[number]; - localOnly?: boolean; -}; - -export type AsUiPostFormButton = AsUiComponentBase & { - type: 'postFormButton'; - text?: string; - primary?: boolean; - rounded?: boolean; - form?: PostFormPropsForAsUi; -}; - -export type AsUiPostForm = AsUiComponentBase & { - type: 'postForm'; - form?: PostFormPropsForAsUi; -}; - -export type AsUiComponent = AsUiRoot | AsUiContainer | AsUiText | AsUiMfm | AsUiButton | AsUiButtons | AsUiSwitch | AsUiTextarea | AsUiTextInput | AsUiNumberInput | AsUiSelect | AsUiFolder | AsUiPostFormButton | AsUiPostForm; - -type Options<T extends AsUiComponent> = T extends AsUiButtons - ? Omit<T, 'id' | 'type' | 'buttons'> & { 'buttons'?: Options<AsUiButton>[] } - : Omit<T, 'id' | 'type'>; - -export function patch(id: string, def: values.Value, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>) { - // TODO -} - -function getRootOptions(def: values.Value | undefined): Options<AsUiRoot> { - utils.assertObject(def); - - const children = def.value.get('children'); - utils.assertArray(children); - - return { - children: children.value.map(v => { - utils.assertObject(v); - const id = v.value.get('id'); - utils.assertString(id); - return id.value; - }), - }; -} - -function getContainerOptions(def: values.Value | undefined): Options<AsUiContainer> { - utils.assertObject(def); - - const children = def.value.get('children'); - if (children) utils.assertArray(children); - const align = def.value.get('align'); - if (align) assertStringAndIsIn(align, ALIGNS); - const bgColor = def.value.get('bgColor'); - if (bgColor) utils.assertString(bgColor); - const fgColor = def.value.get('fgColor'); - if (fgColor) utils.assertString(fgColor); - const font = def.value.get('font'); - if (font) assertStringAndIsIn(font, FONTS); - const borderWidth = def.value.get('borderWidth'); - if (borderWidth) utils.assertNumber(borderWidth); - const borderColor = def.value.get('borderColor'); - if (borderColor) utils.assertString(borderColor); - const borderStyle = def.value.get('borderStyle'); - if (borderStyle) assertStringAndIsIn(borderStyle, BORDER_STYLES); - const borderRadius = def.value.get('borderRadius'); - if (borderRadius) utils.assertNumber(borderRadius); - const padding = def.value.get('padding'); - if (padding) utils.assertNumber(padding); - const rounded = def.value.get('rounded'); - if (rounded) utils.assertBoolean(rounded); - const hidden = def.value.get('hidden'); - if (hidden) utils.assertBoolean(hidden); - - return { - children: children ? children.value.map(v => { - utils.assertObject(v); - const id = v.value.get('id'); - utils.assertString(id); - return id.value; - }) : [], - align: align?.value, - fgColor: fgColor?.value, - bgColor: bgColor?.value, - font: font?.value, - borderWidth: borderWidth?.value, - borderColor: borderColor?.value, - borderStyle: borderStyle?.value, - borderRadius: borderRadius?.value, - padding: padding?.value, - rounded: rounded?.value, - hidden: hidden?.value, - }; -} - -function getTextOptions(def: values.Value | undefined): Options<AsUiText> { - utils.assertObject(def); - - const text = def.value.get('text'); - if (text) utils.assertString(text); - const size = def.value.get('size'); - if (size) utils.assertNumber(size); - const bold = def.value.get('bold'); - if (bold) utils.assertBoolean(bold); - const color = def.value.get('color'); - if (color) utils.assertString(color); - const font = def.value.get('font'); - if (font) assertStringAndIsIn(font, FONTS); - - return { - text: text?.value, - size: size?.value, - bold: bold?.value, - color: color?.value, - font: font?.value, - }; -} - -function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiMfm> { - utils.assertObject(def); - - const text = def.value.get('text'); - if (text) utils.assertString(text); - const size = def.value.get('size'); - if (size) utils.assertNumber(size); - const bold = def.value.get('bold'); - if (bold) utils.assertBoolean(bold); - const color = def.value.get('color'); - if (color) utils.assertString(color); - const font = def.value.get('font'); - if (font) assertStringAndIsIn(font, FONTS); - const onClickEv = def.value.get('onClickEv'); - if (onClickEv) utils.assertFunction(onClickEv); - - return { - text: text?.value, - size: size?.value, - bold: bold?.value, - color: color?.value, - font: font?.value, - onClickEv: async (evId: string) => { - if (onClickEv) await call(onClickEv, [values.STR(evId)]); - }, - }; -} - -function getTextInputOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiTextInput> { - utils.assertObject(def); - - const onInput = def.value.get('onInput'); - if (onInput) utils.assertFunction(onInput); - const defaultValue = def.value.get('default'); - if (defaultValue) utils.assertString(defaultValue); - const label = def.value.get('label'); - if (label) utils.assertString(label); - const caption = def.value.get('caption'); - if (caption) utils.assertString(caption); - - return { - onInput: async (v) => { - if (onInput) await call(onInput, [utils.jsToVal(v)]); - }, - default: defaultValue?.value, - label: label?.value, - caption: caption?.value, - }; -} - -function getTextareaOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiTextarea> { - utils.assertObject(def); - - const onInput = def.value.get('onInput'); - if (onInput) utils.assertFunction(onInput); - const defaultValue = def.value.get('default'); - if (defaultValue) utils.assertString(defaultValue); - const label = def.value.get('label'); - if (label) utils.assertString(label); - const caption = def.value.get('caption'); - if (caption) utils.assertString(caption); - - return { - onInput: async (v) => { - if (onInput) await call(onInput, [utils.jsToVal(v)]); - }, - default: defaultValue?.value, - label: label?.value, - caption: caption?.value, - }; -} - -function getNumberInputOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiNumberInput> { - utils.assertObject(def); - - const onInput = def.value.get('onInput'); - if (onInput) utils.assertFunction(onInput); - const defaultValue = def.value.get('default'); - if (defaultValue) utils.assertNumber(defaultValue); - const label = def.value.get('label'); - if (label) utils.assertString(label); - const caption = def.value.get('caption'); - if (caption) utils.assertString(caption); - - return { - onInput: async (v) => { - if (onInput) await call(onInput, [utils.jsToVal(v)]); - }, - default: defaultValue?.value, - label: label?.value, - caption: caption?.value, - }; -} - -function getButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiButton> { - utils.assertObject(def); - - const text = def.value.get('text'); - if (text) utils.assertString(text); - const onClick = def.value.get('onClick'); - if (onClick) utils.assertFunction(onClick); - const primary = def.value.get('primary'); - if (primary) utils.assertBoolean(primary); - const rounded = def.value.get('rounded'); - if (rounded) utils.assertBoolean(rounded); - const disabled = def.value.get('disabled'); - if (disabled) utils.assertBoolean(disabled); - - return { - text: text?.value, - onClick: async () => { - if (onClick) await call(onClick, []); - }, - primary: primary?.value, - rounded: rounded?.value, - disabled: disabled?.value, - }; -} - -function getButtonsOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiButtons> { - utils.assertObject(def); - - const buttons = def.value.get('buttons'); - if (buttons) utils.assertArray(buttons); - - return { - buttons: buttons ? buttons.value.map(button => { - utils.assertObject(button); - const text = button.value.get('text'); - utils.assertString(text); - const onClick = button.value.get('onClick'); - utils.assertFunction(onClick); - const primary = button.value.get('primary'); - if (primary) utils.assertBoolean(primary); - const rounded = button.value.get('rounded'); - if (rounded) utils.assertBoolean(rounded); - const disabled = button.value.get('disabled'); - if (disabled) utils.assertBoolean(disabled); - - return { - text: text.value, - onClick: async () => { - await call(onClick, []); - }, - primary: primary?.value, - rounded: rounded?.value, - disabled: disabled?.value, - }; - }) : [], - }; -} - -function getSwitchOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiSwitch> { - utils.assertObject(def); - - const onChange = def.value.get('onChange'); - if (onChange) utils.assertFunction(onChange); - const defaultValue = def.value.get('default'); - if (defaultValue) utils.assertBoolean(defaultValue); - const label = def.value.get('label'); - if (label) utils.assertString(label); - const caption = def.value.get('caption'); - if (caption) utils.assertString(caption); - - return { - onChange: async (v) => { - if (onChange) await call(onChange, [utils.jsToVal(v)]); - }, - default: defaultValue?.value, - label: label?.value, - caption: caption?.value, - }; -} - -function getSelectOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiSelect> { - utils.assertObject(def); - - const items = def.value.get('items'); - if (items) utils.assertArray(items); - const onChange = def.value.get('onChange'); - if (onChange) utils.assertFunction(onChange); - const defaultValue = def.value.get('default'); - if (defaultValue) utils.assertString(defaultValue); - const label = def.value.get('label'); - if (label) utils.assertString(label); - const caption = def.value.get('caption'); - if (caption) utils.assertString(caption); - - return { - items: items ? items.value.map(item => { - utils.assertObject(item); - const text = item.value.get('text'); - utils.assertString(text); - const value = item.value.get('value'); - if (value) utils.assertString(value); - return { - text: text.value, - value: value ? value.value : text.value, - }; - }) : [], - onChange: async (v) => { - if (onChange) await call(onChange, [utils.jsToVal(v)]); - }, - default: defaultValue?.value, - label: label?.value, - caption: caption?.value, - }; -} - -function getFolderOptions(def: values.Value | undefined): Options<AsUiFolder> { - utils.assertObject(def); - - const children = def.value.get('children'); - if (children) utils.assertArray(children); - const title = def.value.get('title'); - if (title) utils.assertString(title); - const opened = def.value.get('opened'); - if (opened) utils.assertBoolean(opened); - - return { - children: children ? children.value.map(v => { - utils.assertObject(v); - const id = v.value.get('id'); - utils.assertString(id); - return id.value; - }) : [], - title: title?.value ?? '', - opened: opened?.value ?? true, - }; -} - -function getPostFormProps(form: values.VObj): PostFormPropsForAsUi { - const text = form.value.get('text'); - utils.assertString(text); - const cw = form.value.get('cw'); - if (cw) utils.assertString(cw); - const visibility = form.value.get('visibility'); - if (visibility) utils.assertString(visibility); - const localOnly = form.value.get('localOnly'); - if (localOnly) utils.assertBoolean(localOnly); - - return { - text: text.value, - cw: cw?.value, - visibility: (visibility?.value && (Misskey.noteVisibilities as readonly string[]).includes(visibility.value)) ? visibility.value as typeof Misskey.noteVisibilities[number] : undefined, - localOnly: localOnly?.value, - }; -} - -function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiPostFormButton> { - utils.assertObject(def); - - const text = def.value.get('text'); - if (text) utils.assertString(text); - const primary = def.value.get('primary'); - if (primary) utils.assertBoolean(primary); - const rounded = def.value.get('rounded'); - if (rounded) utils.assertBoolean(rounded); - const form = def.value.get('form'); - if (form) utils.assertObject(form); - - return { - text: text?.value, - primary: primary?.value, - rounded: rounded?.value, - form: form ? getPostFormProps(form) : { - text: '', - }, - }; -} - -function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiPostForm> { - utils.assertObject(def); - - const form = def.value.get('form'); - if (form) utils.assertObject(form); - - return { - form: form ? getPostFormProps(form) : { - text: '', - }, - }; -} - -export function registerAsUiLib(components: Ref<AsUiComponent>[], done: (root: Ref<AsUiRoot>) => void) { - type OptionsConverter<T extends AsUiComponent, C> = (def: values.Value | undefined, call: C) => Options<T>; - - const instances = {}; - - function createComponentInstance<T extends AsUiComponent, C>( - type: T['type'], - def: values.Value | undefined, - id: values.Value | undefined, - getOptions: OptionsConverter<T, C>, - call: C, - ) { - if (id) utils.assertString(id); - const _id = id?.value ?? uuid(); - const component = ref({ - ...getOptions(def, call), - type, - id: _id, - } as T); - components.push(component); - const instance = values.OBJ(new Map<string, values.Value>([ - ['id', values.STR(_id)], - ['update', values.FN_NATIVE(([def], opts) => { - utils.assertObject(def); - const updates = getOptions(def, call); - for (const update of def.value.keys()) { - if (!Object.hasOwn(updates, update)) continue; - component.value[update] = updates[update]; - } - })], - ])); - instances[_id] = instance; - return instance; - } - - const rootInstance = createComponentInstance('root', utils.jsToVal({ children: [] }), utils.jsToVal('___root___'), getRootOptions, () => {}); - const rootComponent = components[0] as Ref<AsUiRoot>; - done(rootComponent); - - return { - 'Ui:root': rootInstance, - - 'Ui:patch': values.FN_NATIVE(([id, val], opts) => { - utils.assertString(id); - utils.assertArray(val); - // patch(id.value, val.value, opts.call); // TODO - }), - - 'Ui:get': values.FN_NATIVE(([id], opts) => { - utils.assertString(id); - const instance = instances[id.value]; - if (instance) { - return instance; - } else { - return values.NULL; - } - }), - - // Ui:root.update({ children: [...] }) の糖衣構文 - 'Ui:render': values.FN_NATIVE(([children], opts) => { - utils.assertArray(children); - - rootComponent.value.children = children.value.map(v => { - utils.assertObject(v); - const id = v.value.get('id'); - utils.assertString(id); - return id.value; - }); - }), - - 'Ui:C:container': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('container', def, id, getContainerOptions, opts.topCall); - }), - - 'Ui:C:text': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('text', def, id, getTextOptions, opts.topCall); - }), - - 'Ui:C:mfm': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('mfm', def, id, getMfmOptions, opts.topCall); - }), - - 'Ui:C:textarea': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('textarea', def, id, getTextareaOptions, opts.topCall); - }), - - 'Ui:C:textInput': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('textInput', def, id, getTextInputOptions, opts.topCall); - }), - - 'Ui:C:numberInput': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('numberInput', def, id, getNumberInputOptions, opts.topCall); - }), - - 'Ui:C:button': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('button', def, id, getButtonOptions, opts.topCall); - }), - - 'Ui:C:buttons': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('buttons', def, id, getButtonsOptions, opts.topCall); - }), - - 'Ui:C:switch': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('switch', def, id, getSwitchOptions, opts.topCall); - }), - - 'Ui:C:select': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('select', def, id, getSelectOptions, opts.topCall); - }), - - 'Ui:C:folder': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('folder', def, id, getFolderOptions, opts.topCall); - }), - - 'Ui:C:postFormButton': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('postFormButton', def, id, getPostFormButtonOptions, opts.topCall); - }), - - 'Ui:C:postForm': values.FN_NATIVE(([def, id], opts) => { - return createComponentInstance('postForm', def, id, getPostFormOptions, opts.topCall); - }), - }; -} diff --git a/packages/frontend/src/scripts/array.ts b/packages/frontend/src/scripts/array.ts deleted file mode 100644 index f2feb29dfc..0000000000 --- a/packages/frontend/src/scripts/array.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -type EndoRelation<T> = (a: T, b: T) => boolean; -type Predicate<T> = (x: T) => boolean; - -/** - * Count the number of elements that satisfy the predicate - */ - -export function countIf<T>(f: Predicate<T>, xs: T[]): number { - return xs.filter(f).length; -} - -/** - * Count the number of elements that is equal to the element - */ -export function count<T>(a: T, xs: T[]): number { - return countIf(x => x === a, xs); -} - -/** - * Concatenate an array of arrays - */ -export function concat<T>(xss: T[][]): T[] { - return ([] as T[]).concat(...xss); -} - -/** - * Intersperse the element between the elements of the array - * @param sep The element to be interspersed - */ -export function intersperse<T>(sep: T, xs: T[]): T[] { - return concat(xs.map(x => [sep, x])).slice(1); -} - -/** - * Returns the array of elements that is not equal to the element - */ -export function erase<T>(a: T, xs: T[]): T[] { - return xs.filter(x => x !== a); -} - -/** - * Finds the array of all elements in the first array not contained in the second array. - * The order of result values are determined by the first array. - */ -export function difference<T>(xs: T[], ys: T[]): T[] { - return xs.filter(x => !ys.includes(x)); -} - -/** - * Remove all but the first element from every group of equivalent elements - */ -export function unique<T>(xs: T[]): T[] { - return [...new Set(xs)]; -} - -export function uniqueBy<TValue, TKey>(values: TValue[], keySelector: (value: TValue) => TKey): TValue[] { - const map = new Map<TKey, TValue>(); - - for (const value of values) { - const key = keySelector(value); - if (!map.has(key)) map.set(key, value); - } - - return [...map.values()]; -} - -export function sum(xs: number[]): number { - return xs.reduce((a, b) => a + b, 0); -} - -export function maximum(xs: number[]): number { - return Math.max(...xs); -} - -/** - * Compare two arrays by lexicographical order - */ -export function lessThan(xs: number[], ys: number[]): boolean { - for (let i = 0; i < Math.min(xs.length, ys.length); i++) { - if (xs[i] < ys[i]) return true; - if (xs[i] > ys[i]) return false; - } - return xs.length < ys.length; -} - -/** - * Returns the longest prefix of elements that satisfy the predicate - */ -export function takeWhile<T>(f: Predicate<T>, xs: T[]): T[] { - const ys: T[] = []; - for (const x of xs) { - if (f(x)) { - ys.push(x); - } else { - break; - } - } - return ys; -} - -export function cumulativeSum(xs: number[]): number[] { - const ys = Array.from(xs); // deep copy - for (let i = 1; i < ys.length; i++) ys[i] += ys[i - 1]; - return ys; -} - -export function toArray<T>(x: T | T[] | undefined): T[] { - return Array.isArray(x) ? x : x != null ? [x] : []; -} - -export function toSingle<T>(x: T | T[] | undefined): T | undefined { - return Array.isArray(x) ? x[0] : x; -} diff --git a/packages/frontend/src/scripts/autocomplete.ts b/packages/frontend/src/scripts/autocomplete.ts deleted file mode 100644 index 8a3a6bf6db..0000000000 --- a/packages/frontend/src/scripts/autocomplete.ts +++ /dev/null @@ -1,316 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { nextTick, Ref, ref, defineAsyncComponent } from 'vue'; -import getCaretCoordinates from 'textarea-caret'; -import { toASCII } from 'punycode.js'; -import { popup } from '@/os.js'; - -export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'mfmParam'; - -export class Autocomplete { - private suggestion: { - x: Ref<number>; - y: Ref<number>; - q: Ref<any>; - close: () => void; - } | null; - private textarea: HTMLInputElement | HTMLTextAreaElement; - private currentType: string; - private textRef: Ref<string | number | null>; - private opening: boolean; - private onlyType: SuggestionType[]; - - private get text(): string { - // Use raw .value to get the latest value - // (Because v-model does not update while composition) - return this.textarea.value; - } - - private set text(text: string) { - // Use ref value to notify other watchers - // (Because .value setter never fires input/change events) - this.textRef.value = text; - } - - /** - * 対象のテキストエリアを与えてインスタンスを初期化します。 - */ - constructor(textarea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string | number | null>, onlyType?: SuggestionType[]) { - //#region BIND - this.onInput = this.onInput.bind(this); - this.complete = this.complete.bind(this); - this.close = this.close.bind(this); - //#endregion - - this.suggestion = null; - this.textarea = textarea; - this.textRef = textRef; - this.opening = false; - this.onlyType = onlyType ?? ['user', 'hashtag', 'emoji', 'mfmTag', 'mfmParam']; - - this.attach(); - } - - /** - * このインスタンスにあるテキストエリアの入力のキャプチャを開始します。 - */ - public attach() { - this.textarea.addEventListener('input', this.onInput); - } - - /** - * このインスタンスにあるテキストエリアの入力のキャプチャを解除します。 - */ - public detach() { - this.textarea.removeEventListener('input', this.onInput); - this.close(); - } - - /** - * テキスト入力時 - */ - private onInput() { - const caretPos = this.textarea.selectionStart; - const text = this.text.substring(0, caretPos).split('\n').pop()!; - - const mentionIndex = text.lastIndexOf('@'); - const hashtagIndex = text.lastIndexOf('#'); - const emojiIndex = text.lastIndexOf(':'); - const mfmTagIndex = text.lastIndexOf('$'); - const mfmParamIndex = text.lastIndexOf('.'); - - const max = Math.max( - mentionIndex, - hashtagIndex, - emojiIndex, - mfmTagIndex); - - if (max === -1) { - this.close(); - return; - } - - const afterLastMfmParam = text.split(/\$\[[a-zA-Z]+/).pop(); - - const isMention = mentionIndex !== -1; - const isHashtag = hashtagIndex !== -1; - const isMfmParam = mfmParamIndex !== -1 && afterLastMfmParam?.includes('.') && !afterLastMfmParam?.includes(' '); - const isMfmTag = mfmTagIndex !== -1 && !isMfmParam; - const isEmoji = emojiIndex !== -1 && text.split(/:[\p{Letter}\p{Number}\p{Mark}_+-]+:/u).pop()!.includes(':'); - - let opened = false; - - if (isMention && this.onlyType.includes('user')) { - const username = text.substring(mentionIndex + 1); - if (username !== '' && username.match(/^[a-zA-Z0-9_]+$/)) { - this.open('user', username); - opened = true; - } else if (username === '') { - this.open('user', null); - opened = true; - } - } - - if (isHashtag && !opened && this.onlyType.includes('hashtag')) { - const hashtag = text.substring(hashtagIndex + 1); - if (!hashtag.includes(' ')) { - this.open('hashtag', hashtag); - opened = true; - } - } - - if (isEmoji && !opened && this.onlyType.includes('emoji')) { - const emoji = text.substring(emojiIndex + 1); - if (!emoji.includes(' ')) { - this.open('emoji', emoji.normalize('NFC')); - opened = true; - } - } - - if (isMfmTag && !opened && this.onlyType.includes('mfmTag')) { - const mfmTag = text.substring(mfmTagIndex + 1); - if (!mfmTag.includes(' ')) { - this.open('mfmTag', mfmTag.replace('[', '')); - opened = true; - } - } - - if (isMfmParam && !opened && this.onlyType.includes('mfmParam')) { - const mfmParam = text.substring(mfmParamIndex + 1); - if (!mfmParam.includes(' ')) { - this.open('mfmParam', { - tag: text.substring(mfmTagIndex + 2, mfmParamIndex), - params: mfmParam.split(','), - }); - opened = true; - } - } - - if (!opened) { - this.close(); - } - } - - /** - * サジェストを提示します。 - */ - private async open(type: string, q: any) { - if (type !== this.currentType) { - this.close(); - } - if (this.opening) return; - this.opening = true; - this.currentType = type; - - //#region サジェストを表示すべき位置を計算 - const caretPosition = getCaretCoordinates(this.textarea, this.textarea.selectionStart); - - const rect = this.textarea.getBoundingClientRect(); - - const x = rect.left + caretPosition.left - this.textarea.scrollLeft; - const y = rect.top + caretPosition.top - this.textarea.scrollTop; - //#endregion - - if (this.suggestion) { - this.suggestion.x.value = x; - this.suggestion.y.value = y; - this.suggestion.q.value = q; - - this.opening = false; - } else { - const _x = ref(x); - const _y = ref(y); - const _q = ref(q); - - const { dispose } = await popup(defineAsyncComponent(() => import('@/components/MkAutocomplete.vue')), { - textarea: this.textarea, - close: this.close, - type: type, - q: _q, - x: _x, - y: _y, - }, { - done: (res) => { - this.complete(res); - }, - }); - - this.suggestion = { - q: _q, - x: _x, - y: _y, - close: () => dispose(), - }; - - this.opening = false; - } - } - - /** - * サジェストを閉じます。 - */ - private close() { - if (this.suggestion == null) return; - - this.suggestion.close(); - this.suggestion = null; - - this.textarea.focus(); - } - - /** - * オートコンプリートする - */ - private complete({ type, value }) { - this.close(); - - const caret = this.textarea.selectionStart; - - if (type === 'user') { - const source = this.text; - - const before = source.substring(0, caret); - const trimmedBefore = before.substring(0, before.lastIndexOf('@')); - const after = source.substring(caret); - - const acct = value.host === null ? value.username : `${value.username}@${toASCII(value.host)}`; - - // 挿入 - this.text = `${trimmedBefore}@${acct} ${after}`; - - // キャレットを戻す - nextTick(() => { - this.textarea.focus(); - const pos = trimmedBefore.length + (acct.length + 2); - this.textarea.setSelectionRange(pos, pos); - }); - } else if (type === 'hashtag') { - const source = this.text; - - const before = source.substring(0, caret); - const trimmedBefore = before.substring(0, before.lastIndexOf('#')); - const after = source.substring(caret); - - // 挿入 - this.text = `${trimmedBefore}#${value} ${after}`; - - // キャレットを戻す - nextTick(() => { - this.textarea.focus(); - const pos = trimmedBefore.length + (value.length + 2); - this.textarea.setSelectionRange(pos, pos); - }); - } else if (type === 'emoji') { - const source = this.text; - - const before = source.substring(0, caret); - const trimmedBefore = before.substring(0, before.lastIndexOf(':')); - const after = source.substring(caret); - - // 挿入 - this.text = trimmedBefore + value + after; - - // キャレットを戻す - nextTick(() => { - this.textarea.focus(); - const pos = trimmedBefore.length + value.length; - this.textarea.setSelectionRange(pos, pos); - }); - } else if (type === 'mfmTag') { - const source = this.text; - - const before = source.substring(0, caret); - const trimmedBefore = before.substring(0, before.lastIndexOf('$')); - const after = source.substring(caret); - - // 挿入 - this.text = `${trimmedBefore}$[${value} ]${after}`; - - // キャレットを戻す - nextTick(() => { - this.textarea.focus(); - const pos = trimmedBefore.length + (value.length + 3); - this.textarea.setSelectionRange(pos, pos); - }); - } else if (type === 'mfmParam') { - const source = this.text; - - const before = source.substring(0, caret); - const trimmedBefore = before.substring(0, before.lastIndexOf('.')); - const after = source.substring(caret); - - // 挿入 - this.text = `${trimmedBefore}.${value}${after}`; - - // キャレットを戻す - nextTick(() => { - this.textarea.focus(); - const pos = trimmedBefore.length + (value.length + 1); - this.textarea.setSelectionRange(pos, pos); - }); - } - } -} diff --git a/packages/frontend/src/scripts/boost-quote.ts b/packages/frontend/src/scripts/boost-quote.ts deleted file mode 100644 index feb949772b..0000000000 --- a/packages/frontend/src/scripts/boost-quote.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SPDX-FileCopyrightText: dakkar and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only -*/ - -import { ref, Ref, computed, ComputedRef } from 'vue'; -import * as Misskey from 'misskey-js'; -import { i18n } from '@/i18n.js'; -import { defaultStore } from '@/store.js'; -import { MenuItem } from '@/types/menu.js'; - -/* - this script should eventually contain all Sharkey-specific bits of - boosting and quoting that we would otherwise have to replicate in - `{M,S}kNote{,Detailed,Sub}.vue` - */ - -export type Visibility = 'public' | 'home' | 'followers' | 'specified'; - -export function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility { - if (a === 'specified' || b === 'specified') return 'specified'; - if (a === 'followers' || b === 'followers') return 'followers'; - if (a === 'home' || b === 'home') return 'home'; - // if (a === 'public' || b === 'public') - return 'public'; -} - -export function visibilityIsAtLeast(a: Visibility | string, b: Visibility | string): boolean { - return smallerVisibility(a, b) === b; -} - -export function boostMenuItems(appearNote: Ref<Misskey.entities.Note>, renote: (v: Visibility, l: boolean) => void): MenuItem[] { - const localOnly = ref(defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); - const effectiveVisibility = ( - appearNote.value.channel?.isSensitive - ? smallerVisibility(appearNote.value.visibility, 'home') - : appearNote.value.visibility - ); - - const menuItems: MenuItem[] = []; - if (visibilityIsAtLeast(effectiveVisibility, 'public')) { - menuItems.push({ - type: 'button', - icon: 'ph-globe-hemisphere-west ph-bold ph-lg', - text: i18n.ts._visibility['public'], - action: () => { - renote('public', localOnly.value); - }, - } as MenuItem); - } - if (visibilityIsAtLeast(effectiveVisibility, 'home')) { - menuItems.push({ - type: 'button', - icon: 'ph-house ph-bold ph-lg', - text: i18n.ts._visibility['home'], - action: () => { - renote('home', localOnly.value); - }, - } as MenuItem); - } - if (visibilityIsAtLeast(effectiveVisibility, 'followers')) { - menuItems.push({ - type: 'button', - icon: 'ph-lock ph-bold ph-lg', - text: i18n.ts._visibility['followers'], - action: () => { - renote('followers', localOnly.value); - }, - } as MenuItem); - } - - return [ - ...menuItems, - { - type: 'switch', - icon: 'ph-planet ph-bold ph-lg', - text: i18n.ts._timelines.local, - ref: localOnly, - } as MenuItem, - ]; -} - -export function computeRenoteTooltip(renoted: Ref<boolean>): ComputedRef<string> { - return computed(() => { - if (renoted.value) return i18n.ts.unrenote; - if (defaultStore.state.showVisibilitySelectorOnBoost) return i18n.ts.renote; - return i18n.ts.renoteShift; - }); -} diff --git a/packages/frontend/src/scripts/cache.ts b/packages/frontend/src/scripts/cache.ts deleted file mode 100644 index 0fbdf34d5d..0000000000 --- a/packages/frontend/src/scripts/cache.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { ref } from 'vue'; - -export class Cache<T> { - private cachedAt: number | null = null; - public value = ref<T | undefined>(); - private lifetime: number; - private fetcher: () => Promise<T>; - - constructor(lifetime: Cache<never>['lifetime'], fetcher: () => Promise<T>) { - this.lifetime = lifetime; - this.fetcher = fetcher; - } - - public set(value: T): void { - this.cachedAt = Date.now(); - this.value.value = value; - } - - private get(): T | undefined { - if (this.cachedAt == null) return undefined; - if ((Date.now() - this.cachedAt) > this.lifetime) { - this.value.value = undefined; - this.cachedAt = null; - return undefined; - } - return this.value.value; - } - - public delete() { - this.cachedAt = null; - } - - /** - * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します - */ - public async fetch(): Promise<T> { - const cachedValue = this.get(); - if (cachedValue !== undefined) { - // Cache HIT - return cachedValue; - } - - // Cache MISS - const value = await this.fetcher(); - this.set(value); - return value; - } -} diff --git a/packages/frontend/src/scripts/chart-legend.ts b/packages/frontend/src/scripts/chart-legend.ts deleted file mode 100644 index 2d534f60c1..0000000000 --- a/packages/frontend/src/scripts/chart-legend.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Plugin } from 'chart.js'; -import MkChartLegend from '@/components/MkChartLegend.vue'; - -export const chartLegend = (legend: InstanceType<typeof MkChartLegend>) => ({ - id: 'htmlLegend', - afterUpdate(chart, args, options) { - // Reuse the built-in legendItems generator - const items = chart.options.plugins.legend.labels.generateLabels(chart); - - legend.update(chart, items); - }, -}) as Plugin; diff --git a/packages/frontend/src/scripts/chart-vline.ts b/packages/frontend/src/scripts/chart-vline.ts deleted file mode 100644 index 24e41245e7..0000000000 --- a/packages/frontend/src/scripts/chart-vline.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Plugin } from 'chart.js'; - -export const chartVLine = (vLineColor: string) => ({ - id: 'vLine', - beforeDraw(chart, args, options) { - if (chart.tooltip?._active?.length) { - const ctx = chart.ctx; - const xs = chart.tooltip._active.map(a => a.element.x); - const x = xs.reduce((a, b) => a + b, 0) / xs.length; - const topY = chart.scales.y.top; - const bottomY = chart.scales.y.bottom; - - ctx.save(); - ctx.beginPath(); - ctx.moveTo(x, bottomY); - ctx.lineTo(x, topY); - ctx.lineWidth = 1; - ctx.strokeStyle = vLineColor; - ctx.stroke(); - ctx.restore(); - } - }, -}) as Plugin; diff --git a/packages/frontend/src/scripts/check-animated-mfm.ts b/packages/frontend/src/scripts/check-animated-mfm.ts deleted file mode 100644 index 2614dfb4f1..0000000000 --- a/packages/frontend/src/scripts/check-animated-mfm.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-FileCopyrightText: marie and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as mfm from '@transfem-org/sfm-js'; - -export function checkAnimationFromMfm(nodes: mfm.MfmNode[]): boolean { - const animatedNodes = mfm.extract(nodes, (node) => { - if (node.type === 'fn') { - if (node.props.name === 'tada' || - node.props.name === 'jelly' || - node.props.name === 'twitch' || - node.props.name === 'shake' || - node.props.name === 'spin' || - node.props.name === 'jump' || - node.props.name === 'bounce' || - node.props.name === 'rainbow' || - node.props.name === 'sparkle' || - node.props.name === 'fade' || - node.props.name === 'followmouse') { - return true; - } else { - return false; - } - } else { - return false; - } - }); - - if (animatedNodes.length > 0) { - return true; - } else { - return false; - } -} diff --git a/packages/frontend/src/scripts/check-permissions.ts b/packages/frontend/src/scripts/check-permissions.ts deleted file mode 100644 index ed86529d5b..0000000000 --- a/packages/frontend/src/scripts/check-permissions.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { instance } from '@/instance.js'; -import { $i } from '@/account.js'; - -export const notesSearchAvailable = ( - // FIXME: instance.policies would be null in Vitest - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - ($i == null && instance.policies != null && instance.policies.canSearchNotes) || - ($i != null && $i.policies.canSearchNotes) || - false -) as boolean; - -export const canSearchNonLocalNotes = ( - instance.noteSearchableScope === 'global' -); diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts deleted file mode 100644 index c3c3f419a9..0000000000 --- a/packages/frontend/src/scripts/check-reaction-permissions.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; -import { UnicodeEmojiDef } from '@@/js/emojilist.js'; - -export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef | string): boolean { - if (typeof emoji === 'string') return true; // UnicodeEmojiDefにも無い絵文字であれば文字列で来る。Unicode絵文字であることには変わりないので常にリアクション可能とする; - if ('char' in emoji) return true; // UnicodeEmojiDefなら常にリアクション可能 - - const roleIdsThatCanBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? []; - return !(emoji.localOnly && note.user.host !== me.host) - && !(emoji.isSensitive && (note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote')) - && (roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || me.roles.some(role => roleIdsThatCanBeUsedThisEmojiAsReaction.includes(role.id))); -} diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts deleted file mode 100644 index 194ef0f420..0000000000 --- a/packages/frontend/src/scripts/check-word-mute.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ -import * as Misskey from 'misskey-js'; - -export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): Array<string | string[]> | false { - // 自分自身 - if (me && (note.userId === me.id)) return false; - - if (mutedWords.length > 0) { - const text = getNoteText(note); - - if (text === '') return false; - - const matched = mutedWords.filter(filter => { - if (Array.isArray(filter)) { - // Clean up - const filteredFilter = filter.filter(keyword => keyword !== ''); - if (filteredFilter.length === 0) return false; - - return filteredFilter.every(keyword => text.includes(keyword)); - } else { - // represents RegExp - const regexp = filter.match(/^\/(.+)\/(.*)$/); - - // This should never happen due to input sanitisation. - if (!regexp) return false; - - try { - return new RegExp(regexp[1], regexp[2]).test(text); - } catch (err) { - // This should never happen due to input sanitisation. - return false; - } - } - }); - - if (matched.length > 0) return matched; - } - - return false; -} - -function getNoteText(note: Note): string { - const textParts: string[] = []; - - if (note.cw) textParts.push(note.cw); - - if (note.text) textParts.push(note.text); - - if (note.files) { - for (const file of note.files) { - if (file.comment) textParts.push(file.comment); - } - } - - if (note.poll) { - for (const choice of note.poll.choices) { - if (choice.text) textParts.push(choice.text); - } - } - - return textParts.join('\n').trim(); -} diff --git a/packages/frontend/src/scripts/chiptune2.ts b/packages/frontend/src/scripts/chiptune2.ts deleted file mode 100644 index 220002ff1e..0000000000 --- a/packages/frontend/src/scripts/chiptune2.ts +++ /dev/null @@ -1,347 +0,0 @@ -/* - * SPDX-FileCopyrightText: marie and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -/* eslint-disable */ - -const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext; - -let libopenmpt -let libopenmptLoadPromise - -type ChiptuneJsConfig = { - repeatCount: number | null; - context: AudioContext | null; -}; - -export function ChiptuneJsConfig (repeatCount?: number, context?: AudioContext) { - this.repeatCount = repeatCount; - this.context = context; -} - -ChiptuneJsConfig.prototype.constructor = ChiptuneJsConfig; - -export function ChiptuneJsPlayer (config: ChiptuneJsConfig) { - this.config = config; - this.audioContext = config.context || new ChiptuneAudioContext(); - this.context = this.audioContext.createGain(); - this.currentPlayingNode = null; - this.handlers = []; - this.touchLocked = true; - this.volume = 1; -} - -ChiptuneJsPlayer.prototype.initialize = function() { - if (libopenmptLoadPromise) return libopenmptLoadPromise; - if (libopenmpt) return Promise.resolve(); - - libopenmptLoadPromise = new Promise<void>(async (resolve, reject) => { - try { - const { Module } = await import('./libopenmpt/libopenmpt.js'); - await new Promise((resolve) => { - Module['onRuntimeInitialized'] = resolve; - }) - libopenmpt = Module; - resolve() - } catch (e) { - reject(e) - } finally { - libopenmptLoadPromise = undefined; - } - }) - - return libopenmptLoadPromise; -} - -ChiptuneJsPlayer.prototype.constructor = ChiptuneJsPlayer; - -ChiptuneJsPlayer.prototype.fireEvent = function (eventName: string, response) { - const handlers = this.handlers; - if (handlers.length > 0) { - for(const handler of handlers) { - if (handler.eventName === eventName) { - handler.handler(response); - } - } - } -}; - -ChiptuneJsPlayer.prototype.addHandler = function (eventName: string, handler: Function) { - this.handlers.push({ eventName, handler }); -}; - -ChiptuneJsPlayer.prototype.onEnded = function (handler: Function) { - this.addHandler('onEnded', handler); -}; - -ChiptuneJsPlayer.prototype.onError = function (handler: Function) { - this.addHandler('onError', handler); -}; - -ChiptuneJsPlayer.prototype.duration = function () { - return libopenmpt._openmpt_module_get_duration_seconds(this.currentPlayingNode.modulePtr); -}; - -ChiptuneJsPlayer.prototype.position = function () { - return libopenmpt._openmpt_module_get_position_seconds(this.currentPlayingNode.modulePtr); -}; - -ChiptuneJsPlayer.prototype.seek = function (position: number) { - if (this.currentPlayingNode) { - libopenmpt._openmpt_module_set_position_seconds(this.currentPlayingNode.modulePtr, position); - } -}; - -ChiptuneJsPlayer.prototype.metadata = function () { - const data = {}; - const keys = libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_metadata_keys(this.currentPlayingNode.modulePtr)).split(';'); - let keyNameBuffer = 0; - for (const key of keys) { - keyNameBuffer = libopenmpt._malloc(key.length + 1); - libopenmpt.writeAsciiToMemory(key, keyNameBuffer); - data[key] = libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_metadata(this.currentPlayingNode.modulePtr, keyNameBuffer)); - libopenmpt._free(keyNameBuffer); - } - return data; -}; - -ChiptuneJsPlayer.prototype.unlock = function () { - const context = this.audioContext; - const buffer = context.createBuffer(1, 1, 22050); - const unlockSource = context.createBufferSource(); - unlockSource.buffer = buffer; - unlockSource.connect(this.context); - this.context.connect(context.destination); - unlockSource.start(0); - this.touchLocked = false; -}; - -ChiptuneJsPlayer.prototype.load = function (input) { - return this.initialize().then(() => new Promise((resolve, reject) => { - if(this.touchLocked) { - this.unlock(); - } - const player = this; - if (input instanceof File) { - const reader = new FileReader(); - reader.onload = () => { - resolve(reader.result); - }; - reader.readAsArrayBuffer(input); - } else { - window.fetch(input).then((response) => { - response.arrayBuffer().then((arrayBuffer) => { - resolve(arrayBuffer); - }).catch((error) => { - reject(error); - }); - }).catch((error) => { - reject(error); - }); - } - })); -}; - -ChiptuneJsPlayer.prototype.play = function (buffer: ArrayBuffer) { - this.unlock(); - this.stop(); - const processNode = this.createLibopenmptNode(buffer, this.buffer); - if (processNode === null) { - return; - } - libopenmpt._openmpt_module_set_repeat_count(processNode.modulePtr, this.config.repeatCount || 0); - this.currentPlayingNode = processNode; - processNode.connect(this.context); - this.context.connect(this.audioContext.destination); -}; - -ChiptuneJsPlayer.prototype.stop = function () { - if (this.currentPlayingNode != null) { - this.currentPlayingNode.disconnect(); - this.currentPlayingNode.cleanup(); - this.currentPlayingNode = null; - } -}; - -ChiptuneJsPlayer.prototype.togglePause = function () { - if (this.currentPlayingNode != null) { - this.currentPlayingNode.togglePause(); - } -}; - -ChiptuneJsPlayer.prototype.getPattern = function () { - if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) { - return libopenmpt._openmpt_module_get_current_pattern(this.currentPlayingNode.modulePtr); - } - return 0; -}; - -ChiptuneJsPlayer.prototype.getRow = function () { - if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) { - return libopenmpt._openmpt_module_get_current_row(this.currentPlayingNode.modulePtr); - } - return 0; -}; - -ChiptuneJsPlayer.prototype.getNumPatterns = function () { - if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) { - return libopenmpt._openmpt_module_get_num_patterns(this.currentPlayingNode.modulePtr); - } - return 0; -}; - -ChiptuneJsPlayer.prototype.getCurrentSpeed = function () { - if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) { - return libopenmpt._openmpt_module_get_current_speed(this.currentPlayingNode.modulePtr); - } - return 0; -}; - -ChiptuneJsPlayer.prototype.getCurrentTempo = function () { - if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) { - return libopenmpt._openmpt_module_get_current_tempo(this.currentPlayingNode.modulePtr); - } - return 0; -}; - -ChiptuneJsPlayer.prototype.getPatternNumRows = function (pattern: number) { - if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) { - return libopenmpt._openmpt_module_get_pattern_num_rows(this.currentPlayingNode.modulePtr, pattern); - } - return 0; -}; - -ChiptuneJsPlayer.prototype.getPatternRowChannel = function (pattern: number, row: number, channel: number) { - if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) { - return libopenmpt.UTF8ToString(libopenmpt._openmpt_module_format_pattern_row_channel(this.currentPlayingNode.modulePtr, pattern, row, channel, 0, true)); - } - return ''; -}; - -ChiptuneJsPlayer.prototype.getCtls = function () { - if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) { - return libopenmpt._openmpt_module_get_ctls(this.currentPlayingNode.modulePtr); - } - return 0; -}; - -ChiptuneJsPlayer.prototype.version = function () { - if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) { - return libopenmpt._openmpt_get_library_version(); - } - return 0; -}; - -ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: object) { - const maxFramesPerChunk = 4096; - const processNode = this.audioContext.createScriptProcessor(2048, 0, 2); - processNode.config = config; - processNode.player = this; - const byteArray = new Int8Array(buffer); - const ptrToFile = libopenmpt._malloc(byteArray.byteLength); - libopenmpt.HEAPU8.set(byteArray, ptrToFile); - processNode.modulePtr = libopenmpt._openmpt_module_create_from_memory(ptrToFile, byteArray.byteLength, 0, 0, 0); - processNode.nbChannels = libopenmpt._openmpt_module_get_num_channels(processNode.modulePtr); - processNode.patternIndex = -1; - processNode.paused = false; - processNode.leftBufferPtr = libopenmpt._malloc(4 * maxFramesPerChunk); - processNode.rightBufferPtr = libopenmpt._malloc(4 * maxFramesPerChunk); - processNode.perf = { 'current': 0, 'max': 0 }; - processNode.cleanup = function () { - if (this.modulePtr !== 0) { - libopenmpt._openmpt_module_destroy(this.modulePtr); - this.modulePtr = 0; - } - if (this.leftBufferPtr !== 0) { - libopenmpt._free(this.leftBufferPtr); - this.leftBufferPtr = 0; - } - if (this.rightBufferPtr !== 0) { - libopenmpt._free(this.rightBufferPtr); - this.rightBufferPtr = 0; - } - }; - processNode.stop = function () { - this.disconnect(); - this.cleanup(); - }; - processNode.pause = function () { - this.paused = true; - }; - processNode.unpause = function () { - this.paused = false; - }; - processNode.togglePause = function () { - this.paused = !this.paused; - }; - processNode.getProcessTime = function() { - const max = this.perf.max; - this.perf.max = 0; - return { 'current': this.perf.current, 'max': max }; - }; - processNode.onaudioprocess = function (e) { - let startTimeP1 = performance.now(); - const outputL = e.outputBuffer.getChannelData(0); - const outputR = e.outputBuffer.getChannelData(1); - let framesToRender = outputL.length; - if (this.ModulePtr === 0) { - for (let i = 0; i < framesToRender; ++i) { - outputL[i] = 0; - outputR[i] = 0; - } - this.disconnect(); - this.cleanup(); - return; - } - if (this.paused) { - for (let i = 0; i < framesToRender; ++i) { - outputL[i] = 0; - outputR[i] = 0; - } - return; - } - let framesRendered = 0; - let ended = false; - let error = false; - - const currentPattern = libopenmpt._openmpt_module_get_current_pattern(this.modulePtr); - const currentRow = libopenmpt._openmpt_module_get_current_row(this.modulePtr); - startTimeP1 = startTimeP1 - performance.now(); - if (currentPattern !== this.patternIndex) { - processNode.player.fireEvent('onPatternChange'); - } - processNode.player.fireEvent('onRowChange', { index: currentRow }); - - const startTimeP2 = performance.now(); - while (framesToRender > 0) { - const framesPerChunk = Math.min(framesToRender, maxFramesPerChunk); - const actualFramesPerChunk = libopenmpt._openmpt_module_read_float_stereo(this.modulePtr, this.context.sampleRate, framesPerChunk, this.leftBufferPtr, this.rightBufferPtr); - if (actualFramesPerChunk === 0) { - ended = true; - // modulePtr will be 0 on openmpt: error: openmpt_module_read_float_stereo: ERROR: module * not valid or other openmpt error - error = !this.modulePtr; - } - const rawAudioLeft = libopenmpt.HEAPF32.subarray(this.leftBufferPtr / 4, this.leftBufferPtr / 4 + actualFramesPerChunk); - const rawAudioRight = libopenmpt.HEAPF32.subarray(this.rightBufferPtr / 4, this.rightBufferPtr / 4 + actualFramesPerChunk); - for (let i = 0; i < actualFramesPerChunk; ++i) { - outputL[framesRendered + i] = rawAudioLeft[i]; - outputR[framesRendered + i] = rawAudioRight[i]; - } - for (let i = actualFramesPerChunk; i < framesPerChunk; ++i) { - outputL[framesRendered + i] = 0; - outputR[framesRendered + i] = 0; - } - framesToRender -= framesPerChunk; - framesRendered += framesPerChunk; - } - if (ended) { - this.disconnect(); - this.cleanup(); - error ? processNode.player.fireEvent('onError', { type: 'openmpt' }) : processNode.player.fireEvent('onEnded'); - } - this.perf.current = performance.now() - startTimeP2 + startTimeP1; - if (this.perf.current > this.perf.max) this.perf.max = this.perf.current; - }; - return processNode; -}; diff --git a/packages/frontend/src/scripts/clear-cache.ts b/packages/frontend/src/scripts/clear-cache.ts deleted file mode 100644 index 71d1232710..0000000000 --- a/packages/frontend/src/scripts/clear-cache.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { unisonReload } from '@/scripts/unison-reload.js'; -import * as os from '@/os.js'; -import { miLocalStorage } from '@/local-storage.js'; -import { fetchCustomEmojis } from '@/custom-emojis.js'; -import { fetchInstance } from '@/instance.js'; - -export async function clearCache() { - os.waiting(); - miLocalStorage.removeItem('instance'); - miLocalStorage.removeItem('instanceCachedAt'); - miLocalStorage.removeItem('locale'); - miLocalStorage.removeItem('localeVersion'); - miLocalStorage.removeItem('theme'); - miLocalStorage.removeItem('emojis'); - miLocalStorage.removeItem('lastEmojisFetchedAt'); - await fetchInstance(true); - await fetchCustomEmojis(true); - unisonReload(); -} diff --git a/packages/frontend/src/scripts/clicker-game.ts b/packages/frontend/src/scripts/clicker-game.ts deleted file mode 100644 index f9c4bc1829..0000000000 --- a/packages/frontend/src/scripts/clicker-game.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { ref, computed } from 'vue'; -import { misskeyApi } from '@/scripts/misskey-api.js'; - -type SaveData = { - gameVersion: number; - cookies: number; - totalCookies: number; - totalHandmadeCookies: number; - clicked: number; - achievements: any[]; - facilities: any[]; -}; - -export const saveData = ref<SaveData>(); -export const ready = computed(() => saveData.value != null); - -let prev = ''; - -export async function load() { - try { - saveData.value = await misskeyApi('i/registry/get', { - scope: ['clickerGame'], - key: 'saveData', - }); - } catch (err) { - if (err.code === 'NO_SUCH_KEY') { - saveData.value = { - gameVersion: 2, - cookies: 0, - totalCookies: 0, - totalHandmadeCookies: 0, - clicked: 0, - achievements: [], - facilities: [], - }; - save(); - return; - } - throw err; - } - - // migration - if (saveData.value.gameVersion === 1) { - saveData.value = { - gameVersion: 2, - cookies: saveData.value.cookies, - totalCookies: saveData.value.cookies, - totalHandmadeCookies: saveData.value.cookies, - clicked: saveData.value.clicked, - achievements: [], - facilities: [], - }; - save(); - } -} - -export async function save() { - const current = JSON.stringify(saveData.value); - if (current === prev) return; - - await misskeyApi('i/registry/set', { - scope: ['clickerGame'], - key: 'saveData', - value: saveData.value, - }); - - prev = current; -} diff --git a/packages/frontend/src/scripts/clone.ts b/packages/frontend/src/scripts/clone.ts deleted file mode 100644 index ea8eea14b5..0000000000 --- a/packages/frontend/src/scripts/clone.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -// structredCloneが遅いため -// SEE: http://var.blog.jp/archives/86038606.html -// あと、Vue RefをIndexedDBに保存しようとしてstructredCloneを使ったらエラーになった -// https://github.com/misskey-dev/misskey/pull/8098#issuecomment-1114144045 - -export type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | { [key: number]: Cloneable } | { [key: symbol]: Cloneable } | Cloneable[]; - -export function deepClone<T extends Cloneable>(x: T): T { - if (typeof x === 'object') { - if (x === null) return x; - if (Array.isArray(x)) return x.map(deepClone) as T; - const obj = {} as Record<string | number | symbol, Cloneable>; - for (const [k, v] of Object.entries(x)) { - obj[k] = v === undefined ? undefined : deepClone(v); - } - return obj as T; - } else { - return x; - } -} diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts deleted file mode 100644 index 4d57dcd944..0000000000 --- a/packages/frontend/src/scripts/code-highlighter.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { createHighlighterCore } from 'shiki/core'; -import { createOnigurumaEngine } from 'shiki/engine/oniguruma'; -import darkPlus from 'shiki/themes/dark-plus.mjs'; -import { bundledThemesInfo } from 'shiki/themes'; -import { bundledLanguagesInfo } from 'shiki/langs'; -import lightTheme from '@@/themes/_light.json5'; -import darkTheme from '@@/themes/_dark.json5'; -import { unique } from './array.js'; -import { deepClone } from './clone.js'; -import { deepMerge } from './merge.js'; -import type { HighlighterCore, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki/core'; -import { ColdDeviceStorage } from '@/store.js'; - -let _highlighter: HighlighterCore | null = null; - -export async function getTheme(mode: 'light' | 'dark', getName: true): Promise<string>; -export async function getTheme(mode: 'light' | 'dark', getName?: false): Promise<ThemeRegistration | ThemeRegistrationRaw>; -export async function getTheme(mode: 'light' | 'dark', getName = false): Promise<ThemeRegistration | ThemeRegistrationRaw | string | null> { - const theme = deepClone(ColdDeviceStorage.get(mode === 'light' ? 'lightTheme' : 'darkTheme')); - - if (theme.base) { - const base = [lightTheme, darkTheme].find(x => x.id === theme.base); - if (base && base.codeHighlighter) theme.codeHighlighter = Object.assign({}, base.codeHighlighter, theme.codeHighlighter); - } - - if (theme.codeHighlighter) { - let _res: ThemeRegistration = {}; - if (theme.codeHighlighter.base === '_none_') { - _res = deepClone(theme.codeHighlighter.overrides); - } else { - const base = await bundledThemesInfo.find(t => t.id === theme.codeHighlighter!.base)?.import() ?? darkPlus; - _res = deepMerge(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base); - } - if (_res.name == null) { - _res.name = theme.id; - } - _res.type = mode; - - if (getName) { - return _res.name; - } - return _res; - } - - if (getName) { - return 'dark-plus'; - } - return darkPlus; -} - -export async function getHighlighter(): Promise<HighlighterCore> { - if (!_highlighter) { - return await initHighlighter(); - } - return _highlighter; -} - -async function initHighlighter() { - // テーマの重複を消す - const themes = unique([ - darkPlus, - ...(await Promise.all([getTheme('light'), getTheme('dark')])), - ]); - - const jsLangInfo = bundledLanguagesInfo.find(t => t.id === 'javascript'); - const highlighter = await createHighlighterCore({ - engine: createOnigurumaEngine(() => import('shiki/onig.wasm?init')), - themes, - langs: [ - ...(jsLangInfo ? [async () => await jsLangInfo.import()] : []), - async () => (await import('aiscript-vscode/aiscript/syntaxes/aiscript.tmLanguage.json')).default as unknown as LanguageRegistration, - ], - }); - - ColdDeviceStorage.watch('lightTheme', async () => { - const newTheme = await getTheme('light'); - if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) { - highlighter.loadTheme(newTheme); - } - }); - - ColdDeviceStorage.watch('darkTheme', async () => { - const newTheme = await getTheme('dark'); - if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) { - highlighter.loadTheme(newTheme); - } - }); - - _highlighter = highlighter; - - return highlighter; -} diff --git a/packages/frontend/src/scripts/collect-page-vars.ts b/packages/frontend/src/scripts/collect-page-vars.ts deleted file mode 100644 index 5096c0669e..0000000000 --- a/packages/frontend/src/scripts/collect-page-vars.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -interface StringPageVar { - name: string, - type: 'string', - value: string -} - -interface NumberPageVar { - name: string, - type: 'number', - value: number -} - -interface BooleanPageVar { - name: string, - type: 'boolean', - value: boolean -} - -type PageVar = StringPageVar | NumberPageVar | BooleanPageVar; - -export function collectPageVars(content): PageVar[] { - const pageVars: PageVar[] = []; - const collect = (xs: any[]): void => { - for (const x of xs) { - if (x.type === 'textInput') { - pageVars.push({ - name: x.name, - type: 'string', - value: x.default || '', - }); - } else if (x.type === 'textareaInput') { - pageVars.push({ - name: x.name, - type: 'string', - value: x.default || '', - }); - } else if (x.type === 'numberInput') { - pageVars.push({ - name: x.name, - type: 'number', - value: x.default || 0, - }); - } else if (x.type === 'switch') { - pageVars.push({ - name: x.name, - type: 'boolean', - value: x.default || false, - }); - } else if (x.type === 'counter') { - pageVars.push({ - name: x.name, - type: 'number', - value: 0, - }); - } else if (x.type === 'radioButton') { - pageVars.push({ - name: x.name, - type: 'string', - value: x.default || '', - }); - } else if (x.children) { - collect(x.children); - } - } - }; - collect(content); - return pageVars; -} diff --git a/packages/frontend/src/scripts/color.ts b/packages/frontend/src/scripts/color.ts deleted file mode 100644 index a11255ffd1..0000000000 --- a/packages/frontend/src/scripts/color.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export const alpha = (hex: string, a: number): string => { - const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!; - const r = parseInt(result[1], 16); - const g = parseInt(result[2], 16); - const b = parseInt(result[3], 16); - return `rgba(${r}, ${g}, ${b}, ${a})`; -}; diff --git a/packages/frontend/src/scripts/confetti.ts b/packages/frontend/src/scripts/confetti.ts deleted file mode 100644 index 8e53a6ceeb..0000000000 --- a/packages/frontend/src/scripts/confetti.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import _confetti from 'canvas-confetti'; -import * as os from '@/os.js'; - -export function confetti(options: { duration?: number; } = {}) { - const duration = options.duration ?? 1000 * 4; - const animationEnd = Date.now() + duration; - const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: os.claimZIndex('high') }; - - function randomInRange(min, max) { - return Math.random() * (max - min) + min; - } - - const interval = setInterval(() => { - const timeLeft = animationEnd - Date.now(); - - if (timeLeft <= 0) { - return clearInterval(interval); - } - - const particleCount = 50 * (timeLeft / duration); - // since particles fall down, start a bit higher than random - _confetti(Object.assign({}, defaults, { particleCount, origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 } })); - _confetti(Object.assign({}, defaults, { particleCount, origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 } })); - }, 250); -} diff --git a/packages/frontend/src/scripts/contains.ts b/packages/frontend/src/scripts/contains.ts deleted file mode 100644 index 6137c06e85..0000000000 --- a/packages/frontend/src/scripts/contains.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export default (parent, child, checkSame = true) => { - if (checkSame && parent === child) return true; - let node = child.parentNode; - while (node) { - if (node === parent) return true; - node = node.parentNode; - } - return false; -}; diff --git a/packages/frontend/src/scripts/copy-to-clipboard.ts b/packages/frontend/src/scripts/copy-to-clipboard.ts deleted file mode 100644 index 08f5b52dae..0000000000 --- a/packages/frontend/src/scripts/copy-to-clipboard.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -/** - * Clipboardに値をコピー(TODO: 文字列以外も対応) - */ -export function copyToClipboard(input: string | null) { - if (input) navigator.clipboard.writeText(input); -} diff --git a/packages/frontend/src/scripts/device-kind.ts b/packages/frontend/src/scripts/device-kind.ts deleted file mode 100644 index 7aadb617ca..0000000000 --- a/packages/frontend/src/scripts/device-kind.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export type DeviceKind = 'smartphone' | 'tablet' | 'desktop'; - -const ua = navigator.userAgent.toLowerCase(); -const isTablet = /ipad/.test(ua) || (/mobile|iphone|android/.test(ua) && window.innerWidth > 700); -const isSmartphone = !isTablet && /mobile|iphone|android/.test(ua); - -export const DEFAULT_DEVICE_KIND: DeviceKind = ( - isSmartphone - ? 'smartphone' - : isTablet - ? 'tablet' - : 'desktop' -); - -export let deviceKind: DeviceKind = DEFAULT_DEVICE_KIND; - -export function updateDeviceKind(kind: DeviceKind | null) { - deviceKind = kind ?? DEFAULT_DEVICE_KIND; -} diff --git a/packages/frontend/src/scripts/emoji-picker.ts b/packages/frontend/src/scripts/emoji-picker.ts deleted file mode 100644 index 14b5cbf35e..0000000000 --- a/packages/frontend/src/scripts/emoji-picker.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { defineAsyncComponent, Ref, ref } from 'vue'; -import { popup } from '@/os.js'; -import { defaultStore } from '@/store.js'; - -/** - * 絵文字ピッカーを表示する。 - * 類似の機能として{@link ReactionPicker}が存在しているが、この機能とは動きが異なる。 - * 投稿フォームなどで絵文字を選択する時など、絵文字ピックアップ後でもダイアログが消えずに残り、 - * 一度表示したダイアログを連続で使用できることが望ましいシーンでの利用が想定される。 - */ -class EmojiPicker { - private src: Ref<HTMLElement | null> = ref(null); - private manualShowing = ref(false); - private onChosen?: (emoji: string) => void; - private onClosed?: () => void; - - constructor() { - // nop - } - - public async init() { - const emojisRef = defaultStore.reactiveState.pinnedEmojis; - await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { - src: this.src, - pinnedEmojis: emojisRef, - asReactionPicker: false, - manualShowing: this.manualShowing, - choseAndClose: false, - }, { - done: emoji => { - if (this.onChosen) this.onChosen(emoji); - }, - close: () => { - this.manualShowing.value = false; - }, - closed: () => { - this.src.value = null; - if (this.onClosed) this.onClosed(); - }, - }); - } - - public show( - src: HTMLElement, - onChosen?: EmojiPicker['onChosen'], - onClosed?: EmojiPicker['onClosed'], - ) { - this.src.value = src; - this.manualShowing.value = true; - this.onChosen = onChosen; - this.onClosed = onClosed; - } -} - -export const emojiPicker = new EmojiPicker(); diff --git a/packages/frontend/src/scripts/extract-mentions.ts b/packages/frontend/src/scripts/extract-mentions.ts deleted file mode 100644 index 89a5ce1df8..0000000000 --- a/packages/frontend/src/scripts/extract-mentions.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -// test is located in test/extract-mentions - -import * as mfm from '@transfem-org/sfm-js'; - -export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] { - // TODO: 重複を削除 - const mentionNodes = mfm.extract(nodes, (node) => node.type === 'mention'); - const mentions = mentionNodes.map(x => x.props); - - return mentions; -} diff --git a/packages/frontend/src/scripts/extract-url-from-mfm.ts b/packages/frontend/src/scripts/extract-url-from-mfm.ts deleted file mode 100644 index a4c84aa740..0000000000 --- a/packages/frontend/src/scripts/extract-url-from-mfm.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as mfm from '@transfem-org/sfm-js'; -import { unique } from '@/scripts/array.js'; - -// unique without hash -// [ http://a/#1, http://a/#2, http://b/#3 ] => [ http://a/#1, http://b/#3 ] -const removeHash = (x: string) => x.replace(/#[^#]*$/, ''); - -export function extractUrlFromMfm(nodes: mfm.MfmNode[], respectSilentFlag = true): string[] { - const urlNodes = mfm.extract(nodes, (node) => { - return (node.type === 'url') || (node.type === 'link' && (!respectSilentFlag || !node.props.silent)); - }); - const urls: string[] = unique(urlNodes.map(x => x.props.url)); - - return urls.reduce((array, url) => { - const urlWithoutHash = removeHash(url); - if (!array.map(x => removeHash(x)).includes(urlWithoutHash)) array.push(url); - return array; - }, [] as string[]); -} diff --git a/packages/frontend/src/scripts/favicon-dot.ts b/packages/frontend/src/scripts/favicon-dot.ts deleted file mode 100644 index a903b4cc7f..0000000000 --- a/packages/frontend/src/scripts/favicon-dot.ts +++ /dev/null @@ -1,156 +0,0 @@ -/* - * SPDX-FileCopyrightText: leah and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import tinycolor from 'tinycolor2'; - -class FavIconDot { - private readonly canvas: HTMLCanvasElement; - private src: string | null = null; - private ctx: CanvasRenderingContext2D | null = null; - private faviconImage: HTMLImageElement | null = null; - private faviconEL: HTMLLinkElement | undefined; - private hasLoaded: Promise<void> | undefined; - - constructor() { - this.canvas = document.createElement('canvas'); - } - - /** - * Must be called before calling any other functions - */ - public async setup() { - const element: HTMLLinkElement = await this.getOrMakeFaviconElement(); - - this.faviconEL = element; - this.src = this.faviconEL.getAttribute('href'); - this.ctx = this.canvas.getContext('2d'); - - this.faviconImage = document.createElement('img'); - this.faviconImage.crossOrigin = 'anonymous'; - - this.hasLoaded = new Promise((resolve, reject) => { - (this.faviconImage as HTMLImageElement).addEventListener('load', () => { - this.canvas.width = (this.faviconImage as HTMLImageElement).width; - this.canvas.height = (this.faviconImage as HTMLImageElement).height; - resolve(); - }); - (this.faviconImage as HTMLImageElement).addEventListener('error', () => { - reject('Failed to create favicon img element'); - }); - }); - - this.faviconImage.src = this.faviconEL.href; - } - - private async getOrMakeFaviconElement(): Promise<HTMLLinkElement> { - return new Promise((resolve, reject) => { - const favicon = (document.querySelector('link[rel=icon]') ?? this.createFaviconElem()) as HTMLLinkElement; - favicon.addEventListener('load', () => { - resolve(favicon); - }); - - favicon.onerror = () => { - reject('Failed to load favicon'); - }; - resolve(favicon); - }); - } - - private createFaviconElem() { - const newLink = document.createElement('link'); - newLink.setAttribute('rel', 'icon'); - newLink.setAttribute('href', '/favicon.ico'); - newLink.setAttribute('type', 'image/x-icon'); - - document.head.appendChild(newLink); - return newLink; - } - - private drawIcon() { - if (!this.ctx || !this.faviconImage) return; - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.ctx.drawImage(this.faviconImage, 0, 0, this.faviconImage.width, this.faviconImage.height); - } - - private drawDot() { - if (!this.ctx || !this.faviconImage) return; - this.ctx.beginPath(); - const radius = Math.min(this.faviconImage.width, this.faviconImage.height) * 0.2; - this.ctx.arc(this.faviconImage.width - radius, radius, radius, 0, 2 * Math.PI); - const computedStyle = getComputedStyle(document.documentElement); - this.ctx.fillStyle = tinycolor(computedStyle.getPropertyValue('--MI_THEME-navIndicator')).toHexString(); - this.ctx.strokeStyle = 'white'; - this.ctx.fill(); - this.ctx.stroke(); - } - - private setFavicon() { - if (this.faviconEL) { - try { - URL.revokeObjectURL(this.faviconEL.href); - } catch { - // the href was probably not an object URL - } - this.canvas.toBlob((blob) => { - const url = URL.createObjectURL(blob); - this.faviconEL.href = url; - }); - } - } - - public async setVisible(isVisible: boolean) { - // Wait for it to have loaded the icon - await this.hasLoaded; - this.drawIcon(); - if (isVisible) this.drawDot(); - this.setFavicon(); - } - - public async worksOnInstance() { - try { - await this.setVisible(true); - await new Promise((resolve) => setTimeout(resolve, 1000)); - await this.setVisible(false); - } catch (error) { - console.error('error setting notification dot', error); - return false; - } - return true; - } -} - -let icon: FavIconDot | undefined = undefined; - -export async function setFavIconDot(visible: boolean) { - const setIconVisibility = async () => { - if (!icon) { - icon = new FavIconDot(); - await icon.setup(); - } - - try { - (icon as FavIconDot).setVisible(visible); - } catch (error) { - console.error('error setting notification dot', error); - } - }; - - // If document is already loaded, set visibility immediately - if (document.readyState === 'complete') { - await setIconVisibility(); - } else { - // Otherwise, set visibility when window loads - window.addEventListener('load', setIconVisibility); - } -} - -export async function worksOnInstance() { - if (!icon) { - icon = new FavIconDot(); - await icon.setup(); - } - - return await icon.worksOnInstance(); -} diff --git a/packages/frontend/src/scripts/file-drop.ts b/packages/frontend/src/scripts/file-drop.ts deleted file mode 100644 index c2e863c0dc..0000000000 --- a/packages/frontend/src/scripts/file-drop.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export type DroppedItem = DroppedFile | DroppedDirectory; - -export type DroppedFile = { - isFile: true; - path: string; - file: File; -}; - -export type DroppedDirectory = { - isFile: false; - path: string; - children: DroppedItem[]; -} - -export async function extractDroppedItems(ev: DragEvent): Promise<DroppedItem[]> { - const dropItems = ev.dataTransfer?.items; - if (!dropItems || dropItems.length === 0) { - return []; - } - - const apiTestItem = dropItems[0]; - if ('webkitGetAsEntry' in apiTestItem) { - return readDataTransferItems(dropItems); - } else { - // webkitGetAsEntryに対応していない場合はfilesから取得する(ディレクトリのサポートは出来ない) - const dropFiles = ev.dataTransfer.files; - if (dropFiles.length === 0) { - return []; - } - - const droppedFiles = Array.of<DroppedFile>(); - for (let i = 0; i < dropFiles.length; i++) { - const file = dropFiles.item(i); - if (file) { - droppedFiles.push({ - isFile: true, - path: file.name, - file, - }); - } - } - - return droppedFiles; - } -} - -/** - * ドラッグ&ドロップされたファイルのリストからディレクトリ構造とファイルへの参照({@link File})を取得する。 - */ -export async function readDataTransferItems(itemList: DataTransferItemList): Promise<DroppedItem[]> { - async function readEntry(entry: FileSystemEntry): Promise<DroppedItem> { - if (entry.isFile) { - return { - isFile: true, - path: entry.fullPath, - file: await readFile(entry as FileSystemFileEntry), - }; - } else { - return { - isFile: false, - path: entry.fullPath, - children: await readDirectory(entry as FileSystemDirectoryEntry), - }; - } - } - - function readFile(fileSystemFileEntry: FileSystemFileEntry): Promise<File> { - return new Promise((resolve, reject) => { - fileSystemFileEntry.file(resolve, reject); - }); - } - - function readDirectory(fileSystemDirectoryEntry: FileSystemDirectoryEntry): Promise<DroppedItem[]> { - return new Promise(async (resolve) => { - const allEntries = Array.of<FileSystemEntry>(); - const reader = fileSystemDirectoryEntry.createReader(); - while (true) { - const entries = await new Promise<FileSystemEntry[]>((res, rej) => reader.readEntries(res, rej)); - if (entries.length === 0) { - break; - } - allEntries.push(...entries); - } - - resolve(await Promise.all(allEntries.map(readEntry))); - }); - } - - // 扱いにくいので配列に変換 - const items = Array.of<DataTransferItem>(); - for (let i = 0; i < itemList.length; i++) { - items.push(itemList[i]); - } - - return Promise.all( - items - .map(it => it.webkitGetAsEntry()) - .filter(it => it) - .map(it => readEntry(it!)), - ); -} - -/** - * {@link DroppedItem}のリストからディレクトリを再帰的に検索し、ファイルのリストを取得する。 - */ -export function flattenDroppedFiles(items: DroppedItem[]): DroppedFile[] { - const result = Array.of<DroppedFile>(); - for (const item of items) { - if (item.isFile) { - result.push(item); - } else { - result.push(...flattenDroppedFiles(item.children)); - } - } - return result; -} diff --git a/packages/frontend/src/scripts/focus-trap.ts b/packages/frontend/src/scripts/focus-trap.ts deleted file mode 100644 index fb7caea830..0000000000 --- a/packages/frontend/src/scripts/focus-trap.ts +++ /dev/null @@ -1,134 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js'; - -const focusTrapElements = new Set<HTMLElement>(); -const ignoreElements = [ - 'script', - 'style', -]; - -function containsFocusTrappedElements(el: HTMLElement): boolean { - return Array.from(focusTrapElements).some((focusTrapElement) => { - return el.contains(focusTrapElement); - }); -} - -function getZIndex(el: HTMLElement): number { - const zIndex = parseInt(window.getComputedStyle(el).zIndex || '0', 10); - if (isNaN(zIndex)) { - return 0; - } - return zIndex; -} - -function getHighestZIndexElement(): { el: HTMLElement; zIndex: number; } | null { - let highestZIndexElement: HTMLElement | null = null; - let highestZIndex = -Infinity; - - focusTrapElements.forEach((el) => { - const zIndex = getZIndex(el); - if (zIndex > highestZIndex) { - highestZIndex = zIndex; - highestZIndexElement = el; - } - }); - - return highestZIndexElement == null ? null : { - el: highestZIndexElement, - zIndex: highestZIndex, - }; -} - -function releaseFocusTrap(el: HTMLElement): void { - focusTrapElements.delete(el); - if (el.inert === true) { - el.inert = false; - } - - const highestZIndexElement = getHighestZIndexElement(); - - if (el.parentElement != null && el !== document.body) { - el.parentElement.childNodes.forEach((siblingNode) => { - const siblingEl = getHTMLElementOrNull(siblingNode); - if (!siblingEl) return; - if ( - siblingEl !== el && - ( - highestZIndexElement == null || - siblingEl === highestZIndexElement.el || - siblingEl.contains(highestZIndexElement.el) - ) - ) { - siblingEl.inert = false; - } else if ( - highestZIndexElement != null && - siblingEl !== highestZIndexElement.el && - !siblingEl.contains(highestZIndexElement.el) && - !ignoreElements.includes(siblingEl.tagName.toLowerCase()) - ) { - siblingEl.inert = true; - } else { - siblingEl.inert = false; - } - }); - releaseFocusTrap(el.parentElement); - } -} - -export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEls: boolean, parent: true): void; -export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEls?: boolean, parent?: false): { release: () => void; }; -export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEls = false, parent = false): { release: () => void; } | void { - const highestZIndexElement = getHighestZIndexElement(); - - const highestZIndex = highestZIndexElement == null ? -Infinity : highestZIndexElement.zIndex; - const zIndex = getZIndex(el); - - // If the element has a lower z-index than the highest z-index element, focus trap the highest z-index element instead - // Focus trapping for this element will be done in the release function - if (!parent && zIndex < highestZIndex) { - focusTrapElements.add(el); - if (highestZIndexElement) { - focusTrap(highestZIndexElement.el, hasInteractionWithOtherFocusTrappedEls); - } - return { - release: () => { - releaseFocusTrap(el); - }, - }; - } - - if (el.inert === true) { - el.inert = false; - } - - if (el.parentElement != null && el !== document.body) { - el.parentElement.childNodes.forEach((siblingNode) => { - const siblingEl = getHTMLElementOrNull(siblingNode); - if (!siblingEl) return; - if ( - siblingEl !== el && - ( - hasInteractionWithOtherFocusTrappedEls === false || - (!focusTrapElements.has(siblingEl) && !containsFocusTrappedElements(siblingEl)) - ) && - !ignoreElements.includes(siblingEl.tagName.toLowerCase()) - ) { - siblingEl.inert = true; - } - }); - focusTrap(el.parentElement, hasInteractionWithOtherFocusTrappedEls, true); - } - - if (!parent) { - focusTrapElements.add(el); - - return { - release: () => { - releaseFocusTrap(el); - }, - }; - } -} diff --git a/packages/frontend/src/scripts/focus.ts b/packages/frontend/src/scripts/focus.ts deleted file mode 100644 index 81278b17ea..0000000000 --- a/packages/frontend/src/scripts/focus.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { getScrollPosition, getScrollContainer, getStickyBottom, getStickyTop } from '@@/js/scroll.js'; -import { getElementOrNull, getNodeOrNull } from '@/scripts/get-dom-node-or-null.js'; - -type MaybeHTMLElement = EventTarget | Node | Element | HTMLElement; - -export const isFocusable = (input: MaybeHTMLElement | null | undefined): input is HTMLElement => { - if (input == null || !(input instanceof HTMLElement)) return false; - - if (input.tabIndex < 0) return false; - if ('disabled' in input && input.disabled === true) return false; - if ('readonly' in input && input.readonly === true) return false; - - if (!input.ownerDocument.contains(input)) return false; - - const style = window.getComputedStyle(input); - if (style.display === 'none') return false; - if (style.visibility === 'hidden') return false; - if (style.opacity === '0') return false; - if (style.pointerEvents === 'none') return false; - - return true; -}; - -export const focusPrev = (input: MaybeHTMLElement | null | undefined, self = false, scroll = true) => { - const element = self ? input : getElementOrNull(input)?.previousElementSibling; - if (element == null) return; - if (isFocusable(element)) { - focusOrScroll(element, scroll); - } else { - focusPrev(element, false, scroll); - } -}; - -export const focusNext = (input: MaybeHTMLElement | null | undefined, self = false, scroll = true) => { - const element = self ? input : getElementOrNull(input)?.nextElementSibling; - if (element == null) return; - if (isFocusable(element)) { - focusOrScroll(element, scroll); - } else { - focusNext(element, false, scroll); - } -}; - -export const focusParent = (input: MaybeHTMLElement | null | undefined, self = false, scroll = true) => { - const element = self ? input : getNodeOrNull(input)?.parentElement; - if (element == null) return; - if (isFocusable(element)) { - focusOrScroll(element, scroll); - } else { - focusParent(element, false, scroll); - } -}; - -const focusOrScroll = (element: HTMLElement, scroll: boolean) => { - if (scroll) { - const scrollContainer = getScrollContainer(element) ?? document.documentElement; - const scrollContainerTop = getScrollPosition(scrollContainer); - const stickyTop = getStickyTop(element, scrollContainer); - const stickyBottom = getStickyBottom(element, scrollContainer); - const top = element.getBoundingClientRect().top; - const bottom = element.getBoundingClientRect().bottom; - - let scrollTo = scrollContainerTop; - if (top < stickyTop) { - scrollTo += top - stickyTop; - } else if (bottom > window.innerHeight - stickyBottom) { - scrollTo += bottom - window.innerHeight + stickyBottom; - } - scrollContainer.scrollTo({ top: scrollTo, behavior: 'instant' }); - } - - if (document.activeElement !== element) { - element.focus({ preventScroll: true }); - } -}; diff --git a/packages/frontend/src/scripts/following-feed-utils.ts b/packages/frontend/src/scripts/following-feed-utils.ts deleted file mode 100644 index 39f17949d6..0000000000 --- a/packages/frontend/src/scripts/following-feed-utils.ts +++ /dev/null @@ -1,181 +0,0 @@ -/* - * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { computed, Ref, WritableComputedRef } from 'vue'; -import { defaultStore } from '@/store.js'; -import { deepMerge } from '@/scripts/merge.js'; -import { PageHeaderItem } from '@/types/page-header.js'; -import { i18n } from '@/i18n.js'; -import { popupMenu } from '@/os.js'; -import { MenuItem } from '@/types/menu.js'; - -export const followingTab = 'following' as const; -export const mutualsTab = 'mutuals' as const; -export const followersTab = 'followers' as const; -export const followingFeedTabs = [followingTab, mutualsTab, followersTab] as const; -export type FollowingFeedTab = typeof followingFeedTabs[number]; - -export function followingTabName(tab: FollowingFeedTab): string; -export function followingTabName(tab: FollowingFeedTab | null | undefined): null; -export function followingTabName(tab: FollowingFeedTab | null | undefined): string | null { - if (tab === followingTab) return i18n.ts.following; - if (tab === followersTab) return i18n.ts.followers; - if (tab === mutualsTab) return i18n.ts.mutuals; - return null; -} - -export function followingTabIcon(tab: FollowingFeedTab | null | undefined): string { - if (tab === followersTab) return 'ph-user ph-bold ph-lg'; - if (tab === mutualsTab) return 'ph-user-switch ph-bold ph-lg'; - return 'ph-user-check ph-bold ph-lg'; -} - -export type FollowingFeedModel = { - [Key in keyof FollowingFeedState]: WritableComputedRef<FollowingFeedState[Key]>; -} - -export interface FollowingFeedState { - withNonPublic: boolean, - withQuotes: boolean, - withBots: boolean, - withReplies: boolean, - onlyFiles: boolean, - userList: FollowingFeedTab, - remoteWarningDismissed: boolean, -} - -export const defaultFollowingFeedState: FollowingFeedState = { - withNonPublic: false, - withQuotes: false, - withBots: true, - withReplies: false, - onlyFiles: false, - userList: followingTab, - remoteWarningDismissed: false, -}; - -interface StorageInterface<T extends Partial<FollowingFeedState> = Partial<FollowingFeedState>> { - readonly state: Partial<T>; - readonly reactiveState: Ref<Partial<T>>; - save(updated: T): void; -} - -export function createHeaderItem(storage?: Ref<StorageInterface>): PageHeaderItem { - const menu = createOptionsMenu(storage); - return { - icon: 'ti ti-dots', - text: i18n.ts.options, - handler: ev => popupMenu(menu, ev.currentTarget ?? ev.target), - }; -} - -export function createOptionsMenu(storage?: Ref<StorageInterface>): MenuItem[] { - const { - userList, - withNonPublic, - withQuotes, - withBots, - withReplies, - onlyFiles, - } = createModel(storage); - - return [ - { - type: 'switch', - text: i18n.ts.showNonPublicNotes, - ref: withNonPublic, - disabled: computed(() => userList.value === followersTab), - }, - { - type: 'switch', - text: i18n.ts.showQuotes, - ref: withQuotes, - }, - { - type: 'switch', - text: i18n.ts.showBots, - ref: withBots, - }, - { - type: 'switch', - text: i18n.ts.showReplies, - ref: withReplies, - disabled: onlyFiles, - }, - { - type: 'divider', - }, - { - type: 'switch', - text: i18n.ts.fileAttachedOnly, - ref: onlyFiles, - disabled: withReplies, - }, - ]; -} - -export function createModel(storage?: Ref<StorageInterface>): FollowingFeedModel { - // eslint-disable-next-line no-param-reassign - storage ??= createDefaultStorage(); - - // Based on timeline.saveTlFilter() - const saveFollowingFilter = <K extends keyof FollowingFeedState>(key: K, value: FollowingFeedState[K]) => { - const state = deepMerge(storage.value.state, defaultFollowingFeedState); - const out = deepMerge({ [key]: value }, state); - storage.value.save(out); - }; - - const userList: WritableComputedRef<FollowingFeedTab> = computed({ - get: () => storage.value.reactiveState.value.userList ?? defaultFollowingFeedState.userList, - set: value => saveFollowingFilter('userList', value), - }); - const withNonPublic: WritableComputedRef<boolean> = computed({ - get: () => { - if (userList.value === 'followers') return false; - return storage.value.reactiveState.value.withNonPublic ?? defaultFollowingFeedState.withNonPublic; - }, - set: value => saveFollowingFilter('withNonPublic', value), - }); - const withQuotes: WritableComputedRef<boolean> = computed({ - get: () => storage.value.reactiveState.value.withQuotes ?? defaultFollowingFeedState.withQuotes, - set: value => saveFollowingFilter('withQuotes', value), - }); - const withBots: WritableComputedRef<boolean> = computed({ - get: () => storage.value.reactiveState.value.withBots ?? defaultFollowingFeedState.withBots, - set: value => saveFollowingFilter('withBots', value), - }); - const withReplies: WritableComputedRef<boolean> = computed({ - get: () => storage.value.reactiveState.value.withReplies ?? defaultFollowingFeedState.withReplies, - set: value => saveFollowingFilter('withReplies', value), - }); - const onlyFiles: WritableComputedRef<boolean> = computed({ - get: () => storage.value.reactiveState.value.onlyFiles ?? defaultFollowingFeedState.onlyFiles, - set: value => saveFollowingFilter('onlyFiles', value), - }); - const remoteWarningDismissed: WritableComputedRef<boolean> = computed({ - get: () => storage.value.reactiveState.value.remoteWarningDismissed ?? defaultFollowingFeedState.remoteWarningDismissed, - set: value => saveFollowingFilter('remoteWarningDismissed', value), - }); - - return { - userList, - withNonPublic, - withQuotes, - withBots, - withReplies, - onlyFiles, - remoteWarningDismissed, - }; -} - -function createDefaultStorage() { - return computed(() => ({ - state: defaultStore.state.followingFeed, - reactiveState: defaultStore.reactiveState.followingFeed, - save(updated: typeof defaultStore.state.followingFeed) { - return defaultStore.set('followingFeed', updated); - }, - })); -} diff --git a/packages/frontend/src/scripts/form.ts b/packages/frontend/src/scripts/form.ts deleted file mode 100644 index 1032e97ac9..0000000000 --- a/packages/frontend/src/scripts/form.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; - -type EnumItem = string | { - label: string; - value: string; -}; - -type Hidden = boolean | ((v: any) => boolean); - -export type FormItem = { - label?: string; - type: 'string'; - default?: string | null; - description?: string; - required?: boolean; - hidden?: Hidden; - multiline?: boolean; - treatAsMfm?: boolean; -} | { - label?: string; - type: 'number'; - default?: number | null; - description?: string; - required?: boolean; - hidden?: Hidden; - step?: number; -} | { - label?: string; - type: 'boolean'; - default?: boolean | null; - description?: string; - hidden?: Hidden; -} | { - label?: string; - type: 'enum'; - default?: string | null; - required?: boolean; - hidden?: Hidden; - enum: EnumItem[]; -} | { - label?: string; - type: 'radio'; - default?: unknown | null; - required?: boolean; - hidden?: Hidden; - options: { - label: string; - value: unknown; - }[]; -} | { - label?: string; - type: 'range'; - default?: number | null; - description?: string; - required?: boolean; - step?: number; - min: number; - max: number; - textConverter?: (value: number) => string; - hidden?: Hidden; -} | { - label?: string; - type: 'object'; - default?: Record<string, unknown> | null; - hidden: Hidden; -} | { - label?: string; - type: 'array'; - default?: unknown[] | null; - hidden: Hidden; -} | { - type: 'button'; - content?: string; - hidden?: Hidden; - action: (ev: MouseEvent, v: any) => void; -} | { - type: 'drive-file'; - defaultFileId?: string | null; - hidden?: Hidden; - validate?: (v: Misskey.entities.DriveFile) => Promise<boolean>; -}; - -export type Form = Record<string, FormItem>; - -type GetItemType<Item extends FormItem> = - Item['type'] extends 'string' ? string : - Item['type'] extends 'number' ? number : - Item['type'] extends 'boolean' ? boolean : - Item['type'] extends 'radio' ? unknown : - Item['type'] extends 'range' ? number : - Item['type'] extends 'enum' ? string : - Item['type'] extends 'array' ? unknown[] : - Item['type'] extends 'object' ? Record<string, unknown> : - Item['type'] extends 'drive-file' ? Misskey.entities.DriveFile | undefined : - never; - -export type GetFormResultType<F extends Form> = { - [P in keyof F]: GetItemType<F[P]>; -}; diff --git a/packages/frontend/src/scripts/format-time-string.ts b/packages/frontend/src/scripts/format-time-string.ts deleted file mode 100644 index 35ad77d982..0000000000 --- a/packages/frontend/src/scripts/format-time-string.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -const defaultLocaleStringFormats: {[index: string]: string} = { - 'weekday': 'narrow', - 'era': 'narrow', - 'year': 'numeric', - 'month': 'numeric', - 'day': 'numeric', - 'hour': 'numeric', - 'minute': 'numeric', - 'second': 'numeric', - 'timeZoneName': 'short', -}; - -function formatLocaleString(date: Date, format: string): string { - return format.replace(/\{\{(\w+)(:(\w+))?\}\}/g, (match: string, kind: string, unused?, option?: string) => { - if (['weekday', 'era', 'year', 'month', 'day', 'hour', 'minute', 'second', 'timeZoneName'].includes(kind)) { - return date.toLocaleString(window.navigator.language, { [kind]: option ? option : defaultLocaleStringFormats[kind] }); - } else { - return match; - } - }); -} - -export function formatDateTimeString(date: Date, format: string): string { - return format - .replace(/yyyy/g, date.getFullYear().toString()) - .replace(/yy/g, date.getFullYear().toString().slice(-2)) - .replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long' })) - .replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short' })) - .replace(/MM/g, (`0${date.getMonth() + 1}`).slice(-2)) - .replace(/M/g, (date.getMonth() + 1).toString()) - .replace(/dd/g, (`0${date.getDate()}`).slice(-2)) - .replace(/d/g, date.getDate().toString()) - .replace(/HH/g, (`0${date.getHours()}`).slice(-2)) - .replace(/H/g, date.getHours().toString()) - .replace(/hh/g, (`0${(date.getHours() % 12) || 12}`).slice(-2)) - .replace(/h/g, ((date.getHours() % 12) || 12).toString()) - .replace(/mm/g, (`0${date.getMinutes()}`).slice(-2)) - .replace(/m/g, date.getMinutes().toString()) - .replace(/ss/g, (`0${date.getSeconds()}`).slice(-2)) - .replace(/s/g, date.getSeconds().toString()) - .replace(/tt/g, date.getHours() >= 12 ? 'PM' : 'AM'); -} - -export function formatTimeString(date: Date, format: string): string { - return format.replace(/\[(([^\[]|\[\])*)\]|(([yMdHhmst])\4{0,3})/g, (match: string, localeformat?: string, unused?, datetimeformat?: string) => { - if (localeformat) return formatLocaleString(date, localeformat); - if (datetimeformat) return formatDateTimeString(date, datetimeformat); - return match; - }); -} diff --git a/packages/frontend/src/scripts/fullscreen.ts b/packages/frontend/src/scripts/fullscreen.ts deleted file mode 100644 index 7a0a018ef3..0000000000 --- a/packages/frontend/src/scripts/fullscreen.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -type PartiallyPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; - -type VideoEl = PartiallyPartial<HTMLVideoElement, 'requestFullscreen'> & { - webkitEnterFullscreen?(): void; - webkitExitFullscreen?(): void; -}; - -type PlayerEl = PartiallyPartial<HTMLElement, 'requestFullscreen'>; - -type RequestFullscreenProps = { - readonly videoEl: VideoEl; - readonly playerEl: PlayerEl; - readonly options?: FullscreenOptions | null; -}; - -type ExitFullscreenProps = { - readonly videoEl: VideoEl; -}; - -export const requestFullscreen = ({ videoEl, playerEl, options }: RequestFullscreenProps) => { - if (playerEl.requestFullscreen != null) { - playerEl.requestFullscreen(options ?? undefined); - return; - } - if (videoEl.webkitEnterFullscreen != null) { - videoEl.webkitEnterFullscreen(); - return; - } -}; - -export const exitFullscreen = ({ videoEl }: ExitFullscreenProps) => { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (document.exitFullscreen != null) { - document.exitFullscreen(); - return; - } - if (videoEl.webkitExitFullscreen != null) { - videoEl.webkitExitFullscreen(); - return; - } -}; diff --git a/packages/frontend/src/scripts/gen-search-query.ts b/packages/frontend/src/scripts/gen-search-query.ts deleted file mode 100644 index a85ee01e26..0000000000 --- a/packages/frontend/src/scripts/gen-search-query.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; -import { host as localHost } from '@@/js/config.js'; - -export async function genSearchQuery(v: any, q: string) { - let host: string; - let userId: string; - if (q.split(' ').some(x => x.startsWith('@'))) { - for (const at of q.split(' ').filter(x => x.startsWith('@')).map(x => x.substring(1))) { - if (at.includes('.')) { - if (at === localHost || at === '.') { - host = null; - } else { - host = at; - } - } else { - const user = await v.api('users/show', Misskey.acct.parse(at)).catch(x => null); - if (user) { - userId = user.id; - } else { - // todo: show error - } - } - } - } - return { - query: q.split(' ').filter(x => !x.startsWith('/') && !x.startsWith('@')).join(' '), - host: host, - userId: userId, - }; -} diff --git a/packages/frontend/src/scripts/get-account-from-id.ts b/packages/frontend/src/scripts/get-account-from-id.ts deleted file mode 100644 index 40afa10f2d..0000000000 --- a/packages/frontend/src/scripts/get-account-from-id.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { get } from '@/scripts/idb-proxy.js'; - -export async function getAccountFromId(id: string) { - const accounts = await get('accounts') as { token: string; id: string; }[]; - if (!accounts) console.log('Accounts are not recorded'); - return accounts.find(account => account.id === id); -} diff --git a/packages/frontend/src/scripts/get-appear-note.ts b/packages/frontend/src/scripts/get-appear-note.ts deleted file mode 100644 index 40ce80eac9..0000000000 --- a/packages/frontend/src/scripts/get-appear-note.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; - -export function getAppearNote(note: Misskey.entities.Note) { - return Misskey.note.isPureRenote(note) ? note.renote : note; -} diff --git a/packages/frontend/src/scripts/get-bg-color.ts b/packages/frontend/src/scripts/get-bg-color.ts deleted file mode 100644 index ccf60b454f..0000000000 --- a/packages/frontend/src/scripts/get-bg-color.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import tinycolor from 'tinycolor2'; - -export const getBgColor = (elem?: Element | null | undefined): string | null => { - if (elem == null) return null; - - const { backgroundColor: bg } = window.getComputedStyle(elem); - - if (bg && tinycolor(bg).getAlpha() !== 0) { - return bg; - } - - return getBgColor(elem.parentElement); -}; diff --git a/packages/frontend/src/scripts/get-dom-node-or-null.ts b/packages/frontend/src/scripts/get-dom-node-or-null.ts deleted file mode 100644 index fbf54675fd..0000000000 --- a/packages/frontend/src/scripts/get-dom-node-or-null.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export const getNodeOrNull = (input: unknown): Node | null => { - if (input instanceof Node) return input; - return null; -}; - -export const getElementOrNull = (input: unknown): Element | null => { - if (input instanceof Element) return input; - return null; -}; - -export const getHTMLElementOrNull = (input: unknown): HTMLElement | null => { - if (input instanceof HTMLElement) return input; - return null; -}; diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts deleted file mode 100644 index c8ab9238d3..0000000000 --- a/packages/frontend/src/scripts/get-drive-file-menu.ts +++ /dev/null @@ -1,162 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; -import { defineAsyncComponent } from 'vue'; -import { i18n } from '@/i18n.js'; -import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import type { MenuItem } from '@/types/menu.js'; -import { defaultStore } from '@/store.js'; - -function rename(file: Misskey.entities.DriveFile) { - os.inputText({ - title: i18n.ts.renameFile, - placeholder: i18n.ts.inputNewFileName, - default: file.name, - }).then(({ canceled, result: name }) => { - if (canceled) return; - misskeyApi('drive/files/update', { - fileId: file.id, - name: name, - }); - }); -} - -function describe(file: Misskey.entities.DriveFile) { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), { - default: file.comment ?? '', - file: file, - }, { - done: caption => { - misskeyApi('drive/files/update', { - fileId: file.id, - comment: caption.length === 0 ? null : caption, - }); - }, - closed: () => dispose(), - }); -} - -function move(file: Misskey.entities.DriveFile) { - os.selectDriveFolder(false).then(folder => { - misskeyApi('drive/files/update', { - fileId: file.id, - folderId: folder[0] ? folder[0].id : null, - }); - }); -} - -function toggleSensitive(file: Misskey.entities.DriveFile) { - misskeyApi('drive/files/update', { - fileId: file.id, - isSensitive: !file.isSensitive, - }).catch(err => { - os.alert({ - type: 'error', - title: i18n.ts.error, - text: err.message, - }); - }); -} - -function copyUrl(file: Misskey.entities.DriveFile) { - copyToClipboard(file.url); - os.success(); -} - -/* -function addApp() { - alert('not implemented yet'); -} -*/ -async function deleteFile(file: Misskey.entities.DriveFile) { - const { canceled } = await os.confirm({ - type: 'warning', - text: i18n.tsx.driveFileDeleteConfirm({ name: file.name }), - }); - - if (canceled) return; - misskeyApi('drive/files/delete', { - fileId: file.id, - }); -} - -export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Misskey.entities.DriveFolder | null): MenuItem[] { - const isImage = file.type.startsWith('image/'); - - const menuItems: MenuItem[] = []; - - menuItems.push({ - type: 'link', - to: `/my/drive/file/${file.id}`, - text: i18n.ts._fileViewer.title, - icon: 'ti ti-info-circle', - }, { type: 'divider' }, { - text: i18n.ts.rename, - icon: 'ti ti-forms', - action: () => rename(file), - }, { - text: i18n.ts.move, - icon: 'ti ti-folder-symlink', - action: () => move(file), - }, { - text: file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, - icon: file.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation', - action: () => toggleSensitive(file), - }, { - text: i18n.ts.describeFile, - icon: 'ti ti-text-caption', - action: () => describe(file), - }); - - if (isImage) { - menuItems.push({ - text: i18n.ts.cropImage, - icon: 'ti ti-crop', - action: () => os.cropImage(file, { - aspectRatio: NaN, - uploadFolder: folder ? folder.id : folder, - }), - }); - } - - menuItems.push({ type: 'divider' }, { - text: i18n.ts.createNoteFromTheFile, - icon: 'ti ti-pencil', - action: () => os.post({ - initialFiles: [file], - }), - }, { - text: i18n.ts.copyUrl, - icon: 'ti ti-link', - action: () => copyUrl(file), - }, { - type: 'a', - href: file.url, - target: '_blank', - text: i18n.ts.download, - icon: 'ti ti-download', - download: file.name, - }, { type: 'divider' }, { - text: i18n.ts.delete, - icon: 'ti ti-trash', - danger: true, - action: () => deleteFile(file), - }); - - if (defaultStore.state.devMode) { - menuItems.push({ type: 'divider' }, { - icon: 'ti ti-id', - text: i18n.ts.copyFileId, - action: () => { - copyToClipboard(file.id); - }, - }); - } - - return menuItems; -} diff --git a/packages/frontend/src/scripts/get-embed-code.ts b/packages/frontend/src/scripts/get-embed-code.ts deleted file mode 100644 index 158ab9c7f8..0000000000 --- a/packages/frontend/src/scripts/get-embed-code.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { defineAsyncComponent } from 'vue'; -import { v4 as uuid } from 'uuid'; -import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js'; -import { url } from '@@/js/config.js'; -import * as os from '@/os.js'; -import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js'; - -const MOBILE_THRESHOLD = 500; - -/** - * パラメータを正規化する(埋め込みコード作成用) - * @param params パラメータ - * @returns 正規化されたパラメータ - */ -export function normalizeEmbedParams(params: EmbedParams): Record<string, string> { - // paramsのvalueをすべてstringに変換。undefinedやnullはプロパティごと消す - const normalizedParams: Record<string, string> = {}; - for (const key in params) { - // デフォルトの値と同じならparamsに含めない - if (params[key] == null || params[key] === defaultEmbedParams[key]) { - continue; - } - switch (typeof params[key]) { - case 'number': - normalizedParams[key] = params[key].toString(); - break; - case 'boolean': - normalizedParams[key] = params[key] ? 'true' : 'false'; - break; - default: - normalizedParams[key] = params[key]; - break; - } - } - return normalizedParams; -} - -/** - * 埋め込みコードを生成(iframe IDの発番もやる) - */ -export function getEmbedCode(path: string, params?: EmbedParams): string { - const iframeId = 'v1_' + uuid(); // 将来embed.jsのバージョンが上がったとき用にv1_を付けておく - - let paramString = ''; - if (params) { - const searchParams = new URLSearchParams(normalizeEmbedParams(params)); - paramString = searchParams.toString() === '' ? '' : '?' + searchParams.toString(); - } - - const iframeCode = [ - `<iframe src="${url + path + paramString}" data-misskey-embed-id="${iframeId}" loading="lazy" referrerpolicy="strict-origin-when-cross-origin" style="border: none; width: 100%; max-width: 500px; height: 300px; color-scheme: light dark;"></iframe>`, - `<script defer src="${url}/embed.js"></script>`, - ]; - return iframeCode.join('\n'); -} - -/** - * 埋め込みコードを生成してコピーする(カスタマイズ機能つき) - * - * カスタマイズ機能がいらない場合(事前にパラメータを指定する場合)は getEmbedCode を直接使ってください - */ -export function genEmbedCode(entity: EmbeddableEntity, id: string, params?: EmbedParams) { - const _params = { ...params }; - - if (embedRouteWithScrollbar.includes(entity) && _params.maxHeight == null) { - _params.maxHeight = 700; - } - - // PCじゃない場合はコードカスタマイズ画面を出さずにそのままコピー - if (window.innerWidth < MOBILE_THRESHOLD) { - copyToClipboard(getEmbedCode(`/embed/${entity}/${id}`, _params)); - os.success(); - } else { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkEmbedCodeGenDialog.vue')), { - entity, - id, - params: _params, - }, { - closed: () => dispose(), - }); - } -} diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts deleted file mode 100644 index 463fec6f97..0000000000 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ /dev/null @@ -1,700 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { defineAsyncComponent, Ref, ShallowRef } from 'vue'; -import * as Misskey from 'misskey-js'; -import { url } from '@@/js/config.js'; -import { claimAchievement } from './achievements.js'; -import type { MenuItem } from '@/types/menu.js'; -import { $i } from '@/account.js'; -import { i18n } from '@/i18n.js'; -import { instance } from '@/instance.js'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { defaultStore, noteActions } from '@/store.js'; -import { miLocalStorage } from '@/local-storage.js'; -import { getUserMenu } from '@/scripts/get-user-menu.js'; -import { clipsCache, favoritedChannelsCache } from '@/cache.js'; -import MkRippleEffect from '@/components/MkRippleEffect.vue'; -import { isSupportShare } from '@/scripts/navigator.js'; -import { getAppearNote } from '@/scripts/get-appear-note.js'; -import { genEmbedCode } from '@/scripts/get-embed-code.js'; - -export async function getNoteClipMenu(props: { - note: Misskey.entities.Note; - isDeleted: Ref<boolean>; - currentClip?: Misskey.entities.Clip; -}) { - function getClipName(clip: Misskey.entities.Clip) { - if ($i && clip.userId === $i.id && clip.notesCount != null) { - return `${clip.name} (${clip.notesCount}/${$i.policies.noteEachClipsLimit})`; - } else { - return clip.name; - } - } - - const appearNote = getAppearNote(props.note); - - const clips = await clipsCache.fetch(); - const menu: MenuItem[] = [...clips.map(clip => ({ - text: getClipName(clip), - action: () => { - claimAchievement('noteClipped1'); - os.promiseDialog( - misskeyApi('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.tsx.confirmToUnclipAlreadyClippedNote({ name: clip.name }), - }); - if (!confirm.canceled) { - os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id }).then(() => { - clipsCache.set(clips.map(c => { - if (c.id === clip.id) { - return { - ...c, - notesCount: Math.max(0, ((c.notesCount ?? 0) - 1)), - }; - } else { - return c; - } - })); - }); - if (props.currentClip?.id === clip.id) props.isDeleted.value = true; - } - } else if (err.id === 'f0dba960-ff73-4615-8df4-d6ac5d9dc118') { - os.alert({ - type: 'error', - text: i18n.ts.clipNoteLimitExceeded, - }); - } else { - os.alert({ - type: 'error', - text: err.message + '\n' + err.id, - }); - } - }, - ).then(() => { - clipsCache.set(clips.map(c => { - if (c.id === clip.id) { - return { - ...c, - notesCount: (c.notesCount ?? 0) + 1, - }; - } else { - return c; - } - })); - }); - }, - })), { type: 'divider' }, { - icon: 'ti ti-plus', - text: i18n.ts.createNew, - action: async () => { - const { canceled, result } = await os.form(i18n.ts.createNewClip, { - name: { - type: 'string', - default: null, - label: i18n.ts.name, - }, - description: { - type: 'string', - required: false, - default: null, - 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); - - clipsCache.delete(); - - claimAchievement('noteClipped1'); - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); - }, - }]; - - return menu; -} - -export function getAbuseNoteMenu(note: Misskey.entities.Note, text: string): MenuItem { - return { - icon: 'ti ti-exclamation-circle', - text, - action: (): void => { - const localUrl = `${url}/notes/${note.id}`; - let noteInfo = ''; - if (note.url ?? note.uri != null) noteInfo = `Note: ${note.url ?? note.uri}\n`; - noteInfo += `Local Note: ${localUrl}\n`; - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), { - user: note.user, - initialComment: `${noteInfo}-----\n`, - }, { - closed: () => dispose(), - }); - }, - }; -} - -export function getCopyNoteLinkMenu(note: Misskey.entities.Note, text: string): MenuItem { - return { - icon: 'ti ti-link', - text, - action: (): void => { - copyToClipboard(`${url}/notes/${note.id}`); - os.success(); - }, - }; -} - -function getNoteEmbedCodeMenu(note: Misskey.entities.Note, text: string): MenuItem | undefined { - if (note.url != null || note.uri != null) return undefined; - if (['specified', 'followers'].includes(note.visibility)) return undefined; - - return { - icon: 'ti ti-code', - text, - action: (): void => { - genEmbedCode('notes', note.id); - }, - }; -} - -export function getNoteMenu(props: { - note: Misskey.entities.Note; - translation: Ref<Misskey.entities.NotesTranslateResponse | null>; - translating: Ref<boolean>; - isDeleted: Ref<boolean>; - currentClip?: Misskey.entities.Clip; -}) { - const appearNote = getAppearNote(props.note); - - const cleanups = [] as (() => void)[]; - - function del(): void { - os.confirm({ - type: 'warning', - text: i18n.ts.noteDeleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - misskeyApi('notes/delete', { - noteId: appearNote.id, - }); - - if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60 && appearNote.userId === $i.id) { - claimAchievement('noteDeletedWithin1min'); - } - }); - } - - function delEdit(): void { - os.confirm({ - type: 'warning', - text: i18n.ts.deleteAndEditConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - misskeyApi('notes/delete', { - noteId: appearNote.id, - }); - - os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel }); - - if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60 && appearNote.userId === $i.id) { - claimAchievement('noteDeletedWithin1min'); - } - }); - } - - function edit(): void { - os.post({ - initialNote: appearNote, - renote: appearNote.renote, - reply: appearNote.reply, - channel: appearNote.channel, - editId: appearNote.id, - initialFiles: appearNote.files, - }); - } - - function toggleFavorite(favorite: boolean): void { - claimAchievement('noteFavorited1'); - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: appearNote.id, - }); - } - - function toggleThreadMute(mute: boolean): void { - os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { - noteId: appearNote.id, - }); - } - - function copyContent(): void { - copyToClipboard(appearNote.text); - os.success(); - } - - function togglePin(pin: boolean): void { - os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { - noteId: appearNote.id, - }, undefined, { - '72dab508-c64d-498f-8740-a8eec1ba385a': { - text: i18n.ts.pinLimitExceeded, - }, - }); - } - - async function unclip(): Promise<void> { - if (!props.currentClip) return; - os.apiWithDialog('clips/remove-note', { clipId: props.currentClip.id, noteId: appearNote.id }); - props.isDeleted.value = true; - } - - async function promote(): Promise<void> { - const { canceled, result: days } = await os.inputNumber({ - title: i18n.ts.numberOfDays, - }); - - if (canceled || days == null) return; - - os.apiWithDialog('admin/promo/create', { - noteId: appearNote.id, - expiresAt: Date.now() + (86400000 * days), - }); - } - - function share(): void { - navigator.share({ - title: i18n.tsx.noteOf({ user: appearNote.user.name ?? appearNote.user.username }), - text: appearNote.text ?? '', - url: `${url}/notes/${appearNote.id}`, - }); - } - - function openDetail(): void { - os.pageWindow(`/notes/${appearNote.id}`); - } - - async function translate(): Promise<void> { - if (props.translation.value != null) return; - props.translating.value = true; - const res = await misskeyApi('notes/translate', { - noteId: appearNote.id, - targetLang: miLocalStorage.getItem('lang') ?? navigator.language, - }); - props.translating.value = false; - props.translation.value = res; - } - - const menuItems: MenuItem[] = []; - - if ($i) { - const statePromise = misskeyApi('notes/state', { - noteId: appearNote.id, - }); - - if (props.currentClip?.userId === $i.id) { - menuItems.push({ - icon: 'ti ti-backspace', - text: i18n.ts.unclip, - danger: true, - action: unclip, - }, { type: 'divider' }); - } - - menuItems.push({ - icon: 'ti ti-info-circle', - text: i18n.ts.details, - action: openDetail, - }, { - icon: 'ti ti-copy', - text: i18n.ts.copyContent, - action: copyContent, - }, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink)); - - if (appearNote.url || appearNote.uri) { - menuItems.push({ - icon: 'ti ti-link', - text: i18n.ts.copyRemoteLink, - action: () => { - copyToClipboard(appearNote.url ?? appearNote.uri); - os.success(); - }, - }, { - icon: 'ti ti-external-link', - text: i18n.ts.showOnRemote, - action: () => { - window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener'); - }, - }); - } else { - menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)); - } - - if (isSupportShare()) { - menuItems.push({ - icon: 'ti ti-share', - text: i18n.ts.share, - action: share, - }); - } - - if ($i.policies.canUseTranslator && instance.translatorAvailable) { - menuItems.push({ - icon: 'ti ti-language-hiragana', - text: i18n.ts.translate, - action: translate, - }); - } - - menuItems.push({ type: 'divider' }); - - menuItems.push(statePromise.then(state => state.isFavorited ? { - icon: 'ti ti-star-off', - text: i18n.ts.unfavorite, - action: () => toggleFavorite(false), - } : { - icon: 'ti ti-star', - text: i18n.ts.favorite, - action: () => toggleFavorite(true), - })); - - menuItems.push({ - type: 'parent', - icon: 'ti ti-paperclip', - text: i18n.ts.clip, - children: () => getNoteClipMenu(props), - }); - - menuItems.push(statePromise.then(state => state.isMutedThread ? { - icon: 'ti ti-message-off', - text: i18n.ts.unmuteThread, - action: () => toggleThreadMute(false), - } : { - icon: 'ti ti-message-off', - text: i18n.ts.muteThread, - action: () => toggleThreadMute(true), - })); - - if (appearNote.userId === $i.id) { - if (($i.pinnedNoteIds ?? []).includes(appearNote.id)) { - menuItems.push({ - icon: 'ti ti-pinned-off', - text: i18n.ts.unpin, - action: () => togglePin(false), - }); - } else { - menuItems.push({ - icon: 'ti ti-pin', - text: i18n.ts.pin, - action: () => togglePin(true), - }); - } - } - - menuItems.push({ - type: 'parent', - icon: 'ti ti-user', - text: i18n.ts.user, - children: async () => { - const user = appearNote.userId === $i?.id ? $i : await misskeyApi('users/show', { userId: appearNote.userId }); - const { menu, cleanup } = getUserMenu(user); - cleanups.push(cleanup); - return menu; - }, - }); - - if (appearNote.userId !== $i.id) { - menuItems.push({ type: 'divider' }); - menuItems.push(getAbuseNoteMenu(appearNote, i18n.ts.reportAbuse)); - } - - if (appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin)) { - menuItems.push({ type: 'divider' }); - menuItems.push({ - type: 'parent', - icon: 'ti ti-device-tv', - text: i18n.ts.channel, - children: async () => { - const channelChildMenu = [] as MenuItem[]; - - const channel = await misskeyApi('channels/show', { channelId: appearNote.channel!.id }); - - if (channel.pinnedNoteIds.includes(appearNote.id)) { - channelChildMenu.push({ - icon: 'ti ti-pinned-off', - text: i18n.ts.unpin, - action: () => os.apiWithDialog('channels/update', { - channelId: appearNote.channel!.id, - pinnedNoteIds: channel.pinnedNoteIds.filter(id => id !== appearNote.id), - }), - }); - } else { - channelChildMenu.push({ - icon: 'ti ti-pin', - text: i18n.ts.pin, - action: () => os.apiWithDialog('channels/update', { - channelId: appearNote.channel!.id, - pinnedNoteIds: [...channel.pinnedNoteIds, appearNote.id], - }), - }); - } - return channelChildMenu; - }, - }); - } - - if (appearNote.userId === $i.id || $i.isModerator || $i.isAdmin) { - menuItems.push({ type: 'divider' }); - if (appearNote.userId === $i.id) { - menuItems.push({ - icon: 'ph-pencil-simple ph-bold ph-lg', - text: i18n.ts.edit, - action: edit, - }); - menuItems.push({ - icon: 'ti ti-edit', - text: i18n.ts.deleteAndEdit, - danger: true, - action: delEdit, - }); - } - menuItems.push({ - icon: 'ti ti-trash', - text: i18n.ts.delete, - danger: true, - action: del, - }); - } - } else { - menuItems.push({ - icon: 'ti ti-info-circle', - text: i18n.ts.details, - action: openDetail, - }, { - icon: 'ti ti-copy', - text: i18n.ts.copyContent, - action: copyContent, - }, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink)); - - if (appearNote.url || appearNote.uri) { - menuItems.push({ - icon: 'ti ti-link', - text: i18n.ts.copyRemoteLink, - action: () => { - copyToClipboard(appearNote.url ?? appearNote.uri); - os.success(); - }, - }, { - icon: 'ti ti-external-link', - text: i18n.ts.showOnRemote, - action: () => { - window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener'); - }, - }); - } else { - menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)); - } - } - - if (noteActions.length > 0) { - menuItems.push({ type: 'divider' }); - - menuItems.push(...noteActions.map(action => ({ - icon: 'ti ti-plug', - text: action.title, - action: () => { - action.handler(appearNote); - }, - }))); - } - - if (defaultStore.state.devMode) { - menuItems.push({ type: 'divider' }, { - icon: 'ti ti-id', - text: i18n.ts.copyNoteId, - action: () => { - copyToClipboard(appearNote.id); - os.success(); - }, - }); - } - - const cleanup = () => { - if (_DEV_) console.log('note menu cleanup', cleanups); - for (const cl of cleanups) { - cl(); - } - }; - - return { - menu: menuItems, - cleanup, - }; -} - -type Visibility = (typeof Misskey.noteVisibilities)[number]; - -function smallerVisibility(a: Visibility, b: Visibility): Visibility { - if (a === 'specified' || b === 'specified') return 'specified'; - if (a === 'followers' || b === 'followers') return 'followers'; - if (a === 'home' || b === 'home') return 'home'; - // if (a === 'public' || b === 'public') - return 'public'; -} - -export function getRenoteMenu(props: { - note: Misskey.entities.Note; - renoteButton: ShallowRef<HTMLElement | undefined>; - mock?: boolean; -}) { - const appearNote = getAppearNote(props.note); - - const channelRenoteItems: MenuItem[] = []; - const normalRenoteItems: MenuItem[] = []; - const normalExternalChannelRenoteItems: MenuItem[] = []; - - if (appearNote.channel) { - channelRenoteItems.push(...[{ - text: i18n.ts.inChannelRenote, - icon: 'ti ti-repeat', - action: () => { - const el = props.renoteButton.value; - if (el) { - const rect = el.getBoundingClientRect(); - const x = rect.left + (el.offsetWidth / 2); - const y = rect.top + (el.offsetHeight / 2); - const { dispose } = os.popup(MkRippleEffect, { x, y }, { - end: () => dispose(), - }); - } - - if (!props.mock) { - misskeyApi('notes/create', { - renoteId: appearNote.id, - channelId: appearNote.channelId, - }).then(() => { - os.toast(i18n.ts.renoted); - }); - } - }, - }, { - text: i18n.ts.inChannelQuote, - icon: 'ti ti-quote', - action: () => { - if (!props.mock) { - os.post({ - renote: appearNote, - channel: appearNote.channel, - }); - } - }, - }]); - } - - if (!appearNote.channel || appearNote.channel.allowRenoteToExternal) { - normalRenoteItems.push(...[{ - text: i18n.ts.renote, - icon: 'ti ti-repeat', - action: () => { - const el = props.renoteButton.value; - if (el) { - const rect = el.getBoundingClientRect(); - const x = rect.left + (el.offsetWidth / 2); - const y = rect.top + (el.offsetHeight / 2); - const { dispose } = os.popup(MkRippleEffect, { x, y }, { - end: () => dispose(), - }); - } - - const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility; - const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly; - - let visibility = appearNote.visibility; - visibility = smallerVisibility(visibility, configuredVisibility); - if (appearNote.channel?.isSensitive) { - visibility = smallerVisibility(visibility, 'home'); - } - - if (!props.mock) { - misskeyApi('notes/create', { - localOnly, - visibility, - renoteId: appearNote.id, - }).then(() => { - os.toast(i18n.ts.renoted); - }); - } - }, - }, (props.mock) ? undefined : { - text: i18n.ts.quote, - icon: 'ti ti-quote', - action: () => { - os.post({ - renote: appearNote, - }); - }, - }]); - - normalExternalChannelRenoteItems.push({ - type: 'parent', - icon: 'ti ti-repeat', - text: appearNote.channel ? i18n.ts.renoteToOtherChannel : i18n.ts.renoteToChannel, - children: async () => { - const channels = await favoritedChannelsCache.fetch(); - return channels.filter((channel) => { - if (!appearNote.channelId) return true; - return channel.id !== appearNote.channelId; - }).map((channel) => ({ - text: channel.name, - action: () => { - const el = props.renoteButton.value; - if (el) { - const rect = el.getBoundingClientRect(); - const x = rect.left + (el.offsetWidth / 2); - const y = rect.top + (el.offsetHeight / 2); - const { dispose } = os.popup(MkRippleEffect, { x, y }, { - end: () => dispose(), - }); - } - - if (!props.mock) { - misskeyApi('notes/create', { - renoteId: appearNote.id, - channelId: channel.id, - }).then(() => { - os.toast(i18n.tsx.renotedToX({ name: channel.name })); - }); - } - }, - })); - }, - }); - } - - const renoteItems = [ - ...normalRenoteItems, - ...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [{ type: 'divider' }] as MenuItem[] : [], - ...channelRenoteItems, - ...(normalExternalChannelRenoteItems.length > 0 && (normalRenoteItems.length > 0 || channelRenoteItems.length > 0)) ? [{ type: 'divider' }] as MenuItem[] : [], - ...normalExternalChannelRenoteItems, - ]; - - return { - menu: renoteItems, - }; -} diff --git a/packages/frontend/src/scripts/get-note-summary.ts b/packages/frontend/src/scripts/get-note-summary.ts deleted file mode 100644 index 4e093bcf4c..0000000000 --- a/packages/frontend/src/scripts/get-note-summary.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; -import { appendContentWarning } from '@@/js/append-content-warning.js'; -import { i18n } from '@/i18n.js'; - -/** - * 投稿を表す文字列を取得します。 - * @param {*} note (packされた)投稿 - */ -export const getNoteSummary = (note?: Misskey.entities.Note | null): string => { - if (note == null) { - return ''; - } - - if (note.deletedAt) { - return `(${i18n.ts.deletedNote})`; - } - - if (note.isHidden) { - return `(${i18n.ts.invisibleNote})`; - } - - let summary = ''; - - // Append mandatory CW, if applicable - let cw = note.cw; - if (note.user.mandatoryCW) { - cw = appendContentWarning(cw, note.user.mandatoryCW); - } - - // 本文 - if (cw != null) { - summary += `CW: ${cw}`; - } else if (note.text) { - summary += note.text; - } - - // ファイルが添付されているとき - if (note.files && note.files.length !== 0) { - summary += ` (${i18n.tsx.withNFiles({ n: note.files.length })})`; - } - - // 投票が添付されているとき - if (note.poll) { - summary += ` (${i18n.ts.poll})`; - } - - // 返信のとき - if (note.replyId) { - if (note.reply && !note.cw) { - summary += `\n\nRE: ${getNoteSummary(note.reply)}`; - } else { - summary += '\n\nRE: ...'; - } - } - - // Renoteのとき - if (note.renoteId) { - if (note.renote && !note.cw) { - summary += `\n\nRN: ${getNoteSummary(note.renote)}`; - } else { - summary += '\n\nRN: ...'; - } - } - - return summary.trim(); -}; diff --git a/packages/frontend/src/scripts/get-note-versions-menu.ts b/packages/frontend/src/scripts/get-note-versions-menu.ts deleted file mode 100644 index 345cec9018..0000000000 --- a/packages/frontend/src/scripts/get-note-versions-menu.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SPDX-FileCopyrightText: marie and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Ref, defineAsyncComponent } from 'vue'; -import * as Misskey from 'misskey-js'; -import { i18n } from '@/i18n.js'; -import * as os from '@/os.js'; -import { misskeyApi } from './misskey-api.js'; -import { MenuItem } from '@/types/menu.js'; -import { dateTimeFormat } from './intl-const.js'; - -export async function getNoteVersionsMenu(props: { - note: Misskey.entities.Note; - menuButton: Ref<HTMLElement>; -}) { - const isRenote = ( - props.note.renote != null && - props.note.text == null && - props.note.fileIds.length === 0 && - props.note.poll == null - ); - - const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note; - - const cleanups = [] as (() => void)[]; - - function openVersion(info): void { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/SkOldNoteWindow.vue')), { - note: appearNote, - oldText: info.text, - updatedAt: info.oldDate ? info.oldDate : info.updatedAt, - }, { - closed: () => dispose(), - }); - } - - const menu: MenuItem[] = []; - const statePromise = misskeyApi('notes/versions', { - noteId: appearNote.id, - }); - - await statePromise.then((versions) => { - for (const edit of versions) { - const _time = edit.oldDate == null ? NaN : - typeof edit.oldDate === 'number' ? edit.oldDate : - (edit.oldDate instanceof Date ? edit.oldDate : new Date(edit.oldDate)).getTime(); - - menu.push({ - icon: 'ph-pencil-simple ph-bold ph-lg', - text: _time ? dateTimeFormat.format(_time) : dateTimeFormat.format(new Date(edit.updatedAt)), - action: () => openVersion(edit), - }); - } - }); - - const cleanup = () => { - if (_DEV_) console.log('note menu cleanup', cleanups); - for (const cl of cleanups) { - cl(); - } - }; - - return { - menu, - cleanup, - }; -} diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts deleted file mode 100644 index 2fbdaf5d3c..0000000000 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ /dev/null @@ -1,439 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { toUnicode } from 'punycode.js'; -import { defineAsyncComponent, ref, watch } from 'vue'; -import * as Misskey from 'misskey-js'; -import { i18n } from '@/i18n.js'; -import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { host, url } from '@@/js/config.js'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { defaultStore, userActions } from '@/store.js'; -import { $i, iAmModerator } from '@/account.js'; -import { notesSearchAvailable, canSearchNonLocalNotes } from '@/scripts/check-permissions.js'; -import { IRouter } from '@/nirax.js'; -import { antennasCache, rolesCache, userListsCache } from '@/cache.js'; -import { mainRouter } from '@/router/main.js'; -import { genEmbedCode } from '@/scripts/get-embed-code.js'; -import type { MenuItem } from '@/types/menu.js'; - -export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) { - const meId = $i ? $i.id : null; - - const cleanups = [] as (() => void)[]; - - async function toggleMute() { - if (user.isMuted) { - os.apiWithDialog('mute/delete', { - userId: user.id, - }).then(() => { - user.isMuted = false; - }); - } else { - const { canceled, result: period } = await os.select({ - title: i18n.ts.mutePeriod, - items: [{ - value: 'indefinitely', text: i18n.ts.indefinitely, - }, { - value: 'tenMinutes', text: i18n.ts.tenMinutes, - }, { - value: 'oneHour', text: i18n.ts.oneHour, - }, { - value: 'oneDay', text: i18n.ts.oneDay, - }, { - value: 'oneWeek', text: i18n.ts.oneWeek, - }], - default: 'indefinitely', - }); - if (canceled) return; - - const expiresAt = period === 'indefinitely' ? null - : period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10) - : period === 'oneHour' ? Date.now() + (1000 * 60 * 60) - : period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24) - : period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7) - : null; - - os.apiWithDialog('mute/create', { - userId: user.id, - expiresAt, - }).then(() => { - user.isMuted = true; - }); - } - } - - async function toggleRenoteMute() { - os.apiWithDialog(user.isRenoteMuted ? 'renote-mute/delete' : 'renote-mute/create', { - userId: user.id, - }).then(() => { - user.isRenoteMuted = !user.isRenoteMuted; - }); - } - - async function toggleBlock() { - if (!await getConfirmed(user.isBlocking ? i18n.ts.unblockConfirm : i18n.ts.blockConfirm)) return; - - os.apiWithDialog(user.isBlocking ? 'blocking/delete' : 'blocking/create', { - userId: user.id, - }).then(() => { - user.isBlocking = !user.isBlocking; - }); - } - - async function toggleNotify() { - os.apiWithDialog('following/update', { - userId: user.id, - notify: user.notify === 'normal' ? 'none' : 'normal', - }).then(() => { - user.notify = user.notify === 'normal' ? 'none' : 'normal'; - }); - } - - function reportAbuse() { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), { - user: user, - }, { - closed: () => dispose(), - }); - } - - async function getConfirmed(text: string): Promise<boolean> { - const confirm = await os.confirm({ - type: 'question', - text, - }); - - return !confirm.canceled; - } - - async function userInfoUpdate() { - os.apiWithDialog('federation/update-remote-user', { - userId: user.id, - }); - } - - async function invalidateFollow() { - if (!await getConfirmed(i18n.ts.breakFollowConfirm)) return; - - os.apiWithDialog('following/invalidate', { - userId: user.id, - }).then(() => { - user.isFollowed = !user.isFollowed; - }); - } - - async function editMemo(): Promise<void> { - const userDetailed = await misskeyApi('users/show', { - userId: user.id, - }); - const { canceled, result } = await os.form(i18n.ts.editMemo, { - memo: { - type: 'string', - required: true, - multiline: true, - label: i18n.ts.memo, - default: userDetailed.memo, - }, - }); - if (canceled) return; - - os.apiWithDialog('users/update-memo', { - memo: result.memo, - userId: user.id, - }); - } - - const menuItems: MenuItem[] = []; - - menuItems.push({ - icon: 'ti ti-at', - text: i18n.ts.copyUsername, - action: () => { - copyToClipboard(`@${user.username}@${user.host ?? host}`); - }, - }); - - if (notesSearchAvailable && (user.host == null || canSearchNonLocalNotes)) { - menuItems.push({ - icon: 'ti ti-search', - text: i18n.ts.searchThisUsersNotes, - action: () => { - router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`); - }, - }); - } - - if (iAmModerator) { - menuItems.push({ - icon: 'ti ti-user-exclamation', - text: i18n.ts.moderation, - action: () => { - router.push(`/admin/user/${user.id}`); - }, - }); - } - - menuItems.push({ - icon: 'ti ti-rss', - text: i18n.ts.copyRSS, - action: () => { - copyToClipboard(`${user.host ?? host}/@${user.username}.atom`); - }, - }); - - if (user.host != null && user.url != null) { - menuItems.push({ - icon: 'ti ti-external-link', - text: i18n.ts.showOnRemote, - action: () => { - if (user.url == null) return; - window.open(user.url, '_blank', 'noopener'); - }, - }); - } else { - menuItems.push({ - icon: 'ti ti-code', - text: i18n.ts.genEmbedCode, - type: 'parent', - children: [{ - text: i18n.ts.noteOfThisUser, - action: () => { - genEmbedCode('user-timeline', user.id); - }, - }], // TODO: ユーザーカードの埋め込みなど - }); - } - - menuItems.push({ - icon: 'ti ti-share', - text: i18n.ts.copyProfileUrl, - action: () => { - const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`; - copyToClipboard(`${url}/${canonical}`); - }, - }); - - if ($i) { - menuItems.push({ - icon: 'ti ti-mail', - text: i18n.ts.sendMessage, - action: () => { - const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`; - os.post({ specified: user, initialText: `${canonical} ` }); - }, - }, { type: 'divider' }, { - icon: 'ti ti-pencil', - text: i18n.ts.editMemo, - action: editMemo, - }, { - type: 'parent', - icon: 'ti ti-list', - text: i18n.ts.addToList, - children: async () => { - const lists = await userListsCache.fetch(); - return lists.map(list => { - const isListed = ref(list.userIds?.includes(user.id) ?? false); - cleanups.push(watch(isListed, () => { - if (isListed.value) { - os.apiWithDialog('users/lists/push', { - listId: list.id, - userId: user.id, - }).then(() => { - list.userIds?.push(user.id); - }); - } else { - os.apiWithDialog('users/lists/pull', { - listId: list.id, - userId: user.id, - }).then(() => { - list.userIds?.splice(list.userIds?.indexOf(user.id), 1); - }); - } - })); - - return { - type: 'switch', - text: list.name, - ref: isListed, - }; - }); - }, - }, { - type: 'parent', - icon: 'ti ti-antenna', - text: i18n.ts.addToAntenna, - children: async () => { - const antennas = await antennasCache.fetch(); - const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`; - return antennas.filter((a) => a.src === 'users').map(antenna => ({ - text: antenna.name, - action: async () => { - await os.apiWithDialog('antennas/update', { - antennaId: antenna.id, - name: antenna.name, - keywords: antenna.keywords, - excludeKeywords: antenna.excludeKeywords, - src: antenna.src, - userListId: antenna.userListId, - users: [...antenna.users, canonical], - caseSensitive: antenna.caseSensitive, - withReplies: antenna.withReplies, - withFile: antenna.withFile, - notify: antenna.notify, - }); - antennasCache.delete(); - }, - })); - }, - }); - } - - if ($i && meId !== user.id) { - if (iAmModerator) { - menuItems.push({ - type: 'parent', - icon: 'ti ti-badges', - text: i18n.ts.roles, - children: async () => { - const roles = await rolesCache.fetch(); - - return roles.filter(r => r.target === 'manual').map(r => ({ - text: r.name, - action: async () => { - const { canceled, result: period } = await os.select({ - title: i18n.ts.period + ': ' + r.name, - items: [{ - value: 'indefinitely', text: i18n.ts.indefinitely, - }, { - value: 'oneHour', text: i18n.ts.oneHour, - }, { - value: 'oneDay', text: i18n.ts.oneDay, - }, { - value: 'oneWeek', text: i18n.ts.oneWeek, - }, { - value: 'oneMonth', text: i18n.ts.oneMonth, - }], - default: 'indefinitely', - }); - if (canceled) return; - - const expiresAt = period === 'indefinitely' ? null - : period === 'oneHour' ? Date.now() + (1000 * 60 * 60) - : period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24) - : period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7) - : period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30) - : null; - - os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id, expiresAt }); - }, - })); - }, - }); - } - - // フォローしたとしても user.isFollowing はリアルタイム更新されないので不便なため - //if (user.isFollowing) { - const withRepliesRef = ref(user.withReplies ?? false); - - menuItems.push({ - type: 'switch', - icon: 'ti ti-messages', - text: i18n.ts.showRepliesToOthersInTimeline, - ref: withRepliesRef, - }, { - icon: user.notify === 'none' ? 'ti ti-bell' : 'ti ti-bell-off', - text: user.notify === 'none' ? i18n.ts.notifyNotes : i18n.ts.unnotifyNotes, - action: toggleNotify, - }); - - watch(withRepliesRef, (withReplies) => { - misskeyApi('following/update', { - userId: user.id, - withReplies, - }).then(() => { - user.withReplies = withReplies; - }); - }); - //} - - menuItems.push({ type: 'divider' }, { - icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off', - text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute, - action: toggleMute, - }, { - icon: user.isRenoteMuted ? 'ti ti-repeat' : 'ti ti-repeat-off', - text: user.isRenoteMuted ? i18n.ts.renoteUnmute : i18n.ts.renoteMute, - action: toggleRenoteMute, - }, { - icon: 'ti ti-ban', - text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block, - action: toggleBlock, - }); - - if (user.isFollowed) { - menuItems.push({ - icon: 'ti ti-link-off', - text: i18n.ts.breakFollow, - action: invalidateFollow, - }); - } - - menuItems.push({ type: 'divider' }, { - icon: 'ti ti-exclamation-circle', - text: i18n.ts.reportAbuse, - action: reportAbuse, - }); - } - - if (user.host !== null) { - menuItems.push({ type: 'divider' }, { - icon: 'ti ti-refresh', - text: i18n.ts.updateRemoteUser, - action: userInfoUpdate, - }); - } - - if (defaultStore.state.devMode) { - menuItems.push({ type: 'divider' }, { - icon: 'ti ti-id', - text: i18n.ts.copyUserId, - action: () => { - copyToClipboard(user.id); - }, - }); - } - - if ($i && meId === user.id) { - menuItems.push({ type: 'divider' }, { - icon: 'ti ti-pencil', - text: i18n.ts.editProfile, - action: () => { - router.push('/settings/profile'); - }, - }); - } - - if (userActions.length > 0) { - menuItems.push({ type: 'divider' }, ...userActions.map(action => ({ - icon: 'ti ti-plug', - text: action.title, - action: () => { - action.handler(user); - }, - }))); - } - - return { - menu: menuItems, - cleanup: () => { - if (_DEV_) console.log('user menu cleanup', cleanups); - for (const cl of cleanups) { - cl(); - } - }, - }; -} diff --git a/packages/frontend/src/scripts/get-user-name.ts b/packages/frontend/src/scripts/get-user-name.ts deleted file mode 100644 index 56e91abba0..0000000000 --- a/packages/frontend/src/scripts/get-user-name.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export default function(user: { name?: string | null, username: string }): string { - return user.name === '' ? user.username : user.name ?? user.username; -} diff --git a/packages/frontend/src/scripts/hotkey.ts b/packages/frontend/src/scripts/hotkey.ts deleted file mode 100644 index d5304ee210..0000000000 --- a/packages/frontend/src/scripts/hotkey.ts +++ /dev/null @@ -1,172 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { getHTMLElementOrNull } from "@/scripts/get-dom-node-or-null.js"; - -//#region types -export type Keymap = Record<string, CallbackFunction | CallbackObject>; - -type CallbackFunction = (ev: KeyboardEvent) => unknown; - -type CallbackObject = { - callback: CallbackFunction; - allowRepeat?: boolean; -}; - -type Pattern = { - which: string[]; - ctrl: boolean; - alt: boolean; - shift: boolean; -}; - -type Action = { - patterns: Pattern[]; - callback: CallbackFunction; - options: Required<Omit<CallbackObject, 'callback'>>; -}; -//#endregion - -//#region consts -const KEY_ALIASES = { - 'esc': 'Escape', - 'enter': 'Enter', - 'space': ' ', - 'up': 'ArrowUp', - 'down': 'ArrowDown', - 'left': 'ArrowLeft', - 'right': 'ArrowRight', - 'plus': ['+', ';'], -}; - -const MODIFIER_KEYS = ['ctrl', 'alt', 'shift']; - -const IGNORE_ELEMENTS = ['input', 'textarea', 'ruffle-player']; -//#endregion - -//#region store -let latestHotkey: Pattern & { callback: CallbackFunction } | null = null; -//#endregion - -//#region impl -export const makeHotkey = (keymap: Keymap) => { - const actions = parseKeymap(keymap); - return (ev: KeyboardEvent) => { - if ('pswp' in window && window.pswp != null) return; - if (document.activeElement != null) { - if (IGNORE_ELEMENTS.includes(document.activeElement.tagName.toLowerCase())) return; - if (getHTMLElementOrNull(document.activeElement)?.isContentEditable) return; - } - for (const action of actions) { - if (matchPatterns(ev, action)) { - ev.preventDefault(); - ev.stopPropagation(); - action.callback(ev); - storePattern(ev, action.callback); - } - } - }; -}; - -const parseKeymap = (keymap: Keymap) => { - return Object.entries(keymap).map(([rawPatterns, rawCallback]) => { - const patterns = parsePatterns(rawPatterns); - const callback = parseCallback(rawCallback); - const options = parseOptions(rawCallback); - return { patterns, callback, options } as const satisfies Action; - }); -}; - -const parsePatterns = (rawPatterns: keyof Keymap) => { - return rawPatterns.split('|').map(part => { - const keys = part.split('+').map(trimLower); - const which = parseKeyCode(keys.findLast(x => !MODIFIER_KEYS.includes(x))); - const ctrl = keys.includes('ctrl'); - const alt = keys.includes('alt'); - const shift = keys.includes('shift'); - return { which, ctrl, alt, shift } as const satisfies Pattern; - }); -}; - -const parseCallback = (rawCallback: Keymap[keyof Keymap]) => { - if (typeof rawCallback === 'object') { - return rawCallback.callback; - } - return rawCallback; -}; - -const parseOptions = (rawCallback: Keymap[keyof Keymap]) => { - const defaultOptions = { - allowRepeat: false, - } as const satisfies Action['options']; - if (typeof rawCallback === 'object') { - const { callback, ...rawOptions } = rawCallback; - const options = { ...defaultOptions, ...rawOptions }; - return { ...options } as const satisfies Action['options']; - } - return { ...defaultOptions } as const satisfies Action['options']; -}; - -const matchPatterns = (ev: KeyboardEvent, action: Action) => { - const { patterns, options, callback } = action; - if (ev.repeat && !options.allowRepeat) return false; - const key = ev.key.toLowerCase(); - return patterns.some(({ which, ctrl, shift, alt }) => { - if ( - options.allowRepeat === false && - latestHotkey != null && - latestHotkey.which.includes(key) && - latestHotkey.ctrl === ctrl && - latestHotkey.alt === alt && - latestHotkey.shift === shift && - latestHotkey.callback === callback - ) { - return false; - } - if (!which.includes(key)) return false; - if (ctrl !== (ev.ctrlKey || ev.metaKey)) return false; - if (alt !== ev.altKey) return false; - if (shift !== ev.shiftKey) return false; - return true; - }); -}; - -let lastHotKeyStoreTimer: number | null = null; - -const storePattern = (ev: KeyboardEvent, callback: CallbackFunction) => { - if (lastHotKeyStoreTimer != null) { - clearTimeout(lastHotKeyStoreTimer); - } - - latestHotkey = { - which: [ev.key.toLowerCase()], - ctrl: ev.ctrlKey || ev.metaKey, - alt: ev.altKey, - shift: ev.shiftKey, - callback, - }; - - lastHotKeyStoreTimer = window.setTimeout(() => { - latestHotkey = null; - }, 500); -}; - -const parseKeyCode = (input?: string | null) => { - if (input == null) return []; - const raw = getValueByKey(KEY_ALIASES, input); - if (raw == null) return [input]; - if (typeof raw === 'string') return [trimLower(raw)]; - return raw.map(trimLower); -}; - -const getValueByKey = < - T extends Record<keyof any, unknown>, - K extends keyof T | keyof any, - R extends K extends keyof T ? T[K] : T[keyof T] | undefined, ->(obj: T, key: K) => { - return obj[key] as R; -}; - -const trimLower = (str: string) => str.trim().toLowerCase(); -//#endregion diff --git a/packages/frontend/src/scripts/idb-proxy.ts b/packages/frontend/src/scripts/idb-proxy.ts deleted file mode 100644 index 20f51660c7..0000000000 --- a/packages/frontend/src/scripts/idb-proxy.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -// FirefoxのプライベートモードなどではindexedDBが使用不可能なので、 -// indexedDBが使えない環境ではlocalStorageを使う -import { - get as iget, - set as iset, - del as idel, -} from 'idb-keyval'; -import { miLocalStorage } from '@/local-storage.js'; - -const PREFIX = 'idbfallback::'; - -let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && typeof window.indexedDB.open === 'function') : true; - -// iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。 -// バグが治って再度有効化するのであれば、cypressのコマンド内のコメントアウトを外すこと -// see https://github.com/misskey-dev/misskey/issues/13605#issuecomment-2053652123 -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-expect-error -if (window.Cypress) { - idbAvailable = false; - console.log('Cypress detected. It will use localStorage.'); -} - -if (idbAvailable) { - await iset('idb-test', 'test') - .catch(err => { - console.error('idb error', err); - console.error('indexedDB is unavailable. It will use localStorage.'); - idbAvailable = false; - }); -} else { - console.error('indexedDB is unavailable. It will use localStorage.'); -} - -export async function get(key: string) { - if (idbAvailable) return iget(key); - return miLocalStorage.getItemAsJson(`${PREFIX}${key}`); -} - -export async function set(key: string, val: any) { - if (idbAvailable) return iset(key, val); - return miLocalStorage.setItemAsJson(`${PREFIX}${key}`, val); -} - -export async function del(key: string) { - if (idbAvailable) return idel(key); - return miLocalStorage.removeItem(`${PREFIX}${key}`); -} diff --git a/packages/frontend/src/scripts/idle-render.ts b/packages/frontend/src/scripts/idle-render.ts deleted file mode 100644 index 6adfedcb9f..0000000000 --- a/packages/frontend/src/scripts/idle-render.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -const requestIdleCallback: typeof globalThis.requestIdleCallback = globalThis.requestIdleCallback ?? ((callback) => { - const start = performance.now(); - const timeoutId = setTimeout(() => { - callback({ - didTimeout: false, // polyfill でタイムアウト発火することはない - timeRemaining() { - const diff = performance.now() - start; - return Math.max(0, 50 - diff); // <https://www.w3.org/TR/requestidlecallback/#idle-periods> - }, - }); - }); - return timeoutId; -}); -const cancelIdleCallback: typeof globalThis.cancelIdleCallback = globalThis.cancelIdleCallback ?? ((timeoutId) => { - clearTimeout(timeoutId); -}); - -class IdlingRenderScheduler { - #renderers: Set<FrameRequestCallback>; - #rafId: number; - #ricId: number; - - constructor() { - this.#renderers = new Set(); - this.#rafId = 0; - this.#ricId = requestIdleCallback((deadline) => this.#schedule(deadline)); - } - - #schedule(deadline: IdleDeadline): void { - if (deadline.timeRemaining()) { - this.#rafId = requestAnimationFrame((time) => { - for (const renderer of this.#renderers) { - renderer(time); - } - }); - } - this.#ricId = requestIdleCallback((arg) => this.#schedule(arg)); - } - - add(renderer: FrameRequestCallback): void { - this.#renderers.add(renderer); - } - - delete(renderer: FrameRequestCallback): void { - this.#renderers.delete(renderer); - } - - dispose(): void { - this.#renderers.clear(); - cancelAnimationFrame(this.#rafId); - cancelIdleCallback(this.#ricId); - } -} - -export const defaultIdlingRenderScheduler = new IdlingRenderScheduler(); diff --git a/packages/frontend/src/scripts/init-chart.ts b/packages/frontend/src/scripts/init-chart.ts deleted file mode 100644 index 41e1636aa7..0000000000 --- a/packages/frontend/src/scripts/init-chart.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { - Chart, - ArcElement, - LineElement, - BarElement, - PointElement, - BarController, - LineController, - DoughnutController, - CategoryScale, - LinearScale, - TimeScale, - Legend, - Title, - Tooltip, - SubTitle, - Filler, -} from 'chart.js'; -import gradient from 'chartjs-plugin-gradient'; -import zoomPlugin from 'chartjs-plugin-zoom'; -import { MatrixController, MatrixElement } from 'chartjs-chart-matrix'; -import { defaultStore } from '@/store.js'; -import 'chartjs-adapter-date-fns'; - -export function initChart() { - Chart.register( - ArcElement, - LineElement, - BarElement, - PointElement, - BarController, - LineController, - DoughnutController, - CategoryScale, - LinearScale, - TimeScale, - Legend, - Title, - Tooltip, - SubTitle, - Filler, - MatrixController, MatrixElement, - zoomPlugin, - gradient, - ); - - // フォントカラー - Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg'); - - Chart.defaults.borderColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; - - Chart.defaults.animation = false; -} diff --git a/packages/frontend/src/scripts/initialize-sw.ts b/packages/frontend/src/scripts/initialize-sw.ts deleted file mode 100644 index 867ebf19ed..0000000000 --- a/packages/frontend/src/scripts/initialize-sw.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { lang } from '@@/js/config.js'; - -export async function initializeSw() { - if (!('serviceWorker' in navigator)) return; - - navigator.serviceWorker.register('/sw.js', { scope: '/', type: 'classic' }); - navigator.serviceWorker.ready.then(registration => { - registration.active?.postMessage({ - msg: 'initialize', - lang, - }); - }); -} diff --git a/packages/frontend/src/scripts/install-plugin.ts b/packages/frontend/src/scripts/install-plugin.ts deleted file mode 100644 index 72ff8bd5ff..0000000000 --- a/packages/frontend/src/scripts/install-plugin.ts +++ /dev/null @@ -1,135 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { defineAsyncComponent } from 'vue'; -import { compareVersions } from 'compare-versions'; -import { v4 as uuid } from 'uuid'; -import { Interpreter, Parser, utils } from '@syuilo/aiscript'; -import type { Plugin } from '@/store.js'; -import { ColdDeviceStorage } from '@/store.js'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { i18n } from '@/i18n.js'; - -export type AiScriptPluginMeta = { - name: string; - version: string; - author: string; - description?: string; - permissions?: string[]; - config?: Record<string, any>; -}; - -const parser = new Parser(); - -export function savePlugin({ id, meta, src, token }: { - id: string; - meta: AiScriptPluginMeta; - src: string; - token: string; -}) { - ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({ - ...meta, - id, - active: true, - configData: {}, - token: token, - src: src, - } as Plugin)); -} - -export function isSupportedAiScriptVersion(version: string): boolean { - try { - return (compareVersions(version, '0.12.0') >= 0); - } catch (err) { - return false; - } -} - -export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta> { - if (!code) { - throw new Error('code is required'); - } - - const lv = utils.getLangVersion(code); - if (lv == null) { - throw new Error('No language version annotation found'); - } else if (!isSupportedAiScriptVersion(lv)) { - throw new Error(`Aiscript version '${lv}' is not supported`); - } - - let ast; - try { - ast = parser.parse(code); - } catch (err) { - if (err instanceof Error) { - throw new Error(`Aiscript syntax error\n${(err as Error).message}`); - } else { - throw new Error('Aiscript syntax error'); - } - } - - const meta = Interpreter.collectMetadata(ast); - if (meta == null) { - throw new Error('Meta block not found'); - } - - const metadata = meta.get(null); - if (metadata == null) { - throw new Error('Metadata not found'); - } - - const { name, version, author, description, permissions, config } = metadata; - if (name == null || version == null || author == null) { - throw new Error('Required property not found'); - } - - return { - name, - version, - author, - description, - permissions, - config, - }; -} - -export async function installPlugin(code: string, meta?: AiScriptPluginMeta) { - if (!code) return; - - let realMeta: AiScriptPluginMeta; - if (!meta) { - realMeta = await parsePluginMeta(code); - } else { - realMeta = meta; - } - - const token = realMeta.permissions == null || realMeta.permissions.length === 0 ? null : await new Promise((res, rej) => { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), { - title: i18n.ts.tokenRequested, - information: i18n.ts.pluginTokenRequestedDescription, - initialName: realMeta.name, - initialPermissions: realMeta.permissions, - }, { - done: async result => { - const { name, permissions } = result; - const { token } = await misskeyApi('miauth/gen-token', { - session: null, - name: name, - permission: permissions, - }); - res(token); - }, - closed: () => dispose(), - }); - }); - - savePlugin({ - id: uuid(), - meta: realMeta, - token, - src: code, - }); -} diff --git a/packages/frontend/src/scripts/install-theme.ts b/packages/frontend/src/scripts/install-theme.ts deleted file mode 100644 index 866f1225bf..0000000000 --- a/packages/frontend/src/scripts/install-theme.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import JSON5 from 'json5'; -import { addTheme, getThemes } from '@/theme-store.js'; -import { Theme, applyTheme, validateTheme } from '@/scripts/theme.js'; - -export function parseThemeCode(code: string): Theme { - let theme; - - try { - theme = JSON5.parse(code); - } catch (err) { - throw new Error('Failed to parse theme json'); - } - if (!validateTheme(theme)) { - throw new Error('This theme is invaild'); - } - if (getThemes().some(t => t.id === theme.id)) { - throw new Error('This theme is already installed'); - } - - return theme; -} - -export function previewTheme(code: string): void { - const theme = parseThemeCode(code); - if (theme) applyTheme(theme, false); -} - -export async function installTheme(code: string): Promise<void> { - const theme = parseThemeCode(code); - if (!theme) return; - await addTheme(theme); -} diff --git a/packages/frontend/src/scripts/intl-const.ts b/packages/frontend/src/scripts/intl-const.ts deleted file mode 100644 index 385f59ec39..0000000000 --- a/packages/frontend/src/scripts/intl-const.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { lang } from '@@/js/config.js'; - -export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP'); - -let _dateTimeFormat: Intl.DateTimeFormat; -try { - _dateTimeFormat = new Intl.DateTimeFormat(versatileLang, { - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - }); -} catch (err) { - console.warn(err); - if (_DEV_) console.log('[Intl] Fallback to en-US'); - - // Fallback to en-US - _dateTimeFormat = new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - }); -} -export const dateTimeFormat = _dateTimeFormat; - -export const timeZone = dateTimeFormat.resolvedOptions().timeZone; - -export const hemisphere = /^(australia|pacific|antarctica|indian)\//i.test(timeZone) ? 'S' : 'N'; - -let _numberFormat: Intl.NumberFormat; -try { - _numberFormat = new Intl.NumberFormat(versatileLang); -} catch (err) { - console.warn(err); - if (_DEV_) console.log('[Intl] Fallback to en-US'); - - // Fallback to en-US - _numberFormat = new Intl.NumberFormat('en-US'); -} -export const numberFormat = _numberFormat; diff --git a/packages/frontend/src/scripts/is-device-darkmode.ts b/packages/frontend/src/scripts/is-device-darkmode.ts deleted file mode 100644 index 4f487c7cb9..0000000000 --- a/packages/frontend/src/scripts/is-device-darkmode.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export function isDeviceDarkmode() { - return window.matchMedia('(prefers-color-scheme: dark)').matches; -} diff --git a/packages/frontend/src/scripts/isFfVisibleForMe.ts b/packages/frontend/src/scripts/isFfVisibleForMe.ts deleted file mode 100644 index e28e5725bc..0000000000 --- a/packages/frontend/src/scripts/isFfVisibleForMe.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; -import { $i } from '@/account.js'; - -export function isFollowingVisibleForMe(user: Misskey.entities.UserDetailed): boolean { - if ($i && ($i.id === user.id || $i.isAdmin || $i.isModerator)) return true; - - if (user.followingVisibility === 'private') return false; - if (user.followingVisibility === 'followers' && !user.isFollowing) return false; - - return true; -} -export function isFollowersVisibleForMe(user: Misskey.entities.UserDetailed): boolean { - if ($i && ($i.id === user.id || $i.isAdmin || $i.isModerator)) return true; - - if (user.followersVisibility === 'private') return false; - if (user.followersVisibility === 'followers' && !user.isFollowing) return false; - - return true; -} diff --git a/packages/frontend/src/scripts/key-event.ts b/packages/frontend/src/scripts/key-event.ts deleted file mode 100644 index a72776d48c..0000000000 --- a/packages/frontend/src/scripts/key-event.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -/** - * {@link KeyboardEvent.code} の値を表す文字列。不足分は適宜追加する - * @see https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values - */ -export type KeyCode = - | 'Backspace' - | 'Tab' - | 'Enter' - | 'Shift' - | 'Control' - | 'Alt' - | 'Pause' - | 'CapsLock' - | 'Escape' - | 'Space' - | 'PageUp' - | 'PageDown' - | 'End' - | 'Home' - | 'ArrowLeft' - | 'ArrowUp' - | 'ArrowRight' - | 'ArrowDown' - | 'Insert' - | 'Delete' - | 'Digit0' - | 'Digit1' - | 'Digit2' - | 'Digit3' - | 'Digit4' - | 'Digit5' - | 'Digit6' - | 'Digit7' - | 'Digit8' - | 'Digit9' - | 'KeyA' - | 'KeyB' - | 'KeyC' - | 'KeyD' - | 'KeyE' - | 'KeyF' - | 'KeyG' - | 'KeyH' - | 'KeyI' - | 'KeyJ' - | 'KeyK' - | 'KeyL' - | 'KeyM' - | 'KeyN' - | 'KeyO' - | 'KeyP' - | 'KeyQ' - | 'KeyR' - | 'KeyS' - | 'KeyT' - | 'KeyU' - | 'KeyV' - | 'KeyW' - | 'KeyX' - | 'KeyY' - | 'KeyZ' - | 'MetaLeft' - | 'MetaRight' - | 'ContextMenu' - | 'F1' - | 'F2' - | 'F3' - | 'F4' - | 'F5' - | 'F6' - | 'F7' - | 'F8' - | 'F9' - | 'F10' - | 'F11' - | 'F12' - | 'NumLock' - | 'ScrollLock' - | 'Semicolon' - | 'Equal' - | 'Comma' - | 'Minus' - | 'Period' - | 'Slash' - | 'Backquote' - | 'BracketLeft' - | 'Backslash' - | 'BracketRight' - | 'Quote' - | 'Meta' - | 'AltGraph' - ; - -/** - * 修飾キーを表す文字列。不足分は適宜追加する。 - */ -export type KeyModifier = - | 'Shift' - | 'Control' - | 'Alt' - | 'Meta' - ; - -/** - * 押下されたキー以外の状態を表す文字列。不足分は適宜追加する。 - */ -export type KeyState = - | 'composing' - | 'repeat' - ; - -export type KeyEventHandler = { - modifiers?: KeyModifier[]; - states?: KeyState[]; - code: KeyCode | 'any'; - handler: (event: KeyboardEvent) => void; -} - -export function handleKeyEvent(event: KeyboardEvent, handlers: KeyEventHandler[]) { - function checkModifier(ev: KeyboardEvent, modifiers? : KeyModifier[]) { - if (modifiers) { - return modifiers.every(modifier => ev.getModifierState(modifier)); - } - return true; - } - - function checkState(ev: KeyboardEvent, states?: KeyState[]) { - if (states) { - return states.every(state => ev.getModifierState(state)); - } - return true; - } - - let hit = false; - for (const handler of handlers.filter(it => it.code === event.code)) { - if (checkModifier(event, handler.modifiers) && checkState(event, handler.states)) { - handler.handler(event); - hit = true; - break; - } - } - - if (!hit) { - for (const handler of handlers.filter(it => it.code === 'any')) { - handler.handler(event); - } - } -} diff --git a/packages/frontend/src/scripts/langmap.ts b/packages/frontend/src/scripts/langmap.ts deleted file mode 100644 index b32de15963..0000000000 --- a/packages/frontend/src/scripts/langmap.ts +++ /dev/null @@ -1,671 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -// TODO: sharedに置いてバックエンドのと統合したい -export const langmap = { - 'ach': { - nativeName: 'Lwo', - }, - 'ady': { - nativeName: 'Адыгэбзэ', - }, - 'af': { - nativeName: 'Afrikaans', - }, - 'af-NA': { - nativeName: 'Afrikaans (Namibia)', - }, - 'af-ZA': { - nativeName: 'Afrikaans (South Africa)', - }, - 'ak': { - nativeName: 'Tɕɥi', - }, - 'ar': { - nativeName: 'العربية', - }, - 'ar-AR': { - nativeName: 'العربية', - }, - 'ar-MA': { - nativeName: 'العربية', - }, - 'ar-SA': { - nativeName: 'العربية (السعودية)', - }, - 'ay-BO': { - nativeName: 'Aymar aru', - }, - 'az': { - nativeName: 'Azərbaycan dili', - }, - 'az-AZ': { - nativeName: 'Azərbaycan dili', - }, - 'be-BY': { - nativeName: 'Беларуская', - }, - 'bg': { - nativeName: 'Български', - }, - 'bg-BG': { - nativeName: 'Български', - }, - 'bn': { - nativeName: 'বাংলা', - }, - 'bn-IN': { - nativeName: 'বাংলা (ভারত)', - }, - 'bn-BD': { - nativeName: 'বাংলা(বাংলাদেশ)', - }, - 'br': { - nativeName: 'Brezhoneg', - }, - 'bs-BA': { - nativeName: 'Bosanski', - }, - 'ca': { - nativeName: 'Català', - }, - 'ca-ES': { - nativeName: 'Català', - }, - 'cak': { - nativeName: 'Maya Kaqchikel', - }, - 'ck-US': { - nativeName: 'ᏣᎳᎩ (tsalagi)', - }, - 'cs': { - nativeName: 'Čeština', - }, - 'cs-CZ': { - nativeName: 'Čeština', - }, - 'cy': { - nativeName: 'Cymraeg', - }, - 'cy-GB': { - nativeName: 'Cymraeg', - }, - 'da': { - nativeName: 'Dansk', - }, - 'da-DK': { - nativeName: 'Dansk', - }, - 'de': { - nativeName: 'Deutsch', - }, - 'de-AT': { - nativeName: 'Deutsch (Österreich)', - }, - 'de-DE': { - nativeName: 'Deutsch (Deutschland)', - }, - 'de-CH': { - nativeName: 'Deutsch (Schweiz)', - }, - 'dsb': { - nativeName: 'Dolnoserbšćina', - }, - 'el': { - nativeName: 'Ελληνικά', - }, - 'el-GR': { - nativeName: 'Ελληνικά', - }, - 'en': { - nativeName: 'English', - }, - 'en-GB': { - nativeName: 'English (UK)', - }, - 'en-AU': { - nativeName: 'English (Australia)', - }, - 'en-CA': { - nativeName: 'English (Canada)', - }, - 'en-IE': { - nativeName: 'English (Ireland)', - }, - 'en-IN': { - nativeName: 'English (India)', - }, - 'en-PI': { - nativeName: 'English (Pirate)', - }, - 'en-SG': { - nativeName: 'English (Singapore)', - }, - 'en-UD': { - nativeName: 'English (Upside Down)', - }, - 'en-US': { - nativeName: 'English (US)', - }, - 'en-ZA': { - nativeName: 'English (South Africa)', - }, - 'en@pirate': { - nativeName: 'English (Pirate)', - }, - 'eo': { - nativeName: 'Esperanto', - }, - 'eo-EO': { - nativeName: 'Esperanto', - }, - 'es': { - nativeName: 'Español', - }, - 'es-AR': { - nativeName: 'Español (Argentine)', - }, - 'es-419': { - nativeName: 'Español (Latinoamérica)', - }, - 'es-CL': { - nativeName: 'Español (Chile)', - }, - 'es-CO': { - nativeName: 'Español (Colombia)', - }, - 'es-EC': { - nativeName: 'Español (Ecuador)', - }, - 'es-ES': { - nativeName: 'Español (España)', - }, - 'es-LA': { - nativeName: 'Español (Latinoamérica)', - }, - 'es-NI': { - nativeName: 'Español (Nicaragua)', - }, - 'es-MX': { - nativeName: 'Español (México)', - }, - 'es-US': { - nativeName: 'Español (Estados Unidos)', - }, - 'es-VE': { - nativeName: 'Español (Venezuela)', - }, - 'et': { - nativeName: 'eesti keel', - }, - 'et-EE': { - nativeName: 'Eesti (Estonia)', - }, - 'eu': { - nativeName: 'Euskara', - }, - 'eu-ES': { - nativeName: 'Euskara', - }, - 'fa': { - nativeName: 'فارسی', - }, - 'fa-IR': { - nativeName: 'فارسی', - }, - 'fb-LT': { - nativeName: 'Leet Speak', - }, - 'ff': { - nativeName: 'Fulah', - }, - 'fi': { - nativeName: 'Suomi', - }, - 'fi-FI': { - nativeName: 'Suomi', - }, - 'fo': { - nativeName: 'Føroyskt', - }, - 'fo-FO': { - nativeName: 'Føroyskt (Færeyjar)', - }, - 'fr': { - nativeName: 'Français', - }, - 'fr-CA': { - nativeName: 'Français (Canada)', - }, - 'fr-FR': { - nativeName: 'Français (France)', - }, - 'fr-BE': { - nativeName: 'Français (Belgique)', - }, - 'fr-CH': { - nativeName: 'Français (Suisse)', - }, - 'fy-NL': { - nativeName: 'Frysk', - }, - 'ga': { - nativeName: 'Gaeilge', - }, - 'ga-IE': { - nativeName: 'Gaeilge', - }, - 'gd': { - nativeName: 'Gàidhlig', - }, - 'gl': { - nativeName: 'Galego', - }, - 'gl-ES': { - nativeName: 'Galego', - }, - 'gn-PY': { - nativeName: 'Avañe\'ẽ', - }, - 'gu-IN': { - nativeName: 'ગુજરાતી', - }, - 'gv': { - nativeName: 'Gaelg', - }, - 'gx-GR': { - nativeName: 'Ἑλληνική ἀρχαία', - }, - 'he': { - nativeName: 'עברית', - }, - 'he-IL': { - nativeName: 'עברית', - }, - 'hi': { - nativeName: 'हिन्दी', - }, - 'hi-IN': { - nativeName: 'हिन्दी', - }, - 'hr': { - nativeName: 'Hrvatski', - }, - 'hr-HR': { - nativeName: 'Hrvatski', - }, - 'hsb': { - nativeName: 'Hornjoserbšćina', - }, - 'ht': { - nativeName: 'Kreyòl', - }, - 'hu': { - nativeName: 'Magyar', - }, - 'hu-HU': { - nativeName: 'Magyar', - }, - 'hy': { - nativeName: 'Հայերեն', - }, - 'hy-AM': { - nativeName: 'Հայերեն (Հայաստան)', - }, - 'id': { - nativeName: 'Bahasa Indonesia', - }, - 'id-ID': { - nativeName: 'Bahasa Indonesia', - }, - 'is': { - nativeName: 'Íslenska', - }, - 'is-IS': { - nativeName: 'Íslenska (Iceland)', - }, - 'it': { - nativeName: 'Italiano', - }, - 'it-IT': { - nativeName: 'Italiano', - }, - 'ja': { - nativeName: '日本語', - }, - 'ja-JP': { - nativeName: '日本語 (日本)', - }, - 'jv-ID': { - nativeName: 'Basa Jawa', - }, - 'ka-GE': { - nativeName: 'ქართული', - }, - 'kk-KZ': { - nativeName: 'Қазақша', - }, - 'km': { - nativeName: 'ភាសាខ្មែរ', - }, - 'kl': { - nativeName: 'kalaallisut', - }, - 'km-KH': { - nativeName: 'ភាសាខ្មែរ', - }, - 'kab': { - nativeName: 'Taqbaylit', - }, - 'kn': { - nativeName: 'ಕನ್ನಡ', - }, - 'kn-IN': { - nativeName: 'ಕನ್ನಡ (India)', - }, - 'ko': { - nativeName: '한국어', - }, - 'ko-KR': { - nativeName: '한국어 (한국)', - }, - 'ku-TR': { - nativeName: 'Kurdî', - }, - 'kw': { - nativeName: 'Kernewek', - }, - 'la': { - nativeName: 'Latin', - }, - 'la-VA': { - nativeName: 'Latin', - }, - 'lb': { - nativeName: 'Lëtzebuergesch', - }, - 'li-NL': { - nativeName: 'Lèmbörgs', - }, - 'lt': { - nativeName: 'Lietuvių', - }, - 'lt-LT': { - nativeName: 'Lietuvių', - }, - 'lv': { - nativeName: 'Latviešu', - }, - 'lv-LV': { - nativeName: 'Latviešu', - }, - 'mai': { - nativeName: 'मैथिली, মৈথিলী', - }, - 'mg-MG': { - nativeName: 'Malagasy', - }, - 'mk': { - nativeName: 'Македонски', - }, - 'mk-MK': { - nativeName: 'Македонски (Македонски)', - }, - 'ml': { - nativeName: 'മലയാളം', - }, - 'ml-IN': { - nativeName: 'മലയാളം', - }, - 'mn-MN': { - nativeName: 'Монгол', - }, - 'mr': { - nativeName: 'मराठी', - }, - 'mr-IN': { - nativeName: 'मराठी', - }, - 'ms': { - nativeName: 'Bahasa Melayu', - }, - 'ms-MY': { - nativeName: 'Bahasa Melayu', - }, - 'mt': { - nativeName: 'Malti', - }, - 'mt-MT': { - nativeName: 'Malti', - }, - 'my': { - nativeName: 'ဗမာစကာ', - }, - 'no': { - nativeName: 'Norsk', - }, - 'nb': { - nativeName: 'Norsk (bokmål)', - }, - 'nb-NO': { - nativeName: 'Norsk (bokmål)', - }, - 'ne': { - nativeName: 'नेपाली', - }, - 'ne-NP': { - nativeName: 'नेपाली', - }, - 'nl': { - nativeName: 'Nederlands', - }, - 'nl-BE': { - nativeName: 'Nederlands (België)', - }, - 'nl-NL': { - nativeName: 'Nederlands (Nederland)', - }, - 'nn-NO': { - nativeName: 'Norsk (nynorsk)', - }, - 'oc': { - nativeName: 'Occitan', - }, - 'or-IN': { - nativeName: 'ଓଡ଼ିଆ', - }, - 'pa': { - nativeName: 'ਪੰਜਾਬੀ', - }, - 'pa-IN': { - nativeName: 'ਪੰਜਾਬੀ (ਭਾਰਤ ਨੂੰ)', - }, - 'pl': { - nativeName: 'Polski', - }, - 'pl-PL': { - nativeName: 'Polski', - }, - 'ps-AF': { - nativeName: 'پښتو', - }, - 'pt': { - nativeName: 'Português', - }, - 'pt-BR': { - nativeName: 'Português (Brasil)', - }, - 'pt-PT': { - nativeName: 'Português (Portugal)', - }, - 'qu-PE': { - nativeName: 'Qhichwa', - }, - 'rm-CH': { - nativeName: 'Rumantsch', - }, - 'ro': { - nativeName: 'Română', - }, - 'ro-RO': { - nativeName: 'Română', - }, - 'ru': { - nativeName: 'Русский', - }, - 'ru-RU': { - nativeName: 'Русский', - }, - 'sa-IN': { - nativeName: 'संस्कृतम्', - }, - 'se-NO': { - nativeName: 'Davvisámegiella', - }, - 'sh': { - nativeName: 'српскохрватски', - }, - 'si-LK': { - nativeName: 'සිංහල', - }, - 'sk': { - nativeName: 'Slovenčina', - }, - 'sk-SK': { - nativeName: 'Slovenčina (Slovakia)', - }, - 'sl': { - nativeName: 'Slovenščina', - }, - 'sl-SI': { - nativeName: 'Slovenščina', - }, - 'so-SO': { - nativeName: 'Soomaaliga', - }, - 'sq': { - nativeName: 'Shqip', - }, - 'sq-AL': { - nativeName: 'Shqip', - }, - 'sr': { - nativeName: 'Српски', - }, - 'sr-RS': { - nativeName: 'Српски (Serbia)', - }, - 'su': { - nativeName: 'Basa Sunda', - }, - 'sv': { - nativeName: 'Svenska', - }, - 'sv-SE': { - nativeName: 'Svenska', - }, - 'sw': { - nativeName: 'Kiswahili', - }, - 'sw-KE': { - nativeName: 'Kiswahili', - }, - 'ta': { - nativeName: 'தமிழ்', - }, - 'ta-IN': { - nativeName: 'தமிழ்', - }, - 'te': { - nativeName: 'తెలుగు', - }, - 'te-IN': { - nativeName: 'తెలుగు', - }, - 'tg': { - nativeName: 'забо́ни тоҷикӣ́', - }, - 'tg-TJ': { - nativeName: 'тоҷикӣ', - }, - 'th': { - nativeName: 'ภาษาไทย', - }, - 'th-TH': { - nativeName: 'ภาษาไทย (ประเทศไทย)', - }, - 'fil': { - nativeName: 'Filipino', - }, - 'tlh': { - nativeName: 'tlhIngan-Hol', - }, - 'tr': { - nativeName: 'Türkçe', - }, - 'tr-TR': { - nativeName: 'Türkçe', - }, - 'tt-RU': { - nativeName: 'татарча', - }, - 'uk': { - nativeName: 'Українська', - }, - 'uk-UA': { - nativeName: 'Українська', - }, - 'ur': { - nativeName: 'اردو', - }, - 'ur-PK': { - nativeName: 'اردو', - }, - 'uz': { - nativeName: 'O\'zbek', - }, - 'uz-UZ': { - nativeName: 'O\'zbek', - }, - 'vi': { - nativeName: 'Tiếng Việt', - }, - 'vi-VN': { - nativeName: 'Tiếng Việt', - }, - 'xh-ZA': { - nativeName: 'isiXhosa', - }, - 'yi': { - nativeName: 'ייִדיש', - }, - 'yi-DE': { - nativeName: 'ייִדיש (German)', - }, - 'zh': { - nativeName: '中文', - }, - 'zh-Hans': { - nativeName: '中文简体', - }, - 'zh-Hant': { - nativeName: '中文繁體', - }, - 'zh-CN': { - nativeName: '中文(中国大陆)', - }, - 'zh-HK': { - nativeName: '中文(香港)', - }, - 'zh-SG': { - nativeName: '中文(新加坡)', - }, - 'zh-TW': { - nativeName: '中文(台灣)', - }, - 'zu-ZA': { - nativeName: 'isiZulu', - }, -}; diff --git a/packages/frontend/src/scripts/libopenmpt/LICENSE b/packages/frontend/src/scripts/libopenmpt/LICENSE deleted file mode 100644 index 2daefe981f..0000000000 --- a/packages/frontend/src/scripts/libopenmpt/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2004-2024, OpenMPT Project Developers and Contributors -Copyright (c) 1997-2003, Olivier Lapicque -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the OpenMPT project nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file diff --git a/packages/frontend/src/scripts/libopenmpt/libopenmpt.js b/packages/frontend/src/scripts/libopenmpt/libopenmpt.js deleted file mode 100644 index e2535529ce..0000000000 --- a/packages/frontend/src/scripts/libopenmpt/libopenmpt.js +++ /dev/null @@ -1,8 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -var Module=typeof libopenmpt!="undefined"?libopenmpt:{};var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}var fs,nodePath;if(typeof require==="function"){fs=require("fs");nodePath=require("path")}read_=(filename,binary)=>{filename=nodePath["normalize"](filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror)=>{filename=nodePath["normalize"](filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);if(typeof module!="undefined"){module["exports"]=Module}process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=(status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function lengthBytesUTF8(str){var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function keepRuntimeAlive(){return noExitRuntime}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile=new URL("./libopenmpt.wasm", import.meta.url).href;if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["pa"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["oc"];addOnInit(Module["asm"]["qa"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync();return{}}var tempDouble;var tempI64;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function ___assert_fail(condition,filename,line,func){abort("Assertion failed: "+UTF8ToString(condition)+", at: "+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"])}function ___cxa_allocate_exception(size){return _malloc(size+24)+24}var exceptionCaught=[];function exception_addRef(info){info.add_ref()}var uncaughtExceptionCount=0;function ___cxa_begin_catch(ptr){var info=new ExceptionInfo(ptr);if(!info.get_caught()){info.set_caught(true);uncaughtExceptionCount--}info.set_rethrown(false);exceptionCaught.push(info);exception_addRef(info);return info.get_exception_ptr()}var exceptionLast=0;function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=prev-1;return prev===1};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}function ___cxa_free_exception(ptr){return _free(new ExceptionInfo(ptr).ptr)}function getWasmTableEntry(funcPtr){return wasmTable.get(funcPtr)}function exception_decRef(info){if(info.release_ref()&&!info.get_rethrown()){var destructor=info.get_destructor();if(destructor){getWasmTableEntry(destructor)(info.excPtr)}___cxa_free_exception(info.excPtr)}}function ___cxa_end_catch(){_setThrew(0);var info=exceptionCaught.pop();exception_decRef(info);exceptionLast=0}function ___resumeException(ptr){if(!exceptionLast){exceptionLast=ptr}throw ptr}function ___cxa_find_matching_catch_17(){var thrown=exceptionLast;if(!thrown){setTempRet0(0);return 0}var info=new ExceptionInfo(thrown);info.set_adjusted_ptr(thrown);var thrownType=info.get_type();if(!thrownType){setTempRet0(0);return thrown}for(var i=0;i<arguments.length;i++){var caughtType=arguments[i];if(caughtType===0||caughtType===thrownType){break}var adjusted_ptr_addr=info.ptr+16;if(___cxa_can_catch(caughtType,thrownType,adjusted_ptr_addr)){setTempRet0(caughtType);return thrown}}setTempRet0(thrownType);return thrown}function ___cxa_find_matching_catch_2(){var thrown=exceptionLast;if(!thrown){setTempRet0(0);return 0}var info=new ExceptionInfo(thrown);info.set_adjusted_ptr(thrown);var thrownType=info.get_type();if(!thrownType){setTempRet0(0);return thrown}for(var i=0;i<arguments.length;i++){var caughtType=arguments[i];if(caughtType===0||caughtType===thrownType){break}var adjusted_ptr_addr=info.ptr+16;if(___cxa_can_catch(caughtType,thrownType,adjusted_ptr_addr)){setTempRet0(caughtType);return thrown}}setTempRet0(thrownType);return thrown}function ___cxa_find_matching_catch_3(){var thrown=exceptionLast;if(!thrown){setTempRet0(0);return 0}var info=new ExceptionInfo(thrown);info.set_adjusted_ptr(thrown);var thrownType=info.get_type();if(!thrownType){setTempRet0(0);return thrown}for(var i=0;i<arguments.length;i++){var caughtType=arguments[i];if(caughtType===0||caughtType===thrownType){break}var adjusted_ptr_addr=info.ptr+16;if(___cxa_can_catch(caughtType,thrownType,adjusted_ptr_addr)){setTempRet0(caughtType);return thrown}}setTempRet0(thrownType);return thrown}function ___cxa_find_matching_catch_4(){var thrown=exceptionLast;if(!thrown){setTempRet0(0);return 0}var info=new ExceptionInfo(thrown);info.set_adjusted_ptr(thrown);var thrownType=info.get_type();if(!thrownType){setTempRet0(0);return thrown}for(var i=0;i<arguments.length;i++){var caughtType=arguments[i];if(caughtType===0||caughtType===thrownType){break}var adjusted_ptr_addr=info.ptr+16;if(___cxa_can_catch(caughtType,thrownType,adjusted_ptr_addr)){setTempRet0(caughtType);return thrown}}setTempRet0(thrownType);return thrown}function ___cxa_find_matching_catch_6(){var thrown=exceptionLast;if(!thrown){setTempRet0(0);return 0}var info=new ExceptionInfo(thrown);info.set_adjusted_ptr(thrown);var thrownType=info.get_type();if(!thrownType){setTempRet0(0);return thrown}for(var i=0;i<arguments.length;i++){var caughtType=arguments[i];if(caughtType===0||caughtType===thrownType){break}var adjusted_ptr_addr=info.ptr+16;if(___cxa_can_catch(caughtType,thrownType,adjusted_ptr_addr)){setTempRet0(caughtType);return thrown}}setTempRet0(thrownType);return thrown}function ___cxa_rethrow(){var info=exceptionCaught.pop();if(!info){abort("no exception to throw")}var ptr=info.excPtr;if(!info.get_rethrown()){exceptionCaught.push(info);info.set_rethrown(true);info.set_caught(false);uncaughtExceptionCount++}exceptionLast=ptr;throw ptr}function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw ptr}function ___cxa_uncaught_exceptions(){return uncaughtExceptionCount}var nowIsMonotonic=true;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}function _abort(){abort("")}function _emscripten_date_now(){return Date.now()}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=()=>{var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else _emscripten_get_now=()=>performance.now();function getHeapMax(){return 2147483648}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i<str.length;++i){HEAP8[buffer++>>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return()=>{crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return()=>crypto_module["randomBytes"](1)[0]}catch(e){}}return()=>abort("randomDevice")}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start<arr.length;start++){if(arr[start]!=="")break}var end=arr.length-1;for(;end>=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i<length;i++){if(fromParts[i]!==toParts[i]){samePartsLength=i;break}}var outputParts=[];for(var i=samePartsLength;i<fromParts.length;i++){outputParts.push("..")}outputParts=outputParts.concat(toParts.slice(samePartsLength));return outputParts.join("/")}};function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.fsync(stream.tty)},fsync:function(stream){stream.tty.ops.fsync(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i<length;i++){var result;try{result=stream.tty.ops.get_char(stream.tty)}catch(e){throw new FS.ErrnoError(29)}if(result===undefined&&bytesRead===0){throw new FS.ErrnoError(6)}if(result===null||result===undefined)break;bytesRead++;buffer[offset+i]=result}if(bytesRead){stream.node.timestamp=Date.now()}return bytesRead},write:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.put_char){throw new FS.ErrnoError(60)}try{for(var i=0;i<length;i++){stream.tty.ops.put_char(stream.tty,buffer[offset+i])}}catch(e){throw new FS.ErrnoError(29)}if(length){stream.node.timestamp=Date.now()}return i}},default_tty_ops:{get_char:function(tty){if(!tty.input.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;try{bytesRead=fs.readSync(process.stdin.fd,buf,0,BUFSIZE,-1)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity<CAPACITY_DOUBLING_MAX?2:1.125)>>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i<size;i++)buffer[offset+i]=contents[position+i]}return size},write:function(stream,buffer,offset,length,position,canOwn){if(buffer.buffer===HEAP8.buffer){canOwn=false}if(!length)return 0;var node=stream.node;node.timestamp=Date.now();if(buffer.subarray&&(!node.contents||node.contents.subarray)){if(canOwn){node.contents=buffer.subarray(offset,offset+length);node.usedBytes=length;return length}else if(node.usedBytes===0&&position===0){node.contents=buffer.slice(offset,offset+length);node.usedBytes=length;return length}else if(position+length<=node.usedBytes){node.contents.set(buffer.subarray(offset,offset+length),position);return length}}MEMFS.expandFileStorage(node,position+length);if(node.contents.subarray&&buffer.subarray){node.contents.set(buffer.subarray(offset,offset+length),position)}else{for(var i=0;i<length;i++){node.contents[position+i]=buffer[offset+i]}}node.usedBytes=Math.max(node.usedBytes,position+length);return length},llseek:function(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.usedBytes}}if(position<0){throw new FS.ErrnoError(28)}return position},allocate:function(stream,offset,length){MEMFS.expandFileStorage(stream.node,offset+length);stream.node.usedBytes=Math.max(stream.node.usedBytes,offset+length)},mmap:function(stream,length,position,prot,flags){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}var ptr;var allocated;var contents=stream.node.contents;if(!(flags&2)&&contents.buffer===buffer){allocated=false;ptr=contents.byteOffset}else{if(position>0||position+length<contents.length){if(contents.subarray){contents=contents.subarray(position,position+length)}else{contents=Array.prototype.slice.call(contents,position,position+length)}}allocated=true;ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}HEAP8.set(contents,ptr)}return{ptr:ptr,allocated:allocated}},msync:function(stream,buffer,offset,length,mmapFlags){MEMFS.stream_ops.write(stream,buffer,0,length,offset,false);return 0}}};function asyncLoad(url,onload,onerror,noRunDep){var dep=!noRunDep?getUniqueRunDependency("al "+url):"";readAsync(url,arrayBuffer=>{assert(arrayBuffer,'Loading data file "'+url+'" failed (no arrayBuffer).');onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw'Loading data file "'+url+'" failed.'}});if(dep)addRunDependency(dep)}var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path,opts={})=>{path=PATH_FS.resolve(FS.cwd(),path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(p=>!!p),false);var current=FS.root;var current_path="/";for(var i=0;i<parts.length;i++){var islast=i===parts.length-1;if(islast&&opts.parent){break}current=FS.lookupNode(current,parts[i]);current_path=PATH.join2(current_path,parts[i]);if(FS.isMountpoint(current)){if(!islast||islast&&opts.follow_mount){current=current.mounted.root}}if(!islast||opts.follow){var count=0;while(FS.isLink(current.mode)){var link=FS.readlink(current_path);current_path=PATH_FS.resolve(PATH.dirname(current_path),link);var lookup=FS.lookupPath(current_path,{recurse_count:opts.recurse_count+1});current=lookup.node;if(count++>40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i<name.length;i++){hash=(hash<<5)-hash+name.charCodeAt(i)|0}return(parentid+hash>>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:str=>{var flags=FS.flagModes[str];if(typeof flags=="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:(fd_start=0,fd_end=FS.MAX_OPEN_FDS)=>{for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd_start,fd_end)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i<dirs.length;++i){if(!dirs[i])continue;d+="/"+dirs[i];try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev:(path,mode,dev)=>{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device=getRandomDevice();FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack="<generic error, no stack>"})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i<FS.streams.length;i++){var stream=FS.streams[i];if(!stream){continue}FS.close(stream)}},getMode:(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i<len;++i)arr[i]=data.charCodeAt(i);data=arr}FS.chmod(node,mode|146);var stream=FS.open(node,577);FS.write(stream,data,0,data.length,0,canOwn);FS.close(stream);FS.chmod(node,mode)}return node},createDevice:(parent,name,input,output)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i<length;i++){var result;try{result=input()}catch(e){throw new FS.ErrnoError(29)}if(result===undefined&&bytesRead===0){throw new FS.ErrnoError(6)}if(result===null||result===undefined)break;bytesRead++;buffer[offset+i]=result}if(bytesRead){stream.node.timestamp=Date.now()}return bytesRead},write:(stream,buffer,offset,length,pos)=>{for(var i=0;i<length;i++){try{output(buffer[offset+i])}catch(e){throw new FS.ErrnoError(29)}}if(length){stream.node.timestamp=Date.now()}return i}});return FS.mkdev(path,mode,dev)},forceLoadFile:obj=>{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i<size;i++){buffer[offset+i]=contents[position+i]}}else{for(var i=0;i<size;i++){buffer[offset+i]=contents.get(position+i)}}return size}stream_ops.read=(stream,buffer,offset,length,position)=>{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node},createPreloadedFile:(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(Browser.handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}},indexedDB:()=>{return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:()=>{return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=()=>{out("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=()=>{var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=()=>{ok++;if(ok+fail==total)finish()};putRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=()=>{var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var getRequest=files.get(path);getRequest.onsuccess=()=>{if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAPU32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;tempI64=[Math.floor(stat.atime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.atime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+56>>2]=tempI64[0],HEAP32[buf+60>>2]=tempI64[1];HEAPU32[buf+64>>2]=0;tempI64=[Math.floor(stat.mtime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.mtime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+72>>2]=tempI64[0],HEAP32[buf+76>>2]=tempI64[1];HEAPU32[buf+80>>2]=0;tempI64=[Math.floor(stat.ctime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.ctime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+88>>2]=tempI64[0],HEAP32[buf+92>>2]=tempI64[1];HEAPU32[buf+96>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+104>>2]=tempI64[0],HEAP32[buf+108>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr<len)break}return ret}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doReadv(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var offset=convertI32PairToI53Checked(offset_low,offset_high);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _getentropy(buffer,size){if(!_getentropy.randomDevice){_getentropy.randomDevice=getRandomDevice()}for(var i=0;i<size;i++){HEAP8[buffer+i>>0]=_getentropy.randomDevice()}return 0}function _llvm_eh_typeid_for(type){return type}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length<digits){str=character[0]+str}return str}function leadingNulls(value,digits){return leadingSomething(value,digits,"0")}function compareByDay(date1,date2){function sgn(value){return value<0?-1:value>0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}return thisDate.getFullYear()}return thisDate.getFullYear()-1}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}return"PM"},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&__isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!__isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _strftime_l(s,maxsize,format,tm){return _strftime(s,maxsize,format,tm)}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();var asmLibraryArg={"p":___assert_fail,"n":___cxa_allocate_exception,"l":___cxa_begin_catch,"m":___cxa_end_catch,"ca":___cxa_find_matching_catch_17,"a":___cxa_find_matching_catch_2,"e":___cxa_find_matching_catch_3,"w":___cxa_find_matching_catch_4,"ba":___cxa_find_matching_catch_6,"o":___cxa_free_exception,"J":___cxa_rethrow,"v":___cxa_throw,"la":___cxa_uncaught_exceptions,"g":___resumeException,"R":__emscripten_get_now_is_monotonic,"H":_abort,"S":_emscripten_date_now,"Q":_emscripten_get_now,"ma":_emscripten_resize_heap,"ia":_environ_get,"ja":_environ_sizes_get,"oa":_fd_close,"ka":_fd_read,"_":_fd_seek,"na":_fd_write,"ga":_getentropy,"G":invoke_di,"E":invoke_dii,"K":invoke_diii,"L":invoke_diiii,"P":invoke_fi,"ea":invoke_fii,"t":invoke_i,"d":invoke_ii,"fa":invoke_iid,"b":invoke_iii,"M":invoke_iiifii,"i":invoke_iiii,"aa":invoke_iiiidd,"q":invoke_iiiii,"$":invoke_iiiiid,"A":invoke_iiiiii,"x":invoke_iiiiiii,"I":invoke_iiiiiiii,"C":invoke_iiiiiiiiiiii,"T":invoke_iiij,"Z":invoke_iiji,"U":invoke_j,"W":invoke_jiiii,"j":invoke_v,"k":invoke_vi,"D":invoke_vid,"c":invoke_vii,"F":invoke_viid,"N":invoke_viif,"f":invoke_viii,"da":invoke_viiidi,"h":invoke_viiii,"r":invoke_viiiii,"z":invoke_viiiiii,"u":invoke_viiiiiii,"O":invoke_viiiiiiii,"y":invoke_viiiiiiiiii,"B":invoke_viiiiiiiiiiiiiii,"X":invoke_viij,"V":invoke_viiji,"Y":invoke_vij,"s":_llvm_eh_typeid_for,"ha":_strftime_l};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["qa"]).apply(null,arguments)};var _openmpt_get_library_version=Module["_openmpt_get_library_version"]=function(){return(_openmpt_get_library_version=Module["_openmpt_get_library_version"]=Module["asm"]["ra"]).apply(null,arguments)};var _openmpt_get_core_version=Module["_openmpt_get_core_version"]=function(){return(_openmpt_get_core_version=Module["_openmpt_get_core_version"]=Module["asm"]["sa"]).apply(null,arguments)};var _openmpt_free_string=Module["_openmpt_free_string"]=function(){return(_openmpt_free_string=Module["_openmpt_free_string"]=Module["asm"]["ta"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["ua"]).apply(null,arguments)};var _openmpt_get_string=Module["_openmpt_get_string"]=function(){return(_openmpt_get_string=Module["_openmpt_get_string"]=Module["asm"]["va"]).apply(null,arguments)};var _openmpt_get_supported_extensions=Module["_openmpt_get_supported_extensions"]=function(){return(_openmpt_get_supported_extensions=Module["_openmpt_get_supported_extensions"]=Module["asm"]["wa"]).apply(null,arguments)};var _openmpt_is_extension_supported=Module["_openmpt_is_extension_supported"]=function(){return(_openmpt_is_extension_supported=Module["_openmpt_is_extension_supported"]=Module["asm"]["xa"]).apply(null,arguments)};var _openmpt_log_func_default=Module["_openmpt_log_func_default"]=function(){return(_openmpt_log_func_default=Module["_openmpt_log_func_default"]=Module["asm"]["ya"]).apply(null,arguments)};var _openmpt_log_func_silent=Module["_openmpt_log_func_silent"]=function(){return(_openmpt_log_func_silent=Module["_openmpt_log_func_silent"]=Module["asm"]["za"]).apply(null,arguments)};var _openmpt_error_is_transient=Module["_openmpt_error_is_transient"]=function(){return(_openmpt_error_is_transient=Module["_openmpt_error_is_transient"]=Module["asm"]["Aa"]).apply(null,arguments)};var _openmpt_error_string=Module["_openmpt_error_string"]=function(){return(_openmpt_error_string=Module["_openmpt_error_string"]=Module["asm"]["Ba"]).apply(null,arguments)};var _openmpt_error_func_default=Module["_openmpt_error_func_default"]=function(){return(_openmpt_error_func_default=Module["_openmpt_error_func_default"]=Module["asm"]["Ca"]).apply(null,arguments)};var _openmpt_error_func_log=Module["_openmpt_error_func_log"]=function(){return(_openmpt_error_func_log=Module["_openmpt_error_func_log"]=Module["asm"]["Da"]).apply(null,arguments)};var _openmpt_error_func_store=Module["_openmpt_error_func_store"]=function(){return(_openmpt_error_func_store=Module["_openmpt_error_func_store"]=Module["asm"]["Ea"]).apply(null,arguments)};var _openmpt_error_func_ignore=Module["_openmpt_error_func_ignore"]=function(){return(_openmpt_error_func_ignore=Module["_openmpt_error_func_ignore"]=Module["asm"]["Fa"]).apply(null,arguments)};var _openmpt_error_func_errno=Module["_openmpt_error_func_errno"]=function(){return(_openmpt_error_func_errno=Module["_openmpt_error_func_errno"]=Module["asm"]["Ga"]).apply(null,arguments)};var _openmpt_error_func_errno_userdata=Module["_openmpt_error_func_errno_userdata"]=function(){return(_openmpt_error_func_errno_userdata=Module["_openmpt_error_func_errno_userdata"]=Module["asm"]["Ha"]).apply(null,arguments)};var _openmpt_could_open_probability=Module["_openmpt_could_open_probability"]=function(){return(_openmpt_could_open_probability=Module["_openmpt_could_open_probability"]=Module["asm"]["Ia"]).apply(null,arguments)};var _openmpt_could_open_probability2=Module["_openmpt_could_open_probability2"]=function(){return(_openmpt_could_open_probability2=Module["_openmpt_could_open_probability2"]=Module["asm"]["Ja"]).apply(null,arguments)};var _openmpt_could_open_propability=Module["_openmpt_could_open_propability"]=function(){return(_openmpt_could_open_propability=Module["_openmpt_could_open_propability"]=Module["asm"]["Ka"]).apply(null,arguments)};var _openmpt_probe_file_header_get_recommended_size=Module["_openmpt_probe_file_header_get_recommended_size"]=function(){return(_openmpt_probe_file_header_get_recommended_size=Module["_openmpt_probe_file_header_get_recommended_size"]=Module["asm"]["La"]).apply(null,arguments)};var _openmpt_probe_file_header=Module["_openmpt_probe_file_header"]=function(){return(_openmpt_probe_file_header=Module["_openmpt_probe_file_header"]=Module["asm"]["Ma"]).apply(null,arguments)};var _openmpt_probe_file_header_without_filesize=Module["_openmpt_probe_file_header_without_filesize"]=function(){return(_openmpt_probe_file_header_without_filesize=Module["_openmpt_probe_file_header_without_filesize"]=Module["asm"]["Na"]).apply(null,arguments)};var _openmpt_probe_file_header_from_stream=Module["_openmpt_probe_file_header_from_stream"]=function(){return(_openmpt_probe_file_header_from_stream=Module["_openmpt_probe_file_header_from_stream"]=Module["asm"]["Oa"]).apply(null,arguments)};var _openmpt_module_create=Module["_openmpt_module_create"]=function(){return(_openmpt_module_create=Module["_openmpt_module_create"]=Module["asm"]["Pa"]).apply(null,arguments)};var _openmpt_module_create2=Module["_openmpt_module_create2"]=function(){return(_openmpt_module_create2=Module["_openmpt_module_create2"]=Module["asm"]["Qa"]).apply(null,arguments)};var _openmpt_module_create_from_memory=Module["_openmpt_module_create_from_memory"]=function(){return(_openmpt_module_create_from_memory=Module["_openmpt_module_create_from_memory"]=Module["asm"]["Ra"]).apply(null,arguments)};var _openmpt_module_create_from_memory2=Module["_openmpt_module_create_from_memory2"]=function(){return(_openmpt_module_create_from_memory2=Module["_openmpt_module_create_from_memory2"]=Module["asm"]["Sa"]).apply(null,arguments)};var _openmpt_module_destroy=Module["_openmpt_module_destroy"]=function(){return(_openmpt_module_destroy=Module["_openmpt_module_destroy"]=Module["asm"]["Ta"]).apply(null,arguments)};var _openmpt_module_set_log_func=Module["_openmpt_module_set_log_func"]=function(){return(_openmpt_module_set_log_func=Module["_openmpt_module_set_log_func"]=Module["asm"]["Ua"]).apply(null,arguments)};var _openmpt_module_set_error_func=Module["_openmpt_module_set_error_func"]=function(){return(_openmpt_module_set_error_func=Module["_openmpt_module_set_error_func"]=Module["asm"]["Va"]).apply(null,arguments)};var _openmpt_module_error_get_last=Module["_openmpt_module_error_get_last"]=function(){return(_openmpt_module_error_get_last=Module["_openmpt_module_error_get_last"]=Module["asm"]["Wa"]).apply(null,arguments)};var _openmpt_module_error_get_last_message=Module["_openmpt_module_error_get_last_message"]=function(){return(_openmpt_module_error_get_last_message=Module["_openmpt_module_error_get_last_message"]=Module["asm"]["Xa"]).apply(null,arguments)};var _openmpt_module_error_set_last=Module["_openmpt_module_error_set_last"]=function(){return(_openmpt_module_error_set_last=Module["_openmpt_module_error_set_last"]=Module["asm"]["Ya"]).apply(null,arguments)};var _openmpt_module_error_clear=Module["_openmpt_module_error_clear"]=function(){return(_openmpt_module_error_clear=Module["_openmpt_module_error_clear"]=Module["asm"]["Za"]).apply(null,arguments)};var _openmpt_module_select_subsong=Module["_openmpt_module_select_subsong"]=function(){return(_openmpt_module_select_subsong=Module["_openmpt_module_select_subsong"]=Module["asm"]["_a"]).apply(null,arguments)};var _openmpt_module_get_selected_subsong=Module["_openmpt_module_get_selected_subsong"]=function(){return(_openmpt_module_get_selected_subsong=Module["_openmpt_module_get_selected_subsong"]=Module["asm"]["$a"]).apply(null,arguments)};var _openmpt_module_set_repeat_count=Module["_openmpt_module_set_repeat_count"]=function(){return(_openmpt_module_set_repeat_count=Module["_openmpt_module_set_repeat_count"]=Module["asm"]["ab"]).apply(null,arguments)};var _openmpt_module_get_repeat_count=Module["_openmpt_module_get_repeat_count"]=function(){return(_openmpt_module_get_repeat_count=Module["_openmpt_module_get_repeat_count"]=Module["asm"]["bb"]).apply(null,arguments)};var _openmpt_module_get_duration_seconds=Module["_openmpt_module_get_duration_seconds"]=function(){return(_openmpt_module_get_duration_seconds=Module["_openmpt_module_get_duration_seconds"]=Module["asm"]["cb"]).apply(null,arguments)};var _openmpt_module_set_position_seconds=Module["_openmpt_module_set_position_seconds"]=function(){return(_openmpt_module_set_position_seconds=Module["_openmpt_module_set_position_seconds"]=Module["asm"]["db"]).apply(null,arguments)};var _openmpt_module_get_position_seconds=Module["_openmpt_module_get_position_seconds"]=function(){return(_openmpt_module_get_position_seconds=Module["_openmpt_module_get_position_seconds"]=Module["asm"]["eb"]).apply(null,arguments)};var _openmpt_module_set_position_order_row=Module["_openmpt_module_set_position_order_row"]=function(){return(_openmpt_module_set_position_order_row=Module["_openmpt_module_set_position_order_row"]=Module["asm"]["fb"]).apply(null,arguments)};var _openmpt_module_get_render_param=Module["_openmpt_module_get_render_param"]=function(){return(_openmpt_module_get_render_param=Module["_openmpt_module_get_render_param"]=Module["asm"]["gb"]).apply(null,arguments)};var _openmpt_module_set_render_param=Module["_openmpt_module_set_render_param"]=function(){return(_openmpt_module_set_render_param=Module["_openmpt_module_set_render_param"]=Module["asm"]["hb"]).apply(null,arguments)};var _openmpt_module_read_mono=Module["_openmpt_module_read_mono"]=function(){return(_openmpt_module_read_mono=Module["_openmpt_module_read_mono"]=Module["asm"]["ib"]).apply(null,arguments)};var _openmpt_module_read_stereo=Module["_openmpt_module_read_stereo"]=function(){return(_openmpt_module_read_stereo=Module["_openmpt_module_read_stereo"]=Module["asm"]["jb"]).apply(null,arguments)};var _openmpt_module_read_quad=Module["_openmpt_module_read_quad"]=function(){return(_openmpt_module_read_quad=Module["_openmpt_module_read_quad"]=Module["asm"]["kb"]).apply(null,arguments)};var _openmpt_module_read_float_mono=Module["_openmpt_module_read_float_mono"]=function(){return(_openmpt_module_read_float_mono=Module["_openmpt_module_read_float_mono"]=Module["asm"]["lb"]).apply(null,arguments)};var _openmpt_module_read_float_stereo=Module["_openmpt_module_read_float_stereo"]=function(){return(_openmpt_module_read_float_stereo=Module["_openmpt_module_read_float_stereo"]=Module["asm"]["mb"]).apply(null,arguments)};var _openmpt_module_read_float_quad=Module["_openmpt_module_read_float_quad"]=function(){return(_openmpt_module_read_float_quad=Module["_openmpt_module_read_float_quad"]=Module["asm"]["nb"]).apply(null,arguments)};var _openmpt_module_read_interleaved_stereo=Module["_openmpt_module_read_interleaved_stereo"]=function(){return(_openmpt_module_read_interleaved_stereo=Module["_openmpt_module_read_interleaved_stereo"]=Module["asm"]["ob"]).apply(null,arguments)};var _openmpt_module_read_interleaved_quad=Module["_openmpt_module_read_interleaved_quad"]=function(){return(_openmpt_module_read_interleaved_quad=Module["_openmpt_module_read_interleaved_quad"]=Module["asm"]["pb"]).apply(null,arguments)};var _openmpt_module_read_interleaved_float_stereo=Module["_openmpt_module_read_interleaved_float_stereo"]=function(){return(_openmpt_module_read_interleaved_float_stereo=Module["_openmpt_module_read_interleaved_float_stereo"]=Module["asm"]["qb"]).apply(null,arguments)};var _openmpt_module_read_interleaved_float_quad=Module["_openmpt_module_read_interleaved_float_quad"]=function(){return(_openmpt_module_read_interleaved_float_quad=Module["_openmpt_module_read_interleaved_float_quad"]=Module["asm"]["rb"]).apply(null,arguments)};var _openmpt_module_get_metadata_keys=Module["_openmpt_module_get_metadata_keys"]=function(){return(_openmpt_module_get_metadata_keys=Module["_openmpt_module_get_metadata_keys"]=Module["asm"]["sb"]).apply(null,arguments)};var _openmpt_module_get_metadata=Module["_openmpt_module_get_metadata"]=function(){return(_openmpt_module_get_metadata=Module["_openmpt_module_get_metadata"]=Module["asm"]["tb"]).apply(null,arguments)};var _openmpt_module_get_current_estimated_bpm=Module["_openmpt_module_get_current_estimated_bpm"]=function(){return(_openmpt_module_get_current_estimated_bpm=Module["_openmpt_module_get_current_estimated_bpm"]=Module["asm"]["ub"]).apply(null,arguments)};var _openmpt_module_get_current_speed=Module["_openmpt_module_get_current_speed"]=function(){return(_openmpt_module_get_current_speed=Module["_openmpt_module_get_current_speed"]=Module["asm"]["vb"]).apply(null,arguments)};var _openmpt_module_get_current_tempo=Module["_openmpt_module_get_current_tempo"]=function(){return(_openmpt_module_get_current_tempo=Module["_openmpt_module_get_current_tempo"]=Module["asm"]["wb"]).apply(null,arguments)};var _openmpt_module_get_current_order=Module["_openmpt_module_get_current_order"]=function(){return(_openmpt_module_get_current_order=Module["_openmpt_module_get_current_order"]=Module["asm"]["xb"]).apply(null,arguments)};var _openmpt_module_get_current_pattern=Module["_openmpt_module_get_current_pattern"]=function(){return(_openmpt_module_get_current_pattern=Module["_openmpt_module_get_current_pattern"]=Module["asm"]["yb"]).apply(null,arguments)};var _openmpt_module_get_current_row=Module["_openmpt_module_get_current_row"]=function(){return(_openmpt_module_get_current_row=Module["_openmpt_module_get_current_row"]=Module["asm"]["zb"]).apply(null,arguments)};var _openmpt_module_get_current_playing_channels=Module["_openmpt_module_get_current_playing_channels"]=function(){return(_openmpt_module_get_current_playing_channels=Module["_openmpt_module_get_current_playing_channels"]=Module["asm"]["Ab"]).apply(null,arguments)};var _openmpt_module_get_current_channel_vu_mono=Module["_openmpt_module_get_current_channel_vu_mono"]=function(){return(_openmpt_module_get_current_channel_vu_mono=Module["_openmpt_module_get_current_channel_vu_mono"]=Module["asm"]["Bb"]).apply(null,arguments)};var _openmpt_module_get_current_channel_vu_left=Module["_openmpt_module_get_current_channel_vu_left"]=function(){return(_openmpt_module_get_current_channel_vu_left=Module["_openmpt_module_get_current_channel_vu_left"]=Module["asm"]["Cb"]).apply(null,arguments)};var _openmpt_module_get_current_channel_vu_right=Module["_openmpt_module_get_current_channel_vu_right"]=function(){return(_openmpt_module_get_current_channel_vu_right=Module["_openmpt_module_get_current_channel_vu_right"]=Module["asm"]["Db"]).apply(null,arguments)};var _openmpt_module_get_current_channel_vu_rear_left=Module["_openmpt_module_get_current_channel_vu_rear_left"]=function(){return(_openmpt_module_get_current_channel_vu_rear_left=Module["_openmpt_module_get_current_channel_vu_rear_left"]=Module["asm"]["Eb"]).apply(null,arguments)};var _openmpt_module_get_current_channel_vu_rear_right=Module["_openmpt_module_get_current_channel_vu_rear_right"]=function(){return(_openmpt_module_get_current_channel_vu_rear_right=Module["_openmpt_module_get_current_channel_vu_rear_right"]=Module["asm"]["Fb"]).apply(null,arguments)};var _openmpt_module_get_num_subsongs=Module["_openmpt_module_get_num_subsongs"]=function(){return(_openmpt_module_get_num_subsongs=Module["_openmpt_module_get_num_subsongs"]=Module["asm"]["Gb"]).apply(null,arguments)};var _openmpt_module_get_num_channels=Module["_openmpt_module_get_num_channels"]=function(){return(_openmpt_module_get_num_channels=Module["_openmpt_module_get_num_channels"]=Module["asm"]["Hb"]).apply(null,arguments)};var _openmpt_module_get_num_orders=Module["_openmpt_module_get_num_orders"]=function(){return(_openmpt_module_get_num_orders=Module["_openmpt_module_get_num_orders"]=Module["asm"]["Ib"]).apply(null,arguments)};var _openmpt_module_get_num_patterns=Module["_openmpt_module_get_num_patterns"]=function(){return(_openmpt_module_get_num_patterns=Module["_openmpt_module_get_num_patterns"]=Module["asm"]["Jb"]).apply(null,arguments)};var _openmpt_module_get_num_instruments=Module["_openmpt_module_get_num_instruments"]=function(){return(_openmpt_module_get_num_instruments=Module["_openmpt_module_get_num_instruments"]=Module["asm"]["Kb"]).apply(null,arguments)};var _openmpt_module_get_num_samples=Module["_openmpt_module_get_num_samples"]=function(){return(_openmpt_module_get_num_samples=Module["_openmpt_module_get_num_samples"]=Module["asm"]["Lb"]).apply(null,arguments)};var _openmpt_module_get_subsong_name=Module["_openmpt_module_get_subsong_name"]=function(){return(_openmpt_module_get_subsong_name=Module["_openmpt_module_get_subsong_name"]=Module["asm"]["Mb"]).apply(null,arguments)};var _openmpt_module_get_channel_name=Module["_openmpt_module_get_channel_name"]=function(){return(_openmpt_module_get_channel_name=Module["_openmpt_module_get_channel_name"]=Module["asm"]["Nb"]).apply(null,arguments)};var _openmpt_module_get_order_name=Module["_openmpt_module_get_order_name"]=function(){return(_openmpt_module_get_order_name=Module["_openmpt_module_get_order_name"]=Module["asm"]["Ob"]).apply(null,arguments)};var _openmpt_module_get_pattern_name=Module["_openmpt_module_get_pattern_name"]=function(){return(_openmpt_module_get_pattern_name=Module["_openmpt_module_get_pattern_name"]=Module["asm"]["Pb"]).apply(null,arguments)};var _openmpt_module_get_instrument_name=Module["_openmpt_module_get_instrument_name"]=function(){return(_openmpt_module_get_instrument_name=Module["_openmpt_module_get_instrument_name"]=Module["asm"]["Qb"]).apply(null,arguments)};var _openmpt_module_get_sample_name=Module["_openmpt_module_get_sample_name"]=function(){return(_openmpt_module_get_sample_name=Module["_openmpt_module_get_sample_name"]=Module["asm"]["Rb"]).apply(null,arguments)};var _openmpt_module_get_order_pattern=Module["_openmpt_module_get_order_pattern"]=function(){return(_openmpt_module_get_order_pattern=Module["_openmpt_module_get_order_pattern"]=Module["asm"]["Sb"]).apply(null,arguments)};var _openmpt_module_get_pattern_num_rows=Module["_openmpt_module_get_pattern_num_rows"]=function(){return(_openmpt_module_get_pattern_num_rows=Module["_openmpt_module_get_pattern_num_rows"]=Module["asm"]["Tb"]).apply(null,arguments)};var _openmpt_module_get_pattern_row_channel_command=Module["_openmpt_module_get_pattern_row_channel_command"]=function(){return(_openmpt_module_get_pattern_row_channel_command=Module["_openmpt_module_get_pattern_row_channel_command"]=Module["asm"]["Ub"]).apply(null,arguments)};var _openmpt_module_format_pattern_row_channel_command=Module["_openmpt_module_format_pattern_row_channel_command"]=function(){return(_openmpt_module_format_pattern_row_channel_command=Module["_openmpt_module_format_pattern_row_channel_command"]=Module["asm"]["Vb"]).apply(null,arguments)};var _openmpt_module_highlight_pattern_row_channel_command=Module["_openmpt_module_highlight_pattern_row_channel_command"]=function(){return(_openmpt_module_highlight_pattern_row_channel_command=Module["_openmpt_module_highlight_pattern_row_channel_command"]=Module["asm"]["Wb"]).apply(null,arguments)};var _openmpt_module_format_pattern_row_channel=Module["_openmpt_module_format_pattern_row_channel"]=function(){return(_openmpt_module_format_pattern_row_channel=Module["_openmpt_module_format_pattern_row_channel"]=Module["asm"]["Xb"]).apply(null,arguments)};var _openmpt_module_highlight_pattern_row_channel=Module["_openmpt_module_highlight_pattern_row_channel"]=function(){return(_openmpt_module_highlight_pattern_row_channel=Module["_openmpt_module_highlight_pattern_row_channel"]=Module["asm"]["Yb"]).apply(null,arguments)};var _openmpt_module_get_ctls=Module["_openmpt_module_get_ctls"]=function(){return(_openmpt_module_get_ctls=Module["_openmpt_module_get_ctls"]=Module["asm"]["Zb"]).apply(null,arguments)};var _openmpt_module_ctl_get=Module["_openmpt_module_ctl_get"]=function(){return(_openmpt_module_ctl_get=Module["_openmpt_module_ctl_get"]=Module["asm"]["_b"]).apply(null,arguments)};var _openmpt_module_ctl_get_boolean=Module["_openmpt_module_ctl_get_boolean"]=function(){return(_openmpt_module_ctl_get_boolean=Module["_openmpt_module_ctl_get_boolean"]=Module["asm"]["$b"]).apply(null,arguments)};var _openmpt_module_ctl_get_integer=Module["_openmpt_module_ctl_get_integer"]=function(){return(_openmpt_module_ctl_get_integer=Module["_openmpt_module_ctl_get_integer"]=Module["asm"]["ac"]).apply(null,arguments)};var _openmpt_module_ctl_get_floatingpoint=Module["_openmpt_module_ctl_get_floatingpoint"]=function(){return(_openmpt_module_ctl_get_floatingpoint=Module["_openmpt_module_ctl_get_floatingpoint"]=Module["asm"]["bc"]).apply(null,arguments)};var _openmpt_module_ctl_get_text=Module["_openmpt_module_ctl_get_text"]=function(){return(_openmpt_module_ctl_get_text=Module["_openmpt_module_ctl_get_text"]=Module["asm"]["cc"]).apply(null,arguments)};var _openmpt_module_ctl_set=Module["_openmpt_module_ctl_set"]=function(){return(_openmpt_module_ctl_set=Module["_openmpt_module_ctl_set"]=Module["asm"]["dc"]).apply(null,arguments)};var _openmpt_module_ctl_set_boolean=Module["_openmpt_module_ctl_set_boolean"]=function(){return(_openmpt_module_ctl_set_boolean=Module["_openmpt_module_ctl_set_boolean"]=Module["asm"]["ec"]).apply(null,arguments)};var _openmpt_module_ctl_set_integer=Module["_openmpt_module_ctl_set_integer"]=function(){return(_openmpt_module_ctl_set_integer=Module["_openmpt_module_ctl_set_integer"]=Module["asm"]["fc"]).apply(null,arguments)};var _openmpt_module_ctl_set_floatingpoint=Module["_openmpt_module_ctl_set_floatingpoint"]=function(){return(_openmpt_module_ctl_set_floatingpoint=Module["_openmpt_module_ctl_set_floatingpoint"]=Module["asm"]["gc"]).apply(null,arguments)};var _openmpt_module_ctl_set_text=Module["_openmpt_module_ctl_set_text"]=function(){return(_openmpt_module_ctl_set_text=Module["_openmpt_module_ctl_set_text"]=Module["asm"]["hc"]).apply(null,arguments)};var _openmpt_module_ext_create=Module["_openmpt_module_ext_create"]=function(){return(_openmpt_module_ext_create=Module["_openmpt_module_ext_create"]=Module["asm"]["ic"]).apply(null,arguments)};var _openmpt_module_ext_create_from_memory=Module["_openmpt_module_ext_create_from_memory"]=function(){return(_openmpt_module_ext_create_from_memory=Module["_openmpt_module_ext_create_from_memory"]=Module["asm"]["jc"]).apply(null,arguments)};var _openmpt_module_ext_destroy=Module["_openmpt_module_ext_destroy"]=function(){return(_openmpt_module_ext_destroy=Module["_openmpt_module_ext_destroy"]=Module["asm"]["kc"]).apply(null,arguments)};var _openmpt_module_ext_get_module=Module["_openmpt_module_ext_get_module"]=function(){return(_openmpt_module_ext_get_module=Module["_openmpt_module_ext_get_module"]=Module["asm"]["lc"]).apply(null,arguments)};var _openmpt_module_ext_get_interface=Module["_openmpt_module_ext_get_interface"]=function(){return(_openmpt_module_ext_get_interface=Module["_openmpt_module_ext_get_interface"]=Module["asm"]["mc"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["nc"]).apply(null,arguments)};var _setThrew=Module["_setThrew"]=function(){return(_setThrew=Module["_setThrew"]=Module["asm"]["pc"]).apply(null,arguments)};var setTempRet0=Module["setTempRet0"]=function(){return(setTempRet0=Module["setTempRet0"]=Module["asm"]["qc"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["rc"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["sc"]).apply(null,arguments)};var ___cxa_can_catch=Module["___cxa_can_catch"]=function(){return(___cxa_can_catch=Module["___cxa_can_catch"]=Module["asm"]["tc"]).apply(null,arguments)};var ___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=function(){return(___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=Module["asm"]["uc"]).apply(null,arguments)};var dynCall_j=Module["dynCall_j"]=function(){return(dynCall_j=Module["dynCall_j"]=Module["asm"]["vc"]).apply(null,arguments)};var dynCall_iiji=Module["dynCall_iiji"]=function(){return(dynCall_iiji=Module["dynCall_iiji"]=Module["asm"]["wc"]).apply(null,arguments)};var dynCall_vij=Module["dynCall_vij"]=function(){return(dynCall_vij=Module["dynCall_vij"]=Module["asm"]["xc"]).apply(null,arguments)};var dynCall_viij=Module["dynCall_viij"]=function(){return(dynCall_viij=Module["dynCall_viij"]=Module["asm"]["yc"]).apply(null,arguments)};var dynCall_jiiii=Module["dynCall_jiiii"]=function(){return(dynCall_jiiii=Module["dynCall_jiiii"]=Module["asm"]["zc"]).apply(null,arguments)};var dynCall_viiji=Module["dynCall_viiji"]=function(){return(dynCall_viiji=Module["dynCall_viiji"]=Module["asm"]["Ac"]).apply(null,arguments)};var dynCall_iiij=Module["dynCall_iiij"]=function(){return(dynCall_iiij=Module["dynCall_iiij"]=Module["asm"]["Bc"]).apply(null,arguments)};function invoke_iii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viii(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_ii(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vii(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_v(index){var sp=stackSave();try{getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_i(index){var sp=stackSave();try{return getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iid(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fi(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viif(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vid(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiifii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_diiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiidi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_diii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_di(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viid(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_dii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiidd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiji(index,a1,a2,a3,a4){var sp=stackSave();try{return dynCall_iiji(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vij(index,a1,a2,a3){var sp=stackSave();try{dynCall_vij(index,a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viij(index,a1,a2,a3,a4){var sp=stackSave();try{dynCall_viij(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return dynCall_jiiii(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiji(index,a1,a2,a3,a4,a5){var sp=stackSave();try{dynCall_viiji(index,a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_j(index){var sp=stackSave();try{return dynCall_j(index)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiij(index,a1,a2,a3,a4){var sp=stackSave();try{return dynCall_iiij(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); - -Module.UTF8ToString = UTF8ToString; -Module.writeAsciiToMemory = writeAsciiToMemory; -export { Module } diff --git a/packages/frontend/src/scripts/libopenmpt/libopenmpt.wasm b/packages/frontend/src/scripts/libopenmpt/libopenmpt.wasm Binary files differdeleted file mode 100644 index 8c11a68d5d..0000000000 --- a/packages/frontend/src/scripts/libopenmpt/libopenmpt.wasm +++ /dev/null diff --git a/packages/frontend/src/scripts/libopenmpt/readme.md b/packages/frontend/src/scripts/libopenmpt/readme.md deleted file mode 100644 index 4b99a6c40f..0000000000 --- a/packages/frontend/src/scripts/libopenmpt/readme.md +++ /dev/null @@ -1,23 +0,0 @@ -modifications made to `libopenmpt.js` (can be taken from https://lib.openmpt.org/libopenmpt/download/): - -at the beginning of the file: -```js -// @ts-nocheck -/* eslint-disable */ -``` - -at the end of the file: -```js -Module.UTF8ToString = UTF8ToString; -Module.writeAsciiToMemory = writeAsciiToMemory; -export { Module } -``` - -replace -``` -wasmBinaryFile="libopenmpt.wasm" -``` -with -``` -wasmBinaryFile=new URL("./libopenmpt.wasm", import.meta.url).href -```
\ No newline at end of file diff --git a/packages/frontend/src/scripts/login-id.ts b/packages/frontend/src/scripts/login-id.ts deleted file mode 100644 index b52735caa0..0000000000 --- a/packages/frontend/src/scripts/login-id.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export function getUrlWithLoginId(url: string, loginId: string) { - const u = new URL(url, origin); - u.searchParams.append('loginId', loginId); - return u.toString(); -} - -export function getUrlWithoutLoginId(url: string) { - const u = new URL(url); - u.searchParams.delete('loginId'); - return u.toString(); -} diff --git a/packages/frontend/src/scripts/lookup.ts b/packages/frontend/src/scripts/lookup.ts deleted file mode 100644 index 54ec2ce39b..0000000000 --- a/packages/frontend/src/scripts/lookup.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { i18n } from '@/i18n.js'; -import { Router } from '@/nirax.js'; -import { mainRouter } from '@/router/main.js'; - -export async function lookup(router?: Router) { - const _router = router ?? mainRouter; - - const { canceled, result: temp } = await os.inputText({ - title: i18n.ts.lookup, - }); - const query = temp ? temp.trim() : ''; - if (canceled || query.length <= 1) return; - - if (query.startsWith('@') && !query.includes(' ')) { - _router.push(`/${query}`); - return; - } - - if (query.startsWith('#')) { - _router.push(`/tags/${encodeURIComponent(query.substring(1))}`); - return; - } - - if (query.startsWith('http://') || query.startsWith('https://')) { - const promise = misskeyApi('ap/show', { - uri: query, - }); - - os.promiseDialog(promise, null, (err) => { - let title = i18n.ts.somethingHappened; - let text = err.message + '\n' + err.id; - - switch (err.id) { - case '974b799e-1a29-4889-b706-18d4dd93e266': - title = i18n.ts._remoteLookupErrors._federationNotAllowed.title; - text = i18n.ts._remoteLookupErrors._federationNotAllowed.description; - break; - case '1a5eab56-e47b-48c2-8d5e-217b897d70db': - title = i18n.ts._remoteLookupErrors._uriInvalid.title; - text = i18n.ts._remoteLookupErrors._uriInvalid.description; - break; - case '81b539cf-4f57-4b29-bc98-032c33c0792e': - title = i18n.ts._remoteLookupErrors._requestFailed.title; - text = i18n.ts._remoteLookupErrors._requestFailed.description; - break; - case '70193c39-54f3-4813-82f0-70a680f7495b': - title = i18n.ts._remoteLookupErrors._responseInvalid.title; - text = i18n.ts._remoteLookupErrors._responseInvalid.description; - break; - case 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a': - title = i18n.ts._remoteLookupErrors._responseInvalid.title; - text = i18n.ts._remoteLookupErrors._responseInvalidIdHostNotMatch.description; - break; - case 'dc94d745-1262-4e63-a17d-fecaa57efc82': - title = i18n.ts._remoteLookupErrors._noSuchObject.title; - text = i18n.ts._remoteLookupErrors._noSuchObject.description; - break; - } - - os.alert({ - type: 'error', - title, - text, - }); - }, i18n.ts.fetchingAsApObject); - - const res = await promise; - - if (res.type === 'User') { - _router.push(`/@${res.object.username}@${res.object.host}`); - } else if (res.type === 'Note') { - _router.push(`/notes/${res.object.id}`); - } - - return; - } -} diff --git a/packages/frontend/src/scripts/media-has-audio.ts b/packages/frontend/src/scripts/media-has-audio.ts deleted file mode 100644 index 4bf3ee5d97..0000000000 --- a/packages/frontend/src/scripts/media-has-audio.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export default async function hasAudio(media: HTMLMediaElement) { - const cloned = media.cloneNode() as HTMLMediaElement; - cloned.muted = (cloned as typeof cloned & Partial<HTMLVideoElement>).playsInline = true; - cloned.play(); - await new Promise((resolve) => cloned.addEventListener('playing', resolve)); - const result = !!(cloned as any).audioTracks?.length || (cloned as any).mozHasAudio || !!(cloned as any).webkitAudioDecodedByteCount; - cloned.remove(); - return result; -} diff --git a/packages/frontend/src/scripts/media-proxy.ts b/packages/frontend/src/scripts/media-proxy.ts deleted file mode 100644 index 78eba35ead..0000000000 --- a/packages/frontend/src/scripts/media-proxy.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { MediaProxy } from '@@/js/media-proxy.js'; -import { url } from '@@/js/config.js'; -import { instance } from '@/instance.js'; - -let _mediaProxy: MediaProxy | null = null; - -export function getProxiedImageUrl(...args: Parameters<MediaProxy['getProxiedImageUrl']>): string { - if (_mediaProxy == null) { - _mediaProxy = new MediaProxy(instance, url); - } - - return _mediaProxy.getProxiedImageUrl(...args); -} - -export function getProxiedImageUrlNullable(...args: Parameters<MediaProxy['getProxiedImageUrlNullable']>): string | null { - if (_mediaProxy == null) { - _mediaProxy = new MediaProxy(instance, url); - } - - return _mediaProxy.getProxiedImageUrlNullable(...args); -} - -export function getStaticImageUrl(...args: Parameters<MediaProxy['getStaticImageUrl']>): string { - if (_mediaProxy == null) { - _mediaProxy = new MediaProxy(instance, url); - } - - return _mediaProxy.getStaticImageUrl(...args); -} diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/scripts/merge.ts deleted file mode 100644 index 004b6d42a4..0000000000 --- a/packages/frontend/src/scripts/merge.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { deepClone } from './clone.js'; -import type { Cloneable } from './clone.js'; - -export type DeepPartial<T> = { - [P in keyof T]?: T[P] extends Record<PropertyKey, unknown> ? DeepPartial<T[P]> : T[P]; -}; - -function isPureObject(value: unknown): value is Record<PropertyKey, unknown> { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -/** - * valueにないキーをdefからもらう(再帰的)\ - * nullはそのまま、undefinedはdefの値 - **/ -export function deepMerge<X extends Record<PropertyKey, unknown>>(value: DeepPartial<X>, def: X): X { - if (isPureObject(value) && isPureObject(def)) { - const result = deepClone(value as Cloneable) as X; - for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) { - if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) { - result[k] = v; - } else if (isPureObject(v) && isPureObject(result[k])) { - const child = deepClone(result[k] as Cloneable) as DeepPartial<X[keyof X] & Record<PropertyKey, unknown>>; - result[k] = deepMerge<typeof v>(child, v); - } - } - return result; - } - throw new Error('deepMerge: value and def must be pure objects'); -} diff --git a/packages/frontend/src/scripts/mfm-function-picker.ts b/packages/frontend/src/scripts/mfm-function-picker.ts deleted file mode 100644 index 2911469cdd..0000000000 --- a/packages/frontend/src/scripts/mfm-function-picker.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Ref, nextTick } from 'vue'; -import * as os from '@/os.js'; -import { i18n } from '@/i18n.js'; -import { MFM_TAGS } from '@@/js/const.js'; -import type { MenuItem } from '@/types/menu.js'; - -/** - * MFMの装飾のリストを表示する - */ -export function mfmFunctionPicker(src: HTMLElement | EventTarget | null, textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) { - os.popupMenu([{ - text: i18n.ts.addMfmFunction, - type: 'label', - }, ...getFunctionList(textArea, textRef)], src); -} - -function getFunctionList(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>): MenuItem[] { - return MFM_TAGS.map(tag => ({ - text: tag, - icon: 'ph-brackets-curly ph-bold ph-lg', - action: () => add(textArea, textRef, tag), - })); -} - -function add(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>, type: string) { - const caretStart: number = textArea.selectionStart as number; - const caretEnd: number = textArea.selectionEnd as number; - - MFM_TAGS.forEach(tag => { - if (type === tag) { - if (caretStart === caretEnd) { - // 単純にFunctionを追加 - const trimmedText = `${textRef.value.substring(0, caretStart)}$[${type} ]${textRef.value.substring(caretEnd)}`; - textRef.value = trimmedText; - } else { - // 選択範囲を囲むようにFunctionを追加 - const trimmedText = `${textRef.value.substring(0, caretStart)}$[${type} ${textRef.value.substring(caretStart, caretEnd)}]${textRef.value.substring(caretEnd)}`; - textRef.value = trimmedText; - } - } - }); - - const nextCaretStart: number = caretStart + 3 + type.length; - const nextCaretEnd: number = caretEnd + 3 + type.length; - - // キャレットを戻す - nextTick(() => { - textArea.focus(); - textArea.setSelectionRange(nextCaretStart, nextCaretEnd); - }); -} diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/scripts/misskey-api.ts deleted file mode 100644 index dc07ad477b..0000000000 --- a/packages/frontend/src/scripts/misskey-api.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; -import { ref } from 'vue'; -import { apiUrl } from '@@/js/config.js'; -import { $i } from '@/account.js'; -export const pendingApiRequestsCount = ref(0); - -export type Endpoint = keyof Misskey.Endpoints; - -export type Request<E extends Endpoint> = Misskey.Endpoints[E]['req']; - -export type AnyRequest<E extends Endpoint | (string & unknown)> = - (E extends Endpoint ? Request<E> : never) | object; - -export type Response<E extends Endpoint | (string & unknown), P extends AnyRequest<E>> = - E extends Endpoint - ? P extends Request<E> ? Misskey.api.SwitchCaseResponseType<E, P> : never - : object; - -// Implements Misskey.api.ApiClient.request -export function misskeyApi< - ResT = void, - E extends Endpoint | NonNullable<string> = Endpoint, - P extends AnyRequest<E> = E extends Endpoint ? Request<E> : never, - _ResT = ResT extends void ? Response<E, P> : ResT, ->( - endpoint: E, - data: P & { i?: string | null; } = {} as any, - token?: string | null | undefined, - signal?: AbortSignal, -): Promise<_ResT> { - if (endpoint.includes('://')) throw new Error('invalid endpoint'); - pendingApiRequestsCount.value++; - - const onFinally = () => { - pendingApiRequestsCount.value--; - }; - - const promise = new Promise<_ResT>((resolve, reject) => { - // Append a credential - if ($i) data.i = $i.token; - if (token !== undefined) data.i = token; - - // Send request - window.fetch(`${apiUrl}/${endpoint}`, { - method: 'POST', - body: JSON.stringify(data), - credentials: 'omit', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json', - }, - signal, - }).then(async (res) => { - const body = res.status === 204 ? null : await res.json(); - - if (res.status === 200) { - resolve(body); - } else if (res.status === 204) { - resolve(undefined as _ResT); // void -> undefined - } else { - reject(body.error); - } - }).catch(reject); - }); - - promise.then(onFinally, onFinally); - - return promise; -} - -// Implements Misskey.api.ApiClient.request -export function misskeyApiGet< - ResT = void, - E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, - P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'], - _ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT, ->( - endpoint: E, - data: P = {} as any, -): Promise<_ResT> { - pendingApiRequestsCount.value++; - - const onFinally = () => { - pendingApiRequestsCount.value--; - }; - - const query = new URLSearchParams(data as any); - - const promise = new Promise<_ResT>((resolve, reject) => { - // Send request - window.fetch(`${apiUrl}/${endpoint}?${query}`, { - method: 'GET', - credentials: 'omit', - cache: 'default', - }).then(async (res) => { - const body = res.status === 204 ? null : await res.json(); - - if (res.status === 200) { - resolve(body); - } else if (res.status === 204) { - resolve(undefined as _ResT); // void -> undefined - } else { - reject(body.error); - } - }).catch(reject); - }); - - promise.then(onFinally, onFinally); - - return promise; -} diff --git a/packages/frontend/src/scripts/navigator.ts b/packages/frontend/src/scripts/navigator.ts deleted file mode 100644 index ffc0a457f4..0000000000 --- a/packages/frontend/src/scripts/navigator.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export function isSupportShare(): boolean { - return 'share' in navigator; -} diff --git a/packages/frontend/src/scripts/page-metadata.ts b/packages/frontend/src/scripts/page-metadata.ts deleted file mode 100644 index 0e3b093ecf..0000000000 --- a/packages/frontend/src/scripts/page-metadata.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; -import { MaybeRefOrGetter, Ref, inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue'; - -export type PageMetadata = { - title: string; - subtitle?: string; - icon?: string | null; - avatar?: Misskey.entities.User | null; - userName?: Misskey.entities.User | null; - needWideArea?: boolean; -}; - -type PageMetadataGetter = () => PageMetadata; -type PageMetadataReceiver = (getter: PageMetadataGetter) => void; - -const RECEIVER_KEY = Symbol('ReceiverKey'); -const setReceiver = (v: PageMetadataReceiver): void => { - provide<PageMetadataReceiver>(RECEIVER_KEY, v); -}; -const getReceiver = (): PageMetadataReceiver | undefined => { - return inject<PageMetadataReceiver>(RECEIVER_KEY); -}; - -const METADATA_KEY = Symbol('MetadataKey'); -const setMetadata = (v: Ref<PageMetadata | null>): void => { - provide<Ref<PageMetadata | null>>(METADATA_KEY, v); -}; -const getMetadata = (): Ref<PageMetadata | null> | undefined => { - return inject<Ref<PageMetadata | null>>(METADATA_KEY); -}; - -export const definePageMetadata = (maybeRefOrGetterMetadata: MaybeRefOrGetter<PageMetadata>): void => { - const metadataRef = ref(toValue(maybeRefOrGetterMetadata)); - const metadataGetter = () => metadataRef.value; - const receiver = getReceiver(); - - // setup handler - receiver?.(metadataGetter); - - // update handler - onBeforeUnmount(watch( - () => toValue(maybeRefOrGetterMetadata), - (metadata) => { - metadataRef.value = metadata; - receiver?.(metadataGetter); - }, - { deep: true }, - )); - onActivated(() => { - receiver?.(metadataGetter); - }); -}; - -export const provideMetadataReceiver = (receiver: PageMetadataReceiver): void => { - setReceiver(receiver); -}; - -export const provideReactiveMetadata = (metadataRef: Ref<PageMetadata | null>): void => { - setMetadata(metadataRef); -}; - -export const injectReactiveMetadata = (): Ref<PageMetadata | null> => { - const metadataRef = getMetadata(); - return isRef(metadataRef) ? metadataRef : ref(null); -}; diff --git a/packages/frontend/src/scripts/physics.ts b/packages/frontend/src/scripts/physics.ts deleted file mode 100644 index 8a4e9319b3..0000000000 --- a/packages/frontend/src/scripts/physics.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Matter from 'matter-js'; - -export function physics(container: HTMLElement) { - const containerWidth = container.offsetWidth; - const containerHeight = container.offsetHeight; - const containerCenterX = containerWidth / 2; - - // サイズ固定化(要らないかも?) - container.style.position = 'relative'; - container.style.boxSizing = 'border-box'; - container.style.width = `${containerWidth}px`; - container.style.height = `${containerHeight}px`; - - // create engine - const engine = Matter.Engine.create({ - constraintIterations: 4, - positionIterations: 8, - velocityIterations: 8, - }); - - const world = engine.world; - - // create renderer - const render = Matter.Render.create({ - engine: engine, - //element: document.getElementById('debug'), - options: { - width: containerWidth, - height: containerHeight, - background: 'transparent', // transparent to hide - wireframeBackground: 'transparent', // transparent to hide - }, - }); - - // Disable to hide debug - Matter.Render.run(render); - - // create runner - const runner = Matter.Runner.create(); - Matter.Runner.run(runner, engine); - - const groundThickness = 1024; - const ground = Matter.Bodies.rectangle(containerCenterX, containerHeight + (groundThickness / 2), containerWidth, groundThickness, { - isStatic: true, - restitution: 0.1, - friction: 2, - }); - - //const wallRight = Matter.Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, wallopts); - //const wallLeft = Matter.Bodies.rectangle(-50, window.innerHeight/2, 100, window.innerHeight, wallopts); - - Matter.World.add(world, [ - ground, - //wallRight, - //wallLeft, - ]); - - const objEls = Array.from(container.children) as HTMLElement[]; - const objs: Matter.Body[] = []; - for (const objEl of objEls) { - const left = objEl.dataset.physicsX ? parseInt(objEl.dataset.physicsX) : objEl.offsetLeft; - const top = objEl.dataset.physicsY ? parseInt(objEl.dataset.physicsY) : objEl.offsetTop; - - let obj: Matter.Body; - if (objEl.classList.contains('_physics_circle_')) { - obj = Matter.Bodies.circle( - left + (objEl.offsetWidth / 2), - top + (objEl.offsetHeight / 2), - Math.max(objEl.offsetWidth, objEl.offsetHeight) / 2, - { - restitution: 0.5, - }, - ); - } else { - const style = window.getComputedStyle(objEl); - obj = Matter.Bodies.rectangle( - left + (objEl.offsetWidth / 2), - top + (objEl.offsetHeight / 2), - objEl.offsetWidth, - objEl.offsetHeight, - { - chamfer: { radius: parseInt(style.borderRadius || '0', 10) }, - restitution: 0.5, - }, - ); - } - objEl.id = obj.id.toString(); - objs.push(obj); - } - - Matter.World.add(engine.world, objs); - - // Add mouse control - - const mouse = Matter.Mouse.create(container); - const mouseConstraint = Matter.MouseConstraint.create(engine, { - mouse: mouse, - constraint: { - stiffness: 0.1, - render: { - visible: false, - }, - }, - }); - - Matter.World.add(engine.world, mouseConstraint); - - // keep the mouse in sync with rendering - render.mouse = mouse; - - for (const objEl of objEls) { - objEl.style.position = 'absolute'; - objEl.style.top = '0'; - objEl.style.left = '0'; - objEl.style.margin = '0'; - } - - window.requestAnimationFrame(update); - - let stop = false; - - function update() { - for (const objEl of objEls) { - const obj = objs.find(obj => obj.id.toString() === objEl.id.toString()); - if (obj == null) continue; - - const x = (obj.position.x - objEl.offsetWidth / 2); - const y = (obj.position.y - objEl.offsetHeight / 2); - const angle = obj.angle; - objEl.style.transform = `translate(${x}px, ${y}px) rotate(${angle}rad)`; - } - - if (!stop) { - window.requestAnimationFrame(update); - } - } - - // 奈落に落ちたオブジェクトは消す - const intervalId = window.setInterval(() => { - for (const obj of objs) { - if (obj.position.y > (containerHeight + 1024)) Matter.World.remove(world, obj); - } - }, 1000 * 10); - - return { - stop: () => { - stop = true; - Matter.Runner.stop(runner); - window.clearInterval(intervalId); - }, - }; -} diff --git a/packages/frontend/src/scripts/player-url-transform.ts b/packages/frontend/src/scripts/player-url-transform.ts deleted file mode 100644 index 39c6df6500..0000000000 --- a/packages/frontend/src/scripts/player-url-transform.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { hostname } from '@@/js/config.js'; - -export function transformPlayerUrl(url: string): string { - const urlObj = new URL(url); - if (!['https:', 'http:'].includes(urlObj.protocol)) throw new Error('Invalid protocol'); - - const urlParams = new URLSearchParams(urlObj.search); - - if (urlObj.hostname === 'player.twitch.tv') { - // TwitchはCSPの制約あり - // https://dev.twitch.tv/docs/embed/video-and-clips/ - urlParams.set('parent', hostname); - urlParams.set('allowfullscreen', ''); - urlParams.set('autoplay', 'true'); - } else { - urlParams.set('autoplay', '1'); - urlParams.set('auto_play', '1'); - } - urlObj.search = urlParams.toString(); - - return urlObj.toString(); -} diff --git a/packages/frontend/src/scripts/please-login.ts b/packages/frontend/src/scripts/please-login.ts deleted file mode 100644 index a8a330eb6d..0000000000 --- a/packages/frontend/src/scripts/please-login.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { defineAsyncComponent } from 'vue'; -import { $i } from '@/account.js'; -import { instance } from '@/instance.js'; -import { i18n } from '@/i18n.js'; -import { popup } from '@/os.js'; - -export type OpenOnRemoteOptions = { - /** - * 外部のMisskey Webで特定のパスを開く - */ - type: 'web'; - - /** - * 内部パス(例: `/settings`) - */ - path: string; -} | { - /** - * 外部のMisskey Webで照会する - */ - type: 'lookup'; - - /** - * 照会したいエンティティのURL - * - * (例: `https://misskey.example.com/notes/abcdexxxxyz`) - */ - url: string; -} | { - /** - * 外部のMisskeyでノートする - */ - type: 'share'; - - /** - * `/share` ページに渡すクエリストリング - * - * @see https://go.misskey-hub.net/spec/share/ - */ - params: Record<string, string>; -}; - -export function pleaseLogin(opts: { - path?: string; - message?: string; - openOnRemote?: OpenOnRemoteOptions; -} = {}) { - if ($i) return; - - let _openOnRemote: OpenOnRemoteOptions | undefined = undefined; - - // 連合できる場合と、(連合ができなくても)共有する場合は外部連携オプションを設定 - if (opts.openOnRemote != null && (instance.federation !== 'none' || opts.openOnRemote.type === 'share')) { - _openOnRemote = opts.openOnRemote; - } - - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), { - autoSet: true, - message: opts.message ?? (_openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired), - openOnRemote: _openOnRemote, - }, { - cancelled: () => { - if (opts.path) { - window.location.href = opts.path; - } - }, - closed: () => dispose(), - }); - - throw new Error('signin required'); -} diff --git a/packages/frontend/src/scripts/popout.ts b/packages/frontend/src/scripts/popout.ts deleted file mode 100644 index 5b141222e8..0000000000 --- a/packages/frontend/src/scripts/popout.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { appendQuery } from '@@/js/url.js'; -import * as config from '@@/js/config.js'; - -export function popout(path: string, w?: HTMLElement) { - let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + path; - url = appendQuery(url, 'zen'); - if (w) { - const position = w.getBoundingClientRect(); - const width = parseInt(getComputedStyle(w, '').width, 10); - const height = parseInt(getComputedStyle(w, '').height, 10); - const x = window.screenX + position.left; - const y = window.screenY + position.top; - window.open(url, url, - `width=${width}, height=${height}, top=${y}, left=${x}`); - } else { - const width = 400; - const height = 500; - const x = window.top.outerHeight / 2 + window.top.screenY - (height / 2); - const y = window.top.outerWidth / 2 + window.top.screenX - (width / 2); - window.open(url, url, - `width=${width}, height=${height}, top=${x}, left=${y}`); - } -} diff --git a/packages/frontend/src/scripts/popup-position.ts b/packages/frontend/src/scripts/popup-position.ts deleted file mode 100644 index be49532cf8..0000000000 --- a/packages/frontend/src/scripts/popup-position.ts +++ /dev/null @@ -1,171 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export function calcPopupPosition(el: HTMLElement, props: { - anchorElement?: HTMLElement | null; - innerMargin: number; - direction: 'top' | 'bottom' | 'left' | 'right'; - align: 'top' | 'bottom' | 'left' | 'right' | 'center'; - alignOffset?: number; - x?: number; - y?: number; -}): { top: number; left: number; transformOrigin: string; } { - const contentWidth = el.offsetWidth; - const contentHeight = el.offsetHeight; - - const HORIZONTAL_MARGIN = 1; - - let rect: DOMRect; - - if (props.anchorElement) { - rect = props.anchorElement.getBoundingClientRect(); - } - - const calcPosWhenTop = () => { - let left: number; - let top: number; - - if (props.anchorElement) { - left = rect.left + window.scrollX + (props.anchorElement.offsetWidth / 2); - top = (rect.top + window.scrollY - contentHeight) - props.innerMargin; - } else { - left = props.x; - top = (props.y - contentHeight) - props.innerMargin; - } - - left -= (el.offsetWidth / 2); - - if (left + contentWidth - window.scrollX > window.innerWidth) { - left = window.innerWidth - contentWidth + window.scrollX - HORIZONTAL_MARGIN; - } - - left = Math.max(HORIZONTAL_MARGIN, left); - - return [left, top]; - }; - - const calcPosWhenBottom = () => { - let left: number; - let top: number; - - if (props.anchorElement) { - left = rect.left + window.scrollX + (props.anchorElement.offsetWidth / 2); - top = (rect.top + window.scrollY + props.anchorElement.offsetHeight) + props.innerMargin; - } else { - left = props.x; - top = (props.y) + props.innerMargin; - } - - left -= (el.offsetWidth / 2); - - if (left + contentWidth - window.scrollX > window.innerWidth) { - left = window.innerWidth - contentWidth + window.scrollX - HORIZONTAL_MARGIN; - } - - left = Math.max(HORIZONTAL_MARGIN, left); - - return [left, top]; - }; - - const calcPosWhenLeft = () => { - let left: number; - let top: number; - - if (props.anchorElement) { - left = (rect.left + window.scrollX - contentWidth) - props.innerMargin; - top = rect.top + window.scrollY + (props.anchorElement.offsetHeight / 2); - } else { - left = (props.x - contentWidth) - props.innerMargin; - top = props.y; - } - - left = Math.max(HORIZONTAL_MARGIN, left); - - top -= (el.offsetHeight / 2); - - if (top + contentHeight - window.scrollY > window.innerHeight) { - top = window.innerHeight - contentHeight + window.scrollY - 1; - } - - return [left, top]; - }; - - const calcPosWhenRight = () => { - let left: number; - let top: number; - - if (props.anchorElement) { - left = (rect.left + props.anchorElement.offsetWidth + window.scrollX) + props.innerMargin; - - if (props.align === 'top') { - top = rect.top + window.scrollY; - if (props.alignOffset != null) top += props.alignOffset; - } else if (props.align === 'bottom') { - // TODO - } else { // center - top = rect.top + window.scrollY + (props.anchorElement.offsetHeight / 2); - top -= (el.offsetHeight / 2); - } - } else { - left = props.x + props.innerMargin; - top = props.y; - top -= (el.offsetHeight / 2); - } - - left = Math.max(HORIZONTAL_MARGIN, left); - - if (top + contentHeight - window.scrollY > window.innerHeight) { - top = window.innerHeight - contentHeight + window.scrollY - 1; - } - - return [left, top]; - }; - - const calc = (): { - left: number; - top: number; - transformOrigin: string; - } => { - switch (props.direction) { - case 'top': { - const [left, top] = calcPosWhenTop(); - - // ツールチップを上に向かって表示するスペースがなければ下に向かって出す - if (top - window.scrollY < 0) { - const [left, top] = calcPosWhenBottom(); - return { left, top, transformOrigin: 'center top' }; - } - - return { left, top, transformOrigin: 'center bottom' }; - } - - case 'bottom': { - const [left, top] = calcPosWhenBottom(); - // TODO: ツールチップを下に向かって表示するスペースがなければ上に向かって出す - return { left, top, transformOrigin: 'center top' }; - } - - case 'left': { - const [left, top] = calcPosWhenLeft(); - - // ツールチップを左に向かって表示するスペースがなければ右に向かって出す - if (left - window.scrollX < 0) { - const [left, top] = calcPosWhenRight(); - return { left, top, transformOrigin: 'left center' }; - } - - return { left, top, transformOrigin: 'right center' }; - } - - case 'right': { - const [left, top] = calcPosWhenRight(); - // TODO: ツールチップを右に向かって表示するスペースがなければ左に向かって出す - return { left, top, transformOrigin: 'left center' }; - } - } - }; - - return calc(); -} diff --git a/packages/frontend/src/scripts/post-message.ts b/packages/frontend/src/scripts/post-message.ts deleted file mode 100644 index 11b6f52ddd..0000000000 --- a/packages/frontend/src/scripts/post-message.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export const postMessageEventTypes = [ - 'misskey:shareForm:shareCompleted', -] as const; - -export type PostMessageEventType = typeof postMessageEventTypes[number]; - -export type MiPostMessageEvent = { - type: PostMessageEventType; - payload?: any; -}; - -/** - * 親フレームにイベントを送信 - */ -export function postMessageToParentWindow(type: PostMessageEventType, payload?: any): void { - window.parent.postMessage({ - type, - payload, - }, '*'); -} diff --git a/packages/frontend/src/scripts/reaction-picker.ts b/packages/frontend/src/scripts/reaction-picker.ts deleted file mode 100644 index 7aec05c0cf..0000000000 --- a/packages/frontend/src/scripts/reaction-picker.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as Misskey from 'misskey-js'; -import { defineAsyncComponent, Ref, ref } from 'vue'; -import { popup } from '@/os.js'; -import { defaultStore } from '@/store.js'; - -class ReactionPicker { - private src: Ref<HTMLElement | null> = ref(null); - private manualShowing = ref(false); - private targetNote: Ref<Misskey.entities.Note | null> = ref(null); - private onChosen?: (reaction: string) => void; - private onClosed?: () => void; - - constructor() { - // nop - } - - public async init() { - const reactionsRef = defaultStore.reactiveState.reactions; - await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { - src: this.src, - pinnedEmojis: reactionsRef, - asReactionPicker: true, - targetNote: this.targetNote, - manualShowing: this.manualShowing, - }, { - done: reaction => { - if (this.onChosen) this.onChosen(reaction); - }, - close: () => { - this.manualShowing.value = false; - }, - closed: () => { - this.src.value = null; - if (this.onClosed) this.onClosed(); - }, - }); - } - - public show(src: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { - this.src.value = src; - this.targetNote.value = targetNote; - this.manualShowing.value = true; - this.onChosen = onChosen; - this.onClosed = onClosed; - } -} - -export const reactionPicker = new ReactionPicker(); diff --git a/packages/frontend/src/scripts/reload-ask.ts b/packages/frontend/src/scripts/reload-ask.ts deleted file mode 100644 index 733d91b85a..0000000000 --- a/packages/frontend/src/scripts/reload-ask.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { i18n } from '@/i18n.js'; -import * as os from '@/os.js'; -import { unisonReload } from '@/scripts/unison-reload.js'; - -let isReloadConfirming = false; - -export async function reloadAsk(opts: { - unison?: boolean; - reason?: string; -}) { - if (isReloadConfirming) { - return; - } - - isReloadConfirming = true; - - const { canceled } = await os.confirm(opts.reason == null ? { - type: 'info', - text: i18n.ts.reloadConfirm, - } : { - type: 'info', - title: i18n.ts.reloadConfirm, - text: opts.reason, - }).finally(() => { - isReloadConfirming = false; - }); - - if (canceled) return; - - if (opts.unison) { - unisonReload(); - } else { - location.reload(); - } -} diff --git a/packages/frontend/src/scripts/sanitize-html.ts b/packages/frontend/src/scripts/sanitize-html.ts deleted file mode 100644 index fc9db9bbdb..0000000000 --- a/packages/frontend/src/scripts/sanitize-html.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: dakkar and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only -*/ - -import original from 'sanitize-html'; - -export default function sanitizeHtml(str: string | null): string | null { - if (str == null) return str; - return original(str, { - allowedTags: original.defaults.allowedTags.concat(['img', 'audio', 'video', 'center', 'details', 'summary']), - allowedAttributes: { - ...original.defaults.allowedAttributes, - a: original.defaults.allowedAttributes.a.concat(['style']), - img: original.defaults.allowedAttributes.img.concat(['style']), - '*': (original.defaults.allowedAttributes['*'] || []).concat(['style']), - }, - }); -} diff --git a/packages/frontend/src/scripts/search-emoji.ts b/packages/frontend/src/scripts/search-emoji.ts deleted file mode 100644 index 4192a2df8f..0000000000 --- a/packages/frontend/src/scripts/search-emoji.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export type EmojiDef = { - emoji: string; - name: string; - url: string; - aliasOf?: string; -} | { - emoji: string; - name: string; - aliasOf?: string; - isCustomEmoji?: true; -}; -type EmojiScore = { emoji: EmojiDef, score: number }; - -export function searchEmoji(query: string | null, emojiDb: EmojiDef[], max = 30): EmojiDef[] { - if (!query) { - return []; - } - - const matched = new Map<string, EmojiScore>(); - // 完全一致(エイリアスなし) - emojiDb.some(x => { - if (x.name.toLowerCase() === query && !x.aliasOf) { - matched.set(x.name, { emoji: x, score: query.length + 3 }); - } - return matched.size === max; - }); - - // 完全一致(エイリアス込み) - if (matched.size < max) { - emojiDb.some(x => { - if (x.name.toLowerCase() === query && !matched.has(x.aliasOf ?? x.name)) { - matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length + 2 }); - } - return matched.size === max; - }); - } - - // 前方一致(エイリアスなし) - if (matched.size < max) { - emojiDb.some(x => { - if (x.name.toLowerCase().startsWith(query) && !x.aliasOf && !matched.has(x.name)) { - matched.set(x.name, { emoji: x, score: query.length + 1 }); - } - return matched.size === max; - }); - } - - // 前方一致(エイリアス込み) - if (matched.size < max) { - emojiDb.some(x => { - if (x.name.toLowerCase().startsWith(query) && !matched.has(x.aliasOf ?? x.name)) { - matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length }); - } - return matched.size === max; - }); - } - - // 部分一致(エイリアス込み) - if (matched.size < max) { - emojiDb.some(x => { - if (x.name.toLowerCase().includes(query) && !matched.has(x.aliasOf ?? x.name)) { - matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length - 1 }); - } - return matched.size === max; - }); - } - - // 簡易あいまい検索(3文字以上) - if (matched.size < max && query.length > 3) { - const queryChars = [...query]; - const hitEmojis = new Map<string, EmojiScore>(); - - for (const x of emojiDb) { - // 文字列の位置を進めながら、クエリの文字を順番に探す - - let pos = 0; - let hit = 0; - for (const c of queryChars) { - pos = x.name.indexOf(c, pos); - if (pos <= -1) break; - hit++; - } - - // 半分以上の文字が含まれていればヒットとする - if (hit > Math.ceil(queryChars.length / 2) && hit - 2 > (matched.get(x.aliasOf ?? x.name)?.score ?? 0)) { - hitEmojis.set(x.aliasOf ?? x.name, { emoji: x, score: hit - 2 }); - } - } - - // ヒットしたものを全部追加すると雑多になるので、先頭の6件程度だけにしておく(6件=オートコンプリートのポップアップのサイズ分) - [...hitEmojis.values()] - .sort((x, y) => y.score - x.score) - .slice(0, 6) - .forEach(it => matched.set(it.emoji.name, it)); - } - - return [...matched.values()] - .sort((x, y) => y.score - x.score) - .slice(0, max) - .map(it => it.emoji); -} diff --git a/packages/frontend/src/scripts/search-engine-map.ts b/packages/frontend/src/scripts/search-engine-map.ts deleted file mode 100644 index 03e5061597..0000000000 --- a/packages/frontend/src/scripts/search-engine-map.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * SPDX-FileCopyrightText: leah and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -//store the URL and if its none of these its a custom one -export const searchEngineMap = { - //The first one is the default search engine - 'https://www.google.com/search?q={query}': 'Google', - 'https://duckduckgo.com/?q={query}': 'Duckduckgo', - 'https://www.bing.com/search?q={query}': 'Bing', - 'https://search.yahoo.com/search?p={query}': 'Yahoo', - 'https://www.ecosia.org/search?q={query}': 'Ecosia', - 'https://www.qwant.com/?q={query}': 'Qwant', - 'https://search.aol.com/aol/search?q={query}': 'AOL', - 'https://yandex.com/search?text={query}': 'Yandex', -}; diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts deleted file mode 100644 index c25b4d73bd..0000000000 --- a/packages/frontend/src/scripts/select-file.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { ref } from 'vue'; -import * as Misskey from 'misskey-js'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { useStream } from '@/stream.js'; -import { i18n } from '@/i18n.js'; -import { defaultStore } from '@/store.js'; -import { uploadFile } from '@/scripts/upload.js'; - -export function chooseFileFromPc( - multiple: boolean, - options?: { - uploadFolder?: string | null; - keepOriginal?: boolean; - nameConverter?: (file: File) => string | undefined; - }, -): Promise<Misskey.entities.DriveFile[]> { - const uploadFolder = options?.uploadFolder ?? defaultStore.state.uploadFolder; - const keepOriginal = options?.keepOriginal ?? defaultStore.state.keepOriginalUploading; - const nameConverter = options?.nameConverter ?? (() => undefined); - - return new Promise((res, rej) => { - const input = document.createElement('input'); - input.type = 'file'; - input.multiple = multiple; - input.onchange = () => { - if (!input.files) return res([]); - const promises = Array.from( - input.files, - file => uploadFile(file, uploadFolder, nameConverter(file), keepOriginal), - ); - - Promise.all(promises).then(driveFiles => { - res(driveFiles); - }).catch(err => { - // アップロードのエラーは uploadFile 内でハンドリングされているためアラートダイアログを出したりはしてはいけない - }); - - // 一応廃棄 - (window as any).__misskey_input_ref__ = null; - }; - - // https://qiita.com/fukasawah/items/b9dc732d95d99551013d - // iOS Safari で正常に動かす為のおまじない - (window as any).__misskey_input_ref__ = input; - - input.click(); - }); -} - -export function chooseFileFromDrive(multiple: boolean): Promise<Misskey.entities.DriveFile[]> { - return new Promise((res, rej) => { - os.selectDriveFile(multiple).then(files => { - res(files); - }); - }); -} - -export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> { - return new Promise((res, rej) => { - os.inputText({ - title: i18n.ts.uploadFromUrl, - type: 'url', - placeholder: i18n.ts.uploadFromUrlDescription, - }).then(({ canceled, result: url }) => { - if (canceled) return; - - const marker = Math.random().toString(); // TODO: UUIDとか使う - - const connection = useStream().useChannel('main'); - connection.on('urlUploadFinished', urlResponse => { - if (urlResponse.marker === marker) { - res(urlResponse.file); - connection.dispose(); - } - }); - - misskeyApi('drive/files/upload-from-url', { - url: url, - folderId: defaultStore.state.uploadFolder, - marker, - }); - - os.alert({ - title: i18n.ts.uploadFromUrlRequested, - text: i18n.ts.uploadFromUrlMayTakeTime, - }); - }); - }); -} - -function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> { - return new Promise((res, rej) => { - const keepOriginal = ref(defaultStore.state.keepOriginalUploading); - - os.popupMenu([label ? { - text: label, - type: 'label', - } : undefined, { - type: 'switch', - text: i18n.ts.keepOriginalUploading, - ref: keepOriginal, - }, { - text: i18n.ts.upload, - icon: 'ti ti-upload', - action: () => chooseFileFromPc(multiple, { keepOriginal: keepOriginal.value }).then(files => res(files)), - }, { - text: i18n.ts.fromDrive, - icon: 'ti ti-cloud', - action: () => chooseFileFromDrive(multiple).then(files => res(files)), - }, { - text: i18n.ts.fromUrl, - icon: 'ti ti-link', - action: () => chooseFileFromUrl().then(file => res([file])), - }], src); - }); -} - -export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> { - return select(src, label, false).then(files => files[0]); -} - -export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> { - return select(src, label, true); -} diff --git a/packages/frontend/src/scripts/show-moved-dialog.ts b/packages/frontend/src/scripts/show-moved-dialog.ts deleted file mode 100644 index 35b3ef79d8..0000000000 --- a/packages/frontend/src/scripts/show-moved-dialog.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as os from '@/os.js'; -import { $i } from '@/account.js'; -import { i18n } from '@/i18n.js'; - -export function showMovedDialog() { - if (!$i) return; - if (!$i.movedTo) return; - - os.alert({ - type: 'error', - title: i18n.ts.accountMovedShort, - text: i18n.ts.operationForbidden, - }); - - throw new Error('account moved'); -} diff --git a/packages/frontend/src/scripts/show-suspended-dialog.ts b/packages/frontend/src/scripts/show-suspended-dialog.ts deleted file mode 100644 index 8b89dbb936..0000000000 --- a/packages/frontend/src/scripts/show-suspended-dialog.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as os from '@/os.js'; -import { i18n } from '@/i18n.js'; - -export function showSuspendedDialog() { - return os.alert({ - type: 'error', - title: i18n.ts.yourAccountSuspendedTitle, - text: i18n.ts.yourAccountSuspendedDescription, - }); -} diff --git a/packages/frontend/src/scripts/show-system-account-dialog.ts b/packages/frontend/src/scripts/show-system-account-dialog.ts deleted file mode 100644 index 3c28d901fc..0000000000 --- a/packages/frontend/src/scripts/show-system-account-dialog.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as os from '@/os.js'; -import { i18n } from '@/i18n.js'; - -export function showSystemAccountDialog(): Promise<void> { - return os.alert({ - type: 'error', - title: i18n.ts.systemAccountTitle, - text: i18n.ts.systemAccountDescription, - }); -} diff --git a/packages/frontend/src/scripts/shuffle.ts b/packages/frontend/src/scripts/shuffle.ts deleted file mode 100644 index 1f6ef1928c..0000000000 --- a/packages/frontend/src/scripts/shuffle.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -/** - * 配列をシャッフル (破壊的) - */ -export function shuffle<T extends unknown[]>(array: T): T { - let currentIndex = array.length; - let randomIndex: number; - - // While there remain elements to shuffle. - while (currentIndex !== 0) { - // Pick a remaining element. - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex--; - - // And swap it with the current element. - [array[currentIndex], array[randomIndex]] = [ - array[randomIndex], array[currentIndex]]; - } - - return array; -} diff --git a/packages/frontend/src/scripts/snowfall-effect.ts b/packages/frontend/src/scripts/snowfall-effect.ts deleted file mode 100644 index d88bdb6660..0000000000 --- a/packages/frontend/src/scripts/snowfall-effect.ts +++ /dev/null @@ -1,490 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export class SnowfallEffect { - private VERTEX_SOURCE = `#version 300 es - in vec4 a_position; - in vec4 a_color; - in vec3 a_rotation; - in vec3 a_speed; - in float a_size; - out vec4 v_color; - out float v_rotation; - uniform float u_time; - uniform mat4 u_projection; - uniform vec3 u_worldSize; - uniform float u_gravity; - uniform float u_wind; - uniform float u_spin_factor; - uniform float u_turbulence; - - void main() { - v_color = a_color; - v_rotation = a_rotation.x + (u_time * u_spin_factor) * a_rotation.y; - - vec3 pos = a_position.xyz; - - pos.x = mod(pos.x + u_time + u_wind * a_speed.x, u_worldSize.x * 2.0) - u_worldSize.x; - pos.y = mod(pos.y - u_time * a_speed.y * u_gravity, u_worldSize.y * 2.0) - u_worldSize.y; - - pos.x += sin(u_time * a_speed.z * u_turbulence) * a_rotation.z; - pos.z += cos(u_time * a_speed.z * u_turbulence) * a_rotation.z; - - gl_Position = u_projection * vec4(pos.xyz, a_position.w); - gl_PointSize = (a_size / gl_Position.w) * 100.0; - } - `; - - private FRAGMENT_SOURCE = `#version 300 es - precision highp float; - - in vec4 v_color; - in float v_rotation; - uniform sampler2D u_texture; - out vec4 out_color; - - void main() { - vec2 rotated = vec2( - cos(v_rotation) * (gl_PointCoord.x - 0.5) + sin(v_rotation) * (gl_PointCoord.y - 0.5) + 0.5, - cos(v_rotation) * (gl_PointCoord.y - 0.5) - sin(v_rotation) * (gl_PointCoord.x - 0.5) + 0.5 - ); - - vec4 snowflake = texture(u_texture, rotated); - - out_color = vec4(snowflake.rgb * v_color.xyz, snowflake.a * v_color.a); - } - `; - - private gl: WebGLRenderingContext; - private program: WebGLProgram; - private canvas: HTMLCanvasElement; - private buffers: Record<string, { - size: number; - value: number[] | Float32Array; - location: number; - ref: WebGLBuffer; - }>; - private uniforms: Record<string, { - type: string; - value: number[] | Float32Array; - location: WebGLUniformLocation; - }>; - private texture: WebGLTexture; - private camera: { - fov: number; - near: number; - far: number; - aspect: number; - z: number; - }; - private wind: { - current: number; - force: number; - target: number; - min: number; - max: number; - easing: number; - }; - private time: { - start: number; - previous: number; - } = { - start: 0, - previous: 0, - }; - private raf = 0; - - private density: number = 1 / 90; - private depth = 100; - private count = 1000; - private gravity = 100; - private speed: number = 1 / 10000; - private color: number[] = [1, 1, 1]; - private opacity = 1; - private size = 4; - private snowflake = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAVlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KTMInWQAAErRJREFUeAHdmgnYlmPax5MShaxRKRElPmXJXpaSsRxDU0bTZ+kt65RloiRDltEMQsxYKmS+zzYjxCCamCzV2LchResMIxFRQ1G93+93Pdf5dL9v7zuf4/hm0fc/jt9znddy3/e1nNd53c/7vHXq/AtVWVnZA/bzkaQjoWG298DeMdvrmP6/EIOqC4fBsbAx7Arz4TaYBPXgWVDnO2jSBrB2T0IMIA9mCmmoE8aonPkR6WPZHlp9xSlfeyeBzq9bHBD5feEdUGfDXBgBqnde+a2wvw/dYdNctvZNAp1PnTaFttA6JgP7eVgBM0CNzgO9HNvy0AcYDda6SaDTdXOnz8X+IkZDugAGQmOYA+ob6Ah/MIOMDRPhJjgJ6uV7pXtWt81/50SnY/Wvwn4ZDHAvwJ9ATYcxyaqsnEnqZCyCPaE80BgYZXG/5A3VyyP/b08LHa11z9KmFUwA5eqruRBHYX1s8WSI1Xcbme8Mt8PWUCU+kF8XbFN+dtH+p06OD4IU8EjD/VOZ5bnezq0XHcHuC2oV7BDlkVIWq56uIX8UjAO31GRIMYW0Vo/xXtSXJyTuXVO6xk1qalRTmQ9AfqzEvog2XYpllnsd6Qr4unCPT7NtByu0uU7vuAaOoy1JuvfXpJdTvSX0gI1gCXwGZdFmEFxoQb7Wid8s7lNu+I8wuHGsTqz2zpQ9DAa5R6HC55A2gvCMXthvwi25bjx26H0M9/9f4Rnok9s0zulFlC2HzzP9cnld8nH/p7DVrbmuIfYs6JLz9U3/z+KGadDeCDsmwre7GyEifn/su8HVSsL2HeBn8CK8AW+B7u9R5yrPgyOjvSn5DWAaXAG2UU7CE9Ayt4k4sR1lX4LaLdd9gn2ftsL+Vtuh1Dp/elH1C8lvCdUj8kDK3gbP8XdhCnSC86rcsNSR9pQvhc/gVlB9bUfqoFNAy/mLrUROrpMwCtpBxBbTtLqkF4K6IF9rf57I9pnYekx5AS0P1VhopXso9pR5buC7+kewU86nFcB+BT4EXdIvNO73sRBubGTXLZtTtgp+DEb++bACdqBuJOlAaMMzLVM3whegNznQDtCb+pW5b8YY76euB5+7pxm0IbzCfS8m3Zf2q4T8/+4JNArXGoptpxz8LqDmQJq0Qnostt/sfIn5GygD4/Zeq7B7wljQO2yjB/QGj0Pjxz4wGdqXrkjXtCT/ISyDa6EPpHrSraFjvnecFpMoMx40Br3xSlD262rYObevddHTs2kYwWUG9uP5It/f1eU5Xw9btwoXPALbwYXcg+unG/KB3Rq8n9ddAOpn4Kr8BAaBcltcDo9D7Ouavig1o34x7F94xqPk74eLQH0MH8HvwS3SLPe9iheEG6f70KiuLpZv6sxG/Va5bFJOabaO7ucAvGEbeAH+AN1hV7iDOidQFz4A2oJb6D1YDhXZHkTqpL8EbqHDYRtwW20AsdIb8syl5N2e6dTAPB2mWYa+hE4Qk7I59iMwFZ70GlJlfyuTVfygs7Hyw7HbwI0w3Tak14BqEtdg7wVdIx8pZbtBUbrjZeA3vUPBANkU+sEehev8O4Db6QpwYm+D8II0KPKHwUFeQ3oLDIMN4WgID1yOPQ+MAXMhNAtju3ztmtuAypiAw7EXwo/Am+0NfUG5mknYc6GfGVIjsoFNuyuoh8COuDcd2LmwA9jWE8bB3Q7N4XrwWAz5XOXR+Tx4n6FgdHeB6sF/w2QwhlSXdXvl/jixx4NH8GW5LDzb7GrR4ES4F5QddB99CieAwStOAPegdUZ2B71F3AXbQSn3vJ1bYaYWrayh3NUPTcbYFExVW3CfXwlvgfoavMbnDAY9dxGo6dCt0LeaB54H4UydDEPA2R4PDlrFLB9XuNmTlO+Xr7X9ZNBr9J4+EN8AMcv6ButpMND9FM6EnTOHkLrSnvtzwbbq3vwMB2ow/qWFSC8ZC++ZQaldbquH2afQWbl8TdcvVtC6LtipifAuOKt6gA9Tzqgzb5R2gP1hX3DVtZVHVvdklY5DA5beIkVPuZn8LOgAnWEfeAaUkxCan/voBNkfF+U5cFu5z5XlxZU20OmZtgm1K45VO4naNCukrcBZVk/CD+E/YBjoYjXJY8Zg9DxsDrbbBHTRotxOrug4eBs+hHgWZtKzfHrdXHBi9gDvqzxFHNA5KVfyBCf0ExgB7nkXStLLEKkniNf0AzUs5+ublkVFKiC9FBZAvGxshT0NnN3zoSUYSJQPcjAvm0HmjcIPemNS96F6E36drFLwugx7EEzNZV/l9IjoEPkW4B7eFtYH9QKcBcfA/aCWgpPQOT+zMbb9fS3nDbYR2MdgV0S5aVlUhLs0w45IHi7sqnnGJ2E7CXqHWgZXgJ1y8KqpDUmfSLmSV5yB/XrpDqVP8ofmehNdOv7I0ShfP4yyJdl2a4SchI1gCXgkHgljYfvc1i3cs/SU1A9jQRpfri/b0Sal1RrtSj4ULyHprY5C6+6E1+EBULq0E+DK7A96iwqX0z4td8B3dCdob5gD3UB3j9fUcNuDKFOvgc+bZAZFf4Zgu/q/AGPMgfm+5ShPWay+k6I31BwAvVDRYL2cuqfUVTkfnTqvVFx5ai7/MXn3tp1UrtRkDWRsaAMjzaD08uJ1irz7+8ps/6ZYj90V3FKrQBkvmubULbN7vs7tZRyJV9w0ePLbQ4PcJspqXnkbhbgoGk/AVptZRxpB0hU7Mpc1x34cdgKPm1dzeTts9XPwlFAO5Au4BDbO7ZycO7J9A/Zh2b4A2+ucALefWpTrflDKVq4kHQBOoi9PO1qvsDeGd6AxXAJbQ5VxlFrW8EnDcJlTsOPcjElxL7WNy7AduC4f2+A/rSN/Hyg7YMBTxgqPUT3F2HAqtIb58GvQW86GqyG+ff4UWz0FBuH4UhaTal1vmAGfg98dfP4d4HPGwmwYAg+D2/J7uU0ap/YaolHZVbBj5d1DaSK8ADsmqiH2JIhgNRhbPZrbhSdZ5heVJGw7477VfYuaagMK2sM8iMloga1HXAt/AeWELgQnR/0Z7k3W6pe3xTn/JamTFPGnPMZSj6p90rA8YOziwHcnH/EgTovJlJ0LPSHkyrTKmZNJ+8KrYKBsCQeB0pWdBFNleieMgzjL44jejTK1CPSY0CiMdyOT09g6ni5O3Ceg51U4VNLaPSA3SDNEwwiKFdgHgANNrpjb7UVejYTYCuZ92DR42HYh8gfDJfAMqBi4dqxk+RrKGkD0YXNsA6AT5qCUXhBe5CR0gPCC4dhqKFwI1m1qX0hr94CotDE4aAd3PCyBX4Jyn+sNL5tBDsRAp3S7b5KVYwa2A0nHaO5AXBeDtnlMxizsW+HomLh8zX9R5sTeBSEn/cqc2Tvak9eDXCyP2PgbYWzn2gefHxT7+0Qu/h18DO7XmPWYcYqSXuHz2myb6G7RNs7meLgeMxXugbiPA3clQx0xtgNPGN819L7+oCzvm6zSx+EkI+Du3Pe0LbOd/jqc7dhG9Wib+mJ5jaJBuL8e4B5aAMpAomKlb8d+KZWUVnw+dgzKSdDtvKaLDyJ1ReZB7O0J2EV5Xwd8OsTJExNpu7Q1SJ8zgy7K93UCX4P4mr4udoyhPGDKygOP+tomIFarMw2d+cfgF2DnDVAGoBvzw33YTHgPDoXQ7Fx/Wy6YkdMrcrmrehO4Pz3WvP90cIVPgonwITg4973yu0XTZK0+ZQaQd+K816twVAwKO71ZRj9zeg7lcVzXHghpVN4n2G3BAHQ1NILx4MBjoppgLwL3Ww8IHZsf6vGk3O8fwx9heK7rhD0o2zdg75JtT6GzQQ8KzcZwElSr3M5J85ktYCzEG+Gx2NNzm/Cm5pSp+K2gfLrZbg3RcB2IQcZN1qPM3+l06SjbAltX/TiXe1wtg7+AdR+AcgIs7xUPw94XxuTrnOD4E1bEoe9Rptw+DWGOGeQi7JOs1SfKKfk+epcakPNxbI8uFVdem8vT6aJdq7jASYjOFPdQDP4Q6t+Em8HVutmbkbYH9Tv4LcQW+H6ujy9Wrtxc6A7vQnznb5TbHUPZ0mw7CeoaOBAegmfBIKw8WZzs34M/oNiPGPzB2KHdrVMUlD29VFLLpw2jMWmnaIbdDNxXur+dWgVumTMglI4zMgbUEV5LmjqW7XnRkDS9qhbu/xZlZ8LWuc3UfM22Of80aVcYDJ/lstdIWxXu0TGXm/TO19vveHWuOglUxOo6iMfyBe7JOEp01ech9puuuBCMA8pVcUUNUB5lqgMYwJyE1oXOGTh9v1gO6kmogKEwHtREMHYofz5zAl3lJ2AWqJfgfohJiKB8HWWfg54YA9Zr1fn5Xmm80SdvHhNwVmq2umF8vWxA+WRwwE9BPNhOulrq0nxz97j6Go6DF8HYcBfYyer6MwWuoINeDG6roq4iE97QCtsJuxWc2JrkCeKEbgX7waOgnLiavxdQEWfohtgRwCrygIoxoQv1K0FNgR7gAKPTB+dr5lAWMliqmbAb7AzbgCs42vYK21NmOiwHJ9atpdxqDlhdA75QdYJT4XUYDfbBiVRe5ySoZTAbBpeekp6T4lo5uFnBz0fpJ6P8E9SJufEdXHipdRA/mw2hzmvfhrfgfjCKPwJnwn2g3igldb4hNaD5a6/fz7eHVuAb2wPwPs+4DB7E/hTagd64BbgoC6Ab9IAfgn+OX0p/ppAaGxZjnw6+Ep8DK8Cj0IDrmHw3GaeN9EZ/AlxFfk1RuVGUYu8K00D9Fa6EvrAUVKzO29gXg9vC1VW3g540w0xBcU2hKJnz+FxYvTCXWaduK/StuTZlLcD6JjnfEvsb6A56m32z78q4FMGw1gA4lEa60WmwMeiSnsljIBSDmEOBE3RdfvggbMuMIbNhItgJtbyUpE9ddjA0Bid1sderXDaQ1OdPAO9zH6hDcpuG2Ml7SQfArHRx6Xpf3JTluySrsrIP6Seg9/iMqsEvF6YZoXIDeAZCRmpneAHEnnLQnaEuXATX53schR3n/e7YyuvOT1bpnyV107Io3xZ6QWs4EirAyXkEqqvK3xa9CQ0c5C5xQ+zN8kWjcr2xZxTsBHfmsipbP671ZmW3wHYA58DdEPobhtwVF2HfBE9H3pT8xjkdja3iiDK4PQBO8Dx4B9wiH8JKeANcKTUW9IITwKNMeYrcArfDhVDsb1pVyty26le5D97/zWzrzVUGXyVjI0WjHUgq4CjoAuGiRuuJkN7mSJX7cn+uaZNyfBBgDHZqXvqsU2cZ6aPwChgE/ap8M9wLbSH+0DKOaw18z8N12GPAyf4BfADbwBmwCbxAHY9NvxQXx2GgVLZXPvurZDE0rqk5+NmAm8U2aIbdH9yDalgpSS80ltlB29fPqW9c8XLUHnsIuGquqt8gN7edwtazrOsAn4MysLryX8BD4Ap3y+0dZROIwPsl9h/hHjgit4lXdrdvHN8dc91wyk7JdvIS7VpF46Jb2ZGz4WJIRyBpBKQW3oR8lZuSvwQMhKtAfQUpYuf27cgbNx6EEeDAzgMHPwYMYi2gEcSfxC7B9qicDMoo/1vQI8p9IG88WAY/yeVpYrJdHpf5vytu4Ky7X46xIamrvjDb52OrG3K+HrZt4xq9wYEZPGPVfp7bhsdE2os2ylV6J1n5mbYPUX4S7AkGX+OAk2t6mm1Iw3PtQ+O4LuooK26RYvW3s7nBLZDiAGlbUHYiRV/S5AWk28DTEFqB4eo+B+n1M55Ivhu4kspj92uYCm6Px0Gv61lor0fcDQNBrQQnOr71lVeYsm894L/bkBuFe/u93eBngJtJMlwTDIDKyfDt6n3se8Dt8jHoNU0o70waq34obZ8lPx4coG+LbifrP6Pt0aQvwn65LFzcAHY8ZUtgAnwExp2WoMpeQLvaA12p7bf/pLPFmS3a/ajr750cfE43wX4YYmU9wi7IddHBCsrc69vm8uuwQydYVhQVvmsUn7s+ebfD0GhXrI+yf2jqA4oPKdo+iHxMwHbYRmgjta4cUTqCWXkg0UHatIR4SxxWKK9PeXhgKiZfxWOthzXuGff4p6b54bH3Y3W3pNxJcK8ebgdI44iys0G0N/8qKGOAGg9Ni50n3yjy2GkxSKtMRtT/21I7Fg/H9lRIX6qK5YX6zSjvDL4BGiBfBnUNmFdzwfKX4Ct40OtJv1sDj0Hlzrk6xbM3tob7uCf4amyk96VHvQg7gltGzQG9wpcwX6BCesfJ3/kJiMmgs+Gm4errUeZqF+Up4IoOzoWLcmqETyLve/2BsKkFpGUvK7VYCz6j06RbQx+ogHhN3Qdb3QF+a/wVKF94OhSHR77sWcXytcKm82usHGW9QE2B3skq/QB7APaqnJ9NuvaufnF1GIhxYH3LSAeA+hM0hMfgNzATdHvjgDHDv+qkP8gW77XW2gwmYsJe2F3zZDgxI7NteTo+/1WD/B9Au3Zjh2RyrgAAAABJRU5ErkJggg=='; - private mode = 'snow'; - - private INITIAL_BUFFERS = () => ({ - position: { size: 3, value: [] }, - color: { size: 4, value: [] }, - size: { size: 1, value: [] }, - rotation: { size: 3, value: [] }, - speed: { size: 3, value: [] }, - }); - - private INITIAL_UNIFORMS = () => ({ - time: { type: 'float', value: 0 }, - worldSize: { type: 'vec3', value: [0, 0, 0] }, - gravity: { type: 'float', value: this.gravity }, - wind: { type: 'float', value: 0 }, - spin_factor: { type: 'float', value: this.mode === 'sakura' ? 8 : 1 }, - turbulence: { type: 'float', value: this.mode === 'sakura' ? 2 : 1 }, - projection: { - type: 'mat4', - value: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], - }, - }); - - private UNIFORM_SETTERS = { - int: 'uniform1i', - float: 'uniform1f', - vec2: 'uniform2fv', - vec3: 'uniform3fv', - vec4: 'uniform4fv', - mat2: 'uniformMatrix2fv', - mat3: 'uniformMatrix3fv', - mat4: 'uniformMatrix4fv', - }; - - private CAMERA = { - fov: 60, - near: 5, - far: 10000, - aspect: 1, - z: 100, - }; - - private WIND = { - current: 0, - force: 0.01, - target: 0.01, - min: 0, - max: 0.125, - easing: 0.0005, - }; - /** - * @throws {Error} - Thrown when it fails to get WebGL context for the canvas - */ - constructor(options: { - sakura?: boolean; - }) { - if (options.sakura) { - this.mode = 'sakura'; - this.snowflake = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAFEmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgeG1wOkNyZWF0ZURhdGU9IjIwMjQtMDItMDFUMTQ6Mzk6NTYrMDkwMCIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjQtMDItMDFUMTQ6NDU6MzQrMDk6MDAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQtMDItMDFUMTQ6NDU6MzQrMDk6MDAiCiAgIHBob3Rvc2hvcDpEYXRlQ3JlYXRlZD0iMjAyNC0wMi0wMVQxNDozOTo1NiswOTAwIgogICBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIgogICBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiCiAgIGV4aWY6UGl4ZWxYRGltZW5zaW9uPSI2NCIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjY0IgogICBleGlmOkNvbG9yU3BhY2U9IjEiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNjQiCiAgIHRpZmY6SW1hZ2VMZW5ndGg9IjY0IgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSI3Mi8xIgogICB0aWZmOllSZXNvbHV0aW9uPSI3Mi8xIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0icHJvZHVjZWQiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFmZmluaXR5IFBob3RvIDIgMi4zLjEiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDItMDFUMTQ6NDU6MzQrMDk6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KPD94cGFja2V0IGVuZD0iciI/PhldI30AAAGBaUNDUHNSR0IgSUVDNjE5NjYtMi4xAAAokXWRu0sDQRCHP6Mh4oOIWlhYBPHRJBIjiDYWEV+gFjGCr+ZyuUuEJB53JyK2gq2gINr4KvQv0FawFgRFEcTaWtFG5ZwzgQQxs+zst7+dGXZnwRPPqFmrKgzZnG3GRqOB2bn5gO8FDxV46aJRUS1jcnokTln7uJdYsduQW6t83L9Wm9QsFSqqhQdVw7SFx4QnVm3D5R3hZjWtJIXPhIOmXFD4ztUTeX5xOZXnL5fNeGwIPA3CgVQJJ0pYTZtZYXk57dnMilq4j/uSOi03My1rm8xWLGKMEiXAOMMM0UcPA+L7CBGhW3aUyQ//5k+xLLmqeIM1TJZIkcYmKOqKVNdk1UXXZGRYc/v/t6+W3hvJV6+LgvfZcd46wLcN31uO83nkON/HUPkEl7li/vIh9L+LvlXU2g/AvwHnV0UtsQsXm9DyaCim8itVyvToOryeQv0cNN1AzUK+Z4VzTh4gvi5fdQ17+9Ap8f7FHyc6Z8kcDq1+AAAACXBIWXMAAAsTAAALEwEAmpwYAAADwElEQVR4nO2bT4hWVRjGf75TkhoEkhSa/9ocRIIwCsrE1pVnLbkYdFdGgQRS6caVm3CVy2oRuqmQ2yJXKTJh4GqCGs/CJCcLccAJ/yDpnGnxHYeZ4TrNfOc55y78nuWdc3/ve57v+b65f86BgQaqotiE5bEJKxYx7onYhOU1egKwGkViE/YCN4Cx2ITNC4xbDVwAJmMT9tXobVnpArEJe4CvZx0aB7aZdxPzxhkwArw66/Ae8+5Eyf6KJiA2YRPw+bzD64EjLcP3MXfyAMdjEzYWaG1GxRIQmzAEnAVeb/nzFPCSeTeaxj4FBOCZlrEjwBvm3VSJPksm4BPaJw8wBHwXm/BibMIW4HvaJ09ifFygP6BQAtKkfgEeEyHvAy+YdxdFvBmVSsBBdJMnsQ4KeTOSJyA2YT1wCXhcjL4HPG/e/amElkjAAfSTJzEPqKHSBKQLmSvAKiV3lm4BG8y7GyqgOgHvU27yAE+mGjLJEhCbsBL4A3haxXyIJoCN5t0dBUyZgF2UnzypxtsqmNKAt4SsarUkX4F0I3ONOgkAuA48a97FXJAqAa9Qb/IAa4CXFSCVATXjL635yBuQ/RsQm7AWuCroZamaBtaZd3/nQBQJeFPA6EfLFLUVBrwmYPSr7bkAhQHPCRj9al0uQGHAWgGjs9oKA7I/hS5rZ/0XSC86JDclGVph3t3t9+TcBHT56T9QVg+5BnT5/X+grB4GCcgs/sgnYCjzfIWyesg14Hrm+Qpl9ZBrwMT/DymurB4GCeiyuEidGnCN3n15V5pOPfStLAPMu1vAWA4jU7+Zd7dzAIqboREBo7PaCgN+EjA6qz1IQDbAu9/prQeorUvm3eVciOqx+JcizlL0hQKiMuAreiu/amkq1cyWxADz7ipwWsFapH4w7/5SgJRvh+cviCyp4yqQeonMOWCHktmic+bdThVMvUSmyFK2kjWkBph354FTSuY8nTLvflYCSyyT+xD4pwB3EvhADZUbYN5dAfarucB+825cDS25WvwksFuEO2nevSNizVHJ1eLvAoplrePAewJOq4oZYN5NAsPkPTCZBoYTq4iK7hgx734EjmUgjpl3Z1T9tKnGpqlP6e+p0Vg6t6iKG5De3A6ztJul+/Si3/db38WqyrY58+4CcHQJpxxN5xRXFQOSjgCjixg3SvuusiKqZoB59y+964KbCwy7Cew27+7V6apuAkibnhbaEbq3xMaohVTVAADz7hvgMHN/FKeAQ+bdt7X7Kb519mGKTdgKfEbvYucj8+7XLvr4DxAA134c0w/5AAAAAElFTkSuQmCC'; - this.size = 10; - this.density = 1 / 280; - } - - const canvas = this.initCanvas(); - const gl = canvas.getContext('webgl2', { antialias: true }); - if (gl == null) throw new Error('Failed to get WebGL context'); - - document.body.append(canvas); - - this.canvas = canvas; - this.gl = gl; - this.program = this.initProgram(); - this.buffers = this.initBuffers(); - this.uniforms = this.initUniforms(); - this.texture = this.initTexture(); - this.camera = this.initCamera(); - this.wind = this.initWind(); - - this.resize = this.resize.bind(this); - this.update = this.update.bind(this); - - window.addEventListener('resize', () => this.resize()); - } - - private initCanvas(): HTMLCanvasElement { - const canvas = document.createElement('canvas'); - - Object.assign(canvas.style, { - position: 'fixed', - top: 0, - left: 0, - width: '100vw', - height: '100vh', - background: 'transparent', - 'pointer-events': 'none', - 'z-index': 2147483647, - }); - - return canvas; - } - - private initCamera() { - return { ...this.CAMERA }; - } - - private initWind() { - return { ...this.WIND }; - } - - private initShader(type, source): WebGLShader { - const { gl } = this; - const shader = gl.createShader(type); - if (shader == null) throw new Error('Failed to create shader'); - - gl.shaderSource(shader, source); - gl.compileShader(shader); - - return shader; - } - - private initProgram(): WebGLProgram { - const { gl } = this; - const vertex = this.initShader(gl.VERTEX_SHADER, this.VERTEX_SOURCE); - const fragment = this.initShader(gl.FRAGMENT_SHADER, this.FRAGMENT_SOURCE); - const program = gl.createProgram(); - if (program == null) throw new Error('Failed to create program'); - - gl.attachShader(program, vertex); - gl.attachShader(program, fragment); - gl.linkProgram(program); - gl.useProgram(program); - - return program; - } - - private initBuffers(): SnowfallEffect['buffers'] { - const { gl, program } = this; - const buffers = this.INITIAL_BUFFERS() as unknown as SnowfallEffect['buffers']; - - for (const [name, buffer] of Object.entries(buffers)) { - buffer.location = gl.getAttribLocation(program, `a_${name}`); - buffer.ref = gl.createBuffer()!; - - gl.bindBuffer(gl.ARRAY_BUFFER, buffer.ref); - gl.enableVertexAttribArray(buffer.location); - gl.vertexAttribPointer( - buffer.location, - buffer.size, - gl.FLOAT, - false, - 0, - 0, - ); - } - - return buffers; - } - - private updateBuffers() { - const { buffers } = this; - - for (const name of Object.keys(buffers)) { - this.setBuffer(name); - } - } - - private setBuffer(name: string, value?) { - const { gl, buffers } = this; - const buffer = buffers[name]; - - buffer.value = new Float32Array(value ?? buffer.value); - - gl.bindBuffer(gl.ARRAY_BUFFER, buffer.ref); - gl.bufferData(gl.ARRAY_BUFFER, buffer.value, gl.STATIC_DRAW); - } - - private initUniforms(): SnowfallEffect['uniforms'] { - const { gl, program } = this; - const uniforms = this.INITIAL_UNIFORMS() as unknown as SnowfallEffect['uniforms']; - - for (const [name, uniform] of Object.entries(uniforms)) { - uniform.location = gl.getUniformLocation(program, `u_${name}`)!; - } - - return uniforms; - } - - private updateUniforms() { - const { uniforms } = this; - - for (const name of Object.keys(uniforms)) { - this.setUniform(name); - } - } - - private setUniform(name: string, value?) { - const { gl, uniforms } = this; - const uniform = uniforms[name]; - const setter = this.UNIFORM_SETTERS[uniform.type]; - const isMatrix = /^mat[2-4]$/i.test(uniform.type); - - uniform.value = value ?? uniform.value; - - if (isMatrix) { - gl[setter](uniform.location, false, uniform.value); - } else { - gl[setter](uniform.location, uniform.value); - } - } - - private initTexture() { - const { gl } = this; - const texture = gl.createTexture(); - if (texture == null) throw new Error('Failed to create texture'); - const image = new Image(); - - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D( - gl.TEXTURE_2D, - 0, - gl.RGBA, - 1, - 1, - 0, - gl.RGBA, - gl.UNSIGNED_BYTE, - new Uint8Array([0, 0, 0, 0]), - ); - - image.onload = () => { - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D( - gl.TEXTURE_2D, - 0, - gl.RGBA, - gl.RGBA, - gl.UNSIGNED_BYTE, - image, - ); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - }; - - image.src = this.snowflake; - - return texture; - } - - private initSnowflakes(vw: number, vh: number, dpi: number) { - const position: number[] = []; - const color: number[] = []; - const size: number[] = []; - const rotation: number[] = []; - const speed: number[] = []; - - const height = 1 / this.density; - const width = (vw / vh) * height; - const depth = this.depth; - const count = this.count; - const length = (vw / vh) * count; - - for (let i = 0; i < length; ++i) { - position.push( - -width + Math.random() * width * 2, - -height + Math.random() * height * 2, - Math.random() * depth * 2, - ); - - speed.push(1 + Math.random(), 1 + Math.random(), Math.random() * 10); - - rotation.push( - Math.random() * 2 * Math.PI, - Math.random() * 20, - Math.random() * 10, - ); - - color.push(...this.color, 0.1 + Math.random() * this.opacity); - //size.push((this.size * Math.random() * this.size * vh * dpi) / 1000); - size.push((this.size * vh * dpi) / 1000); - } - - this.setUniform('worldSize', [width, height, depth]); - - this.setBuffer('position', position); - this.setBuffer('color', color); - this.setBuffer('rotation', rotation); - this.setBuffer('size', size); - this.setBuffer('speed', speed); - } - - private setProjection(aspect: number) { - const { camera } = this; - - camera.aspect = aspect; - - const fovRad = (camera.fov * Math.PI) / 180; - const f = Math.tan(Math.PI * 0.5 - 0.5 * fovRad); - const rangeInv = 1.0 / (camera.near - camera.far); - - const m0 = f / camera.aspect; - const m5 = f; - const m10 = (camera.near + camera.far) * rangeInv; - const m11 = -1; - const m14 = camera.near * camera.far * rangeInv * 2 + camera.z; - const m15 = camera.z; - - return [m0, 0, 0, 0, 0, m5, 0, 0, 0, 0, m10, m11, 0, 0, m14, m15]; - } - - public render() { - const { gl } = this; - - gl.enable(gl.BLEND); - gl.enable(gl.CULL_FACE); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE); - gl.disable(gl.DEPTH_TEST); - - this.updateBuffers(); - this.updateUniforms(); - this.resize(true); - - this.time = { - start: window.performance.now(), - previous: window.performance.now(), - }; - - if (this.raf) window.cancelAnimationFrame(this.raf); - this.raf = window.requestAnimationFrame(this.update); - - return this; - } - - private resize(updateSnowflakes = false) { - const { canvas, gl } = this; - const vw = canvas.offsetWidth; - const vh = canvas.offsetHeight; - const aspect = vw / vh; - const dpi = window.devicePixelRatio; - - canvas.width = vw * dpi; - canvas.height = vh * dpi; - - gl.viewport(0, 0, vw * dpi, vh * dpi); - gl.clearColor(0, 0, 0, 0); - - if (updateSnowflakes === true) { - this.initSnowflakes(vw, vh, dpi); - } - - this.setUniform('projection', this.setProjection(aspect)); - } - - private update(timestamp: number) { - const { gl, buffers, wind } = this; - const elapsed = (timestamp - this.time.start) * this.speed; - const delta = timestamp - this.time.previous; - - gl.clear(gl.COLOR_BUFFER_BIT); - gl.drawArrays( - gl.POINTS, - 0, - buffers.position.value.length / buffers.position.size, - ); - - if (Math.random() > 0.995) { - wind.target = - (wind.min + Math.random() * (wind.max - wind.min)) * - (Math.random() > 0.5 ? -1 : 1); - } - - wind.force += (wind.target - wind.force) * wind.easing; - wind.current += wind.force * (delta * 0.2); - - this.setUniform('wind', wind.current); - this.setUniform('time', elapsed); - - this.time.previous = timestamp; - - this.raf = window.requestAnimationFrame(this.update); - } -} diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts deleted file mode 100644 index 2008afe045..0000000000 --- a/packages/frontend/src/scripts/sound.ts +++ /dev/null @@ -1,257 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import type { SoundStore } from '@/store.js'; -import { defaultStore } from '@/store.js'; - -let ctx: AudioContext; -const cache = new Map<string, AudioBuffer>(); -let canPlay = true; - -export const soundsTypes = [ - // 音声なし - null, - - // ドライブの音声 - '_driveFile_', - - // プリインストール - 'syuilo/n-aec', - 'syuilo/n-aec-4va', - 'syuilo/n-aec-4vb', - 'syuilo/n-aec-8va', - 'syuilo/n-aec-8vb', - 'syuilo/n-cea', - 'syuilo/n-cea-4va', - 'syuilo/n-cea-4vb', - 'syuilo/n-cea-8va', - 'syuilo/n-cea-8vb', - 'syuilo/n-eca', - 'syuilo/n-eca-4va', - 'syuilo/n-eca-4vb', - 'syuilo/n-eca-8va', - 'syuilo/n-eca-8vb', - 'syuilo/n-ea', - 'syuilo/n-ea-4va', - 'syuilo/n-ea-4vb', - 'syuilo/n-ea-8va', - 'syuilo/n-ea-8vb', - 'syuilo/n-ea-harmony', - 'syuilo/up', - 'syuilo/down', - 'syuilo/pope1', - 'syuilo/pope2', - 'syuilo/waon', - 'syuilo/popo', - 'syuilo/triple', - 'syuilo/bubble1', - 'syuilo/bubble2', - 'syuilo/poi1', - 'syuilo/poi2', - 'syuilo/pirori', - 'syuilo/pirori-wet', - 'syuilo/pirori-square-wet', - 'syuilo/square-pico', - 'syuilo/reverved', - 'syuilo/ryukyu', - 'syuilo/kick', - 'syuilo/snare', - 'syuilo/queue-jammed', - 'aisha/1', - 'aisha/2', - 'aisha/3', - 'noizenecio/kick_gaba1', - 'noizenecio/kick_gaba2', - 'noizenecio/kick_gaba3', - 'noizenecio/kick_gaba4', - 'noizenecio/kick_gaba5', - 'noizenecio/kick_gaba6', - 'noizenecio/kick_gaba7', -] as const; - -export const operationTypes = [ - 'noteMy', - 'note', - 'notification', - 'reaction', -] as const; - -/** サウンドの種類 */ -export type SoundType = typeof soundsTypes[number]; - -/** スプライトの種類 */ -export type OperationType = typeof operationTypes[number]; - -/** - * 音声を読み込む - * @param url url - * @param options `useCache`: デフォルトは`true` 一度再生した音声はキャッシュする - */ -export async function loadAudio(url: string, options?: { useCache?: boolean; }) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (ctx == null) { - ctx = new AudioContext(); - - window.addEventListener('beforeunload', () => { - ctx.close(); - }); - } - if (options?.useCache ?? true) { - if (cache.has(url)) { - return cache.get(url) as AudioBuffer; - } - } - - let response: Response; - - try { - response = await fetch(url); - } catch (err) { - return; - } - - const arrayBuffer = await response.arrayBuffer(); - const audioBuffer = await ctx.decodeAudioData(arrayBuffer); - - if (options?.useCache ?? true) { - cache.set(url, audioBuffer); - } - - return audioBuffer; -} - -/** - * 既定のスプライトを再生する - * @param type スプライトの種類を指定 - */ -export function playMisskeySfx(operationType: OperationType) { - const sound = defaultStore.state[`sound_${operationType}`]; - playMisskeySfxFile(sound).then((succeed) => { - if (!succeed && sound.type === '_driveFile_') { - // ドライブファイルが存在しない場合はデフォルトのサウンドを再生する - const soundName = defaultStore.def[`sound_${operationType}`].default.type as Exclude<SoundType, '_driveFile_'>; - if (_DEV_) console.log(`Failed to play sound: ${sound.fileUrl}, so play default sound: ${soundName}`); - playMisskeySfxFileInternal({ - type: soundName, - volume: sound.volume, - }); - } - }); -} - -/** - * サウンド設定形式で指定された音声を再生する - * @param soundStore サウンド設定 - */ -export async function playMisskeySfxFile(soundStore: SoundStore): Promise<boolean> { - // 連続して再生しない - if (!canPlay) return false; - // ユーザーアクティベーションが必要な場合はそれがない場合は再生しない - if ('userActivation' in navigator && !navigator.userActivation.hasBeenActive) return false; - // サウンドがない場合は再生しない - if (soundStore.type === null || soundStore.type === '_driveFile_' && !soundStore.fileUrl) return false; - - canPlay = false; - return await playMisskeySfxFileInternal(soundStore).finally(() => { - // ごく短時間に音が重複しないように - setTimeout(() => { - canPlay = true; - }, 25); - }); -} - -async function playMisskeySfxFileInternal(soundStore: SoundStore): Promise<boolean> { - if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) { - return false; - } - const masterVolume = defaultStore.state.sound_masterVolume; - if (isMute() || masterVolume === 0 || soundStore.volume === 0) { - return true; // ミュート時は成功として扱う - } - const url = soundStore.type === '_driveFile_' ? soundStore.fileUrl : `/client-assets/sounds/${soundStore.type}.mp3`; - const buffer = await loadAudio(url).catch(() => { - return undefined; - }); - if (!buffer) return false; - const volume = soundStore.volume * masterVolume; - createSourceNode(buffer, { volume }).soundSource.start(); - return true; -} - -export async function playUrl(url: string, opts: { - volume?: number; - pan?: number; - playbackRate?: number; -}) { - if (opts.volume === 0) { - return; - } - const buffer = await loadAudio(url); - if (!buffer) return; - createSourceNode(buffer, opts).soundSource.start(); -} - -export function createSourceNode(buffer: AudioBuffer, opts: { - volume?: number; - pan?: number; - playbackRate?: number; -}): { - soundSource: AudioBufferSourceNode; - panNode: StereoPannerNode; - gainNode: GainNode; -} { - const panNode = ctx.createStereoPanner(); - panNode.pan.value = opts.pan ?? 0; - - const gainNode = ctx.createGain(); - - gainNode.gain.value = opts.volume ?? 1; - - const soundSource = ctx.createBufferSource(); - soundSource.buffer = buffer; - soundSource.playbackRate.value = opts.playbackRate ?? 1; - soundSource - .connect(panNode) - .connect(gainNode) - .connect(ctx.destination); - - return { soundSource, panNode, gainNode }; -} - -/** - * 音声の長さをミリ秒で取得する - * @param file ファイルのURL(ドライブIDではない) - */ -export async function getSoundDuration(file: string): Promise<number> { - const audioEl = document.createElement('audio'); - audioEl.src = file; - return new Promise((resolve) => { - const si = setInterval(() => { - if (audioEl.readyState > 0) { - resolve(audioEl.duration * 1000); - clearInterval(si); - audioEl.remove(); - } - }, 100); - }); -} - -/** - * ミュートすべきかどうかを判断する - */ -export function isMute(): boolean { - if (defaultStore.state.sound_notUseSound) { - // サウンドを出力しない - return true; - } - - // noinspection RedundantIfStatementJS - if (defaultStore.state.sound_useSoundOnlyWhenActive && document.visibilityState === 'hidden') { - // ブラウザがアクティブな時のみサウンドを出力する - return true; - } - - return false; -} diff --git a/packages/frontend/src/scripts/sticky-sidebar.ts b/packages/frontend/src/scripts/sticky-sidebar.ts deleted file mode 100644 index 50f1e6ecc8..0000000000 --- a/packages/frontend/src/scripts/sticky-sidebar.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export class StickySidebar { - private lastScrollTop = 0; - private container: HTMLElement; - private el: HTMLElement; - private spacer: HTMLElement; - private marginTop: number; - private isTop = false; - private isBottom = false; - private offsetTop: number; - private globalHeaderHeight = 59; - - constructor(container: StickySidebar['container'], marginTop = 0, globalHeaderHeight = 0) { - this.container = container; - this.el = this.container.children[0] as HTMLElement; - this.el.style.position = 'sticky'; - this.spacer = document.createElement('div'); - this.container.prepend(this.spacer); - this.marginTop = marginTop; - this.offsetTop = this.container.getBoundingClientRect().top; - this.globalHeaderHeight = globalHeaderHeight; - } - - public calc(scrollTop: number) { - if (scrollTop > this.lastScrollTop) { // downscroll - const overflow = Math.max(0, this.globalHeaderHeight + (this.el.clientHeight + this.marginTop) - window.innerHeight); - this.el.style.bottom = null; - this.el.style.top = `${-overflow + this.marginTop + this.globalHeaderHeight}px`; - - this.isBottom = (scrollTop + window.innerHeight) >= (this.el.offsetTop + this.el.clientHeight); - - if (this.isTop) { - this.isTop = false; - this.spacer.style.marginTop = `${Math.max(0, this.globalHeaderHeight + this.lastScrollTop + this.marginTop - this.offsetTop)}px`; - } - } else { // upscroll - const overflow = this.globalHeaderHeight + (this.el.clientHeight + this.marginTop) - window.innerHeight; - this.el.style.top = null; - this.el.style.bottom = `${-overflow}px`; - - this.isTop = scrollTop + this.marginTop + this.globalHeaderHeight <= this.el.offsetTop; - - if (this.isBottom) { - this.isBottom = false; - this.spacer.style.marginTop = `${this.globalHeaderHeight + this.lastScrollTop + this.marginTop - this.offsetTop - overflow}px`; - } - } - - this.lastScrollTop = scrollTop <= 0 ? 0 : scrollTop; - } -} diff --git a/packages/frontend/src/scripts/stream-mock.ts b/packages/frontend/src/scripts/stream-mock.ts deleted file mode 100644 index cb0e607fcb..0000000000 --- a/packages/frontend/src/scripts/stream-mock.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { EventEmitter } from 'eventemitter3'; -import * as Misskey from 'misskey-js'; -import type { Channels, StreamEvents, IStream, IChannelConnection } from 'misskey-js'; - -type AnyOf<T extends Record<any, any>> = T[keyof T]; -type OmitFirst<T extends any[]> = T extends [any, ...infer R] ? R : never; - -/** - * Websocket無効化時に使うStreamのモック(なにもしない) - */ -export class StreamMock extends EventEmitter<StreamEvents> implements IStream { - public readonly state = 'initializing'; - - constructor(...args: ConstructorParameters<typeof Misskey.Stream>) { - super(); - // do nothing - } - - public useChannel<C extends keyof Channels>(channel: C, params?: Channels[C]['params'], name?: string): ChannelConnectionMock<Channels[C]> { - return new ChannelConnectionMock(this, channel, name); - } - - public removeSharedConnection(connection: any): void { - // do nothing - } - - public removeSharedConnectionPool(pool: any): void { - // do nothing - } - - public disconnectToChannel(): void { - // do nothing - } - - public send(typeOrPayload: string): void - public send(typeOrPayload: string, payload: any): void - public send(typeOrPayload: Record<string, any> | any[]): void - public send(typeOrPayload: string | Record<string, any> | any[], payload?: any): void { - // do nothing - } - - public ping(): void { - // do nothing - } - - public heartbeat(): void { - // do nothing - } - - public close(): void { - // do nothing - } -} - -class ChannelConnectionMock<Channel extends AnyOf<Channels> = any> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> { - public id = ''; - public name?: string; // for debug - public inCount = 0; // for debug - public outCount = 0; // for debug - public channel: string; - - constructor(stream: IStream, ...args: OmitFirst<ConstructorParameters<typeof Misskey.ChannelConnection<Channel>>>) { - super(); - - this.channel = args[0]; - this.name = args[1]; - } - - public send<T extends keyof Channel['receives']>(type: T, body: Channel['receives'][T]): void { - // do nothing - } - - public dispose(): void { - // do nothing - } -} diff --git a/packages/frontend/src/scripts/test-utils.ts b/packages/frontend/src/scripts/test-utils.ts deleted file mode 100644 index 52bb2d94e0..0000000000 --- a/packages/frontend/src/scripts/test-utils.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export async function tick(): Promise<void> { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - await new Promise((globalThis.requestIdleCallback ?? setTimeout) as never); -} diff --git a/packages/frontend/src/scripts/theme-editor.ts b/packages/frontend/src/scripts/theme-editor.ts deleted file mode 100644 index 0092af1640..0000000000 --- a/packages/frontend/src/scripts/theme-editor.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { v4 as uuid } from 'uuid'; - -import { themeProps, Theme } from './theme.js'; - -export type Default = null; -export type Color = string; -export type FuncName = 'alpha' | 'darken' | 'lighten'; -export type Func = { type: 'func'; name: FuncName; arg: number; value: string; }; -export type RefProp = { type: 'refProp'; key: string; }; -export type RefConst = { type: 'refConst'; key: string; }; -export type Css = { type: 'css'; value: string; }; - -export type ThemeValue = Color | Func | RefProp | RefConst | Css | Default; - -export type ThemeViewModel = [ string, ThemeValue ][]; - -export const fromThemeString = (str?: string) : ThemeValue => { - if (!str) return null; - if (str.startsWith(':')) { - const parts = str.slice(1).split('<'); - const name = parts[0] as FuncName; - const arg = parseFloat(parts[1]); - const value = parts[2].startsWith('@') ? parts[2].slice(1) : ''; - return { type: 'func', name, arg, value }; - } else if (str.startsWith('@')) { - return { - type: 'refProp', - key: str.slice(1), - }; - } else if (str.startsWith('$')) { - return { - type: 'refConst', - key: str.slice(1), - }; - } else if (str.startsWith('"')) { - return { - type: 'css', - value: str.substring(1).trim(), - }; - } else { - return str; - } -}; - -export const toThemeString = (value: Color | Func | RefProp | RefConst | Css) => { - if (typeof value === 'string') return value; - switch (value.type) { - case 'func': return `:${value.name}<${value.arg}<@${value.value}`; - case 'refProp': return `@${value.key}`; - case 'refConst': return `$${value.key}`; - case 'css': return `" ${value.value}`; - } -}; - -export const convertToMisskeyTheme = (vm: ThemeViewModel, name: string, desc: string, author: string, base: 'dark' | 'light'): Theme => { - const props = { } as { [key: string]: string }; - for (const [key, value] of vm) { - if (value === null) continue; - props[key] = toThemeString(value); - } - - return { - id: uuid(), - name, desc, author, props, base, - }; -}; - -export const convertToViewModel = (theme: Theme): ThemeViewModel => { - const vm: ThemeViewModel = []; - // プロパティの登録 - vm.push(...themeProps.map(key => [key, fromThemeString(theme.props[key])] as [ string, ThemeValue ])); - - // 定数の登録 - const consts = Object - .keys(theme.props) - .filter(k => k.startsWith('$')) - .map(k => [k, fromThemeString(theme.props[k])] as [ string, ThemeValue ]); - - vm.push(...consts); - return vm; -}; diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts deleted file mode 100644 index 8242e7d2e4..0000000000 --- a/packages/frontend/src/scripts/theme.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { ref } from 'vue'; -import tinycolor from 'tinycolor2'; -import lightTheme from '@@/themes/_light.json5'; -import darkTheme from '@@/themes/_dark.json5'; -import { deepClone } from './clone.js'; -import type { BundledTheme } from 'shiki/themes'; -import { globalEvents } from '@/events.js'; -import { miLocalStorage } from '@/local-storage.js'; - -export type Theme = { - id: string; - name: string; - author: string; - desc?: string; - base?: 'dark' | 'light'; - props: Record<string, string>; - codeHighlighter?: { - base: BundledTheme; - overrides?: Record<string, any>; - } | { - base: '_none_'; - overrides: Record<string, any>; - }; -}; - -export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); - -export const getBuiltinThemes = () => Promise.all( - [ - 'l-light', - 'l-coffee', - 'l-apricot', - 'l-rainy', - 'l-botanical', - 'l-vivid', - 'l-cherry', - 'l-sushi', - 'l-u0', - - 'd-dark', - 'd-persimmon', - 'd-astro', - 'd-future', - 'd-botanical', - 'd-green-lime', - 'd-green-orange', - 'd-cherry', - 'd-ice', - 'd-u0', - 'rosepine', - 'rosepine-dawn', - ].map(name => import(`@@/themes/${name}.json5`).then(({ default: _default }): Theme => _default)), -); - -export const getBuiltinThemesRef = () => { - const builtinThemes = ref<Theme[]>([]); - getBuiltinThemes().then(themes => builtinThemes.value = themes); - return builtinThemes; -}; - -const themeFontFaceName = 'sharkey-theme-font-face'; - -let timeout: number | null = null; - -export function applyTheme(theme: Theme, persist = true) { - if (timeout) window.clearTimeout(timeout); - - document.documentElement.classList.add('_themeChanging_'); - - timeout = window.setTimeout(() => { - document.documentElement.classList.remove('_themeChanging_'); - }, 1000); - - const colorScheme = theme.base === 'dark' ? 'dark' : 'light'; - - document.documentElement.dataset.colorScheme = colorScheme; - - // Deep copy - const _theme = deepClone(theme); - - if (_theme.base) { - const base = [lightTheme, darkTheme].find(x => x.id === _theme.base); - if (base) _theme.props = Object.assign({}, base.props, _theme.props); - } - - const props = compile(_theme); - - for (const tag of document.head.children) { - if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { - tag.setAttribute('content', props['htmlThemeColor']); - break; - } - } - - let existingFontFace; - document.fonts.forEach( - (fontFace) => { - if (fontFace.family === themeFontFaceName) existingFontFace = fontFace; - }, - ); - if (existingFontFace) document.fonts.delete(existingFontFace); - - const fontFaceSrc = props.fontFaceSrc; - const fontFaceOpts = props.fontFaceOpts || {}; - - if (fontFaceSrc) { - const fontFace = new FontFace( - themeFontFaceName, - fontFaceSrc, fontFaceOpts, - ); - document.fonts.add(fontFace); - fontFace.load().catch( - (failure) => { - console.log(failure); - }, - ); - } - - for (const [k, v] of Object.entries(props)) { - if (k.startsWith('font')) continue; - document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); - } - - document.documentElement.style.setProperty('color-scheme', colorScheme); - - if (persist) { - miLocalStorage.setItem('theme', JSON.stringify(props)); - miLocalStorage.setItem('colorScheme', colorScheme); - } - - // 色計算など再度行えるようにクライアント全体に通知 - globalEvents.emit('themeChanged'); -} - -function compile(theme: Theme): Record<string, string> { - function getColor(val: string): tinycolor.Instance { - if (val[0] === '@') { // ref (prop) - return getColor(theme.props[val.substring(1)]); - } else if (val[0] === '$') { // ref (const) - return getColor(theme.props[val]); - } else if (val[0] === ':') { // func - const parts = val.split('<'); - const func = parts.shift().substring(1); - const arg = parseFloat(parts.shift()); - const color = getColor(parts.join('<')); - - switch (func) { - case 'darken': return color.darken(arg); - case 'lighten': return color.lighten(arg); - case 'alpha': return color.setAlpha(arg); - case 'hue': return color.spin(arg); - case 'saturate': return color.saturate(arg); - } - } - - // other case - return tinycolor(val); - } - - const props = {}; - - for (const [k, v] of Object.entries(theme.props)) { - if (k.startsWith('$')) continue; // ignore const - if (k.startsWith('font')) { // font specs are different - props[k] = v; - continue; - } - - props[k] = v.startsWith('"') ? v.replace(/^"\s*/, '') : genValue(getColor(v)); - } - - return props; -} - -function genValue(c: tinycolor.Instance): string { - return c.toRgbString(); -} - -export function validateTheme(theme: Record<string, any>): boolean { - if (theme.id == null || typeof theme.id !== 'string') return false; - if (theme.name == null || typeof theme.name !== 'string') return false; - if (theme.base == null || !['light', 'dark'].includes(theme.base)) return false; - if (theme.props == null || typeof theme.props !== 'object') return false; - return true; -} diff --git a/packages/frontend/src/scripts/time.ts b/packages/frontend/src/scripts/time.ts deleted file mode 100644 index 275b67ed00..0000000000 --- a/packages/frontend/src/scripts/time.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -const dateTimeIntervals = { - 'day': 86400000, - 'hour': 3600000, - 'ms': 1, -}; - -export function dateUTC(time: number[]): Date { - const d = - time.length === 2 ? Date.UTC(time[0], time[1]) - : time.length === 3 ? Date.UTC(time[0], time[1], time[2]) - : time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3]) - : time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4]) - : time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5]) - : time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6]) - : null; - - if (!d) throw new Error('wrong number of arguments'); - - return new Date(d); -} - -export function isTimeSame(a: Date, b: Date): boolean { - return a.getTime() === b.getTime(); -} - -export function isTimeBefore(a: Date, b: Date): boolean { - return (a.getTime() - b.getTime()) < 0; -} - -export function isTimeAfter(a: Date, b: Date): boolean { - return (a.getTime() - b.getTime()) > 0; -} - -export function addTime(x: Date, value: number, span: keyof typeof dateTimeIntervals = 'ms'): Date { - return new Date(x.getTime() + (value * dateTimeIntervals[span])); -} - -export function subtractTime(x: Date, value: number, span: keyof typeof dateTimeIntervals = 'ms'): Date { - return new Date(x.getTime() - (value * dateTimeIntervals[span])); -} diff --git a/packages/frontend/src/scripts/timezones.ts b/packages/frontend/src/scripts/timezones.ts deleted file mode 100644 index c7582e06da..0000000000 --- a/packages/frontend/src/scripts/timezones.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export const timezones = [{ - name: 'UTC', - abbrev: 'UTC', - offset: 0, -}, { - name: 'Europe/Berlin', - abbrev: 'CET', - offset: 60, -}, { - name: 'Asia/Tokyo', - abbrev: 'JST', - offset: 540, -}, { - name: 'Asia/Seoul', - abbrev: 'KST', - offset: 540, -}, { - name: 'Asia/Shanghai', - abbrev: 'CST', - offset: 480, -}, { - name: 'Australia/Sydney', - abbrev: 'AEST', - offset: 600, -}, { - name: 'Australia/Darwin', - abbrev: 'ACST', - offset: 570, -}, { - name: 'Australia/Perth', - abbrev: 'AWST', - offset: 480, -}, { - name: 'America/New_York', - abbrev: 'EST', - offset: -300, -}, { - name: 'America/Mexico_City', - abbrev: 'CST', - offset: -360, -}, { - name: 'America/Phoenix', - abbrev: 'MST', - offset: -420, -}, { - name: 'America/Los_Angeles', - abbrev: 'PST', - offset: -480, -}]; diff --git a/packages/frontend/src/scripts/touch.ts b/packages/frontend/src/scripts/touch.ts deleted file mode 100644 index 13c9d648dc..0000000000 --- a/packages/frontend/src/scripts/touch.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { ref } from 'vue'; -import { deviceKind } from '@/scripts/device-kind.js'; - -const isTouchSupported = 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0; - -export let isTouchUsing = deviceKind === 'tablet' || deviceKind === 'smartphone'; - -if (isTouchSupported && !isTouchUsing) { - window.addEventListener('touchstart', () => { - // maxTouchPointsなどでの判定だけだと、「タッチ機能付きディスプレイを使っているがマウスでしか操作しない」場合にも - // タッチで使っていると判定されてしまうため、実際に一度でもタッチされたらtrueにする - isTouchUsing = true; - }, { passive: true }); -} - -/** (MkHorizontalSwipe) 横スワイプ中か? */ -export const isHorizontalSwipeSwiping = ref(false); diff --git a/packages/frontend/src/scripts/unison-reload.ts b/packages/frontend/src/scripts/unison-reload.ts deleted file mode 100644 index a24941d02e..0000000000 --- a/packages/frontend/src/scripts/unison-reload.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -// SafariがBroadcastChannel未実装なのでライブラリを使う -import { BroadcastChannel } from 'broadcast-channel'; - -export const reloadChannel = new BroadcastChannel<string | null>('reload'); - -// BroadcastChannelを用いて、クライアントが一斉にreloadするようにします。 -export function unisonReload(path?: string) { - if (path !== undefined) { - reloadChannel.postMessage(path); - location.href = path; - } else { - reloadChannel.postMessage(null); - location.reload(); - } -} diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts deleted file mode 100644 index 713573a377..0000000000 --- a/packages/frontend/src/scripts/upload.ts +++ /dev/null @@ -1,162 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { reactive, ref } from 'vue'; -import * as Misskey from 'misskey-js'; -import { v4 as uuid } from 'uuid'; -import { readAndCompressImage } from '@misskey-dev/browser-image-resizer'; -import { getCompressionConfig } from './upload/compress-config.js'; -import { defaultStore } from '@/store.js'; -import { apiUrl } from '@@/js/config.js'; -import { $i } from '@/account.js'; -import { alert } from '@/os.js'; -import { i18n } from '@/i18n.js'; -import { instance } from '@/instance.js'; - -type Uploading = { - id: string; - name: string; - progressMax: number | undefined; - progressValue: number | undefined; - img: string; -}; -export const uploads = ref<Uploading[]>([]); - -const mimeTypeMap = { - 'image/webp': 'webp', - 'image/jpeg': 'jpg', - 'image/png': 'png', -} as const; - -export function uploadFile( - file: File, - folder?: string | Misskey.entities.DriveFolder, - name?: string, - keepOriginal: boolean = defaultStore.state.keepOriginalUploading, -): Promise<Misskey.entities.DriveFile> { - if ($i == null) throw new Error('Not logged in'); - - const _folder = typeof folder === 'string' ? folder : folder?.id; - - if (file.size > instance.maxFileSize) { - alert({ - type: 'error', - title: i18n.ts.failedToUpload, - text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit, - }); - return Promise.reject(); - } - - return new Promise((resolve, reject) => { - const id = uuid(); - - const reader = new FileReader(); - reader.onload = async (): Promise<void> => { - const filename = name ?? file.name ?? 'untitled'; - const extension = filename.split('.').length > 1 ? '.' + filename.split('.').pop() : ''; - - const ctx = reactive<Uploading>({ - id, - name: defaultStore.state.keepOriginalFilename ? filename : id + extension, - progressMax: undefined, - progressValue: undefined, - img: window.URL.createObjectURL(file), - }); - - uploads.value.push(ctx); - - const config = !keepOriginal ? await getCompressionConfig(file) : undefined; - let resizedImage: Blob | undefined; - if (config) { - try { - const resized = await readAndCompressImage(file, config); - if (resized.size < file.size || file.type === 'image/webp') { - // The compression may not always reduce the file size - // (and WebP is not browser safe yet) - resizedImage = resized; - } - if (_DEV_) { - const saved = ((1 - resized.size / file.size) * 100).toFixed(2); - console.log(`Image compression: before ${file.size} bytes, after ${resized.size} bytes, saved ${saved}%`); - } - - ctx.name = file.type !== config.mimeType ? `${ctx.name}.${mimeTypeMap[config.mimeType]}` : ctx.name; - } catch (err) { - console.error('Failed to resize image', err); - } - } - - const formData = new FormData(); - formData.append('i', $i!.token); - formData.append('force', 'true'); - formData.append('file', resizedImage ?? file); - formData.append('name', ctx.name); - if (_folder) formData.append('folderId', _folder); - - const xhr = new XMLHttpRequest(); - xhr.open('POST', apiUrl + '/drive/files/create', true); - xhr.onload = ((ev: ProgressEvent<XMLHttpRequest>) => { - if (xhr.status !== 200 || ev.target == null || ev.target.response == null) { - // TODO: 消すのではなくて(ネットワーク的なエラーなら)再送できるようにしたい - uploads.value = uploads.value.filter(x => x.id !== id); - - if (xhr.status === 413) { - alert({ - type: 'error', - title: i18n.ts.failedToUpload, - text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit, - }); - } else if (ev.target?.response) { - const res = JSON.parse(ev.target.response); - if (res.error?.id === 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2') { - alert({ - type: 'error', - title: i18n.ts.failedToUpload, - text: i18n.ts.cannotUploadBecauseInappropriate, - }); - } else if (res.error?.id === 'd08dbc37-a6a9-463a-8c47-96c32ab5f064') { - alert({ - type: 'error', - title: i18n.ts.failedToUpload, - text: i18n.ts.cannotUploadBecauseNoFreeSpace, - }); - } else { - alert({ - type: 'error', - title: i18n.ts.failedToUpload, - text: `${res.error?.message}\n${res.error?.code}\n${res.error?.id}`, - }); - } - } else { - alert({ - type: 'error', - title: 'Failed to upload', - text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}`, - }); - } - - reject(); - return; - } - - const driveFile = JSON.parse(ev.target.response); - - resolve(driveFile); - - uploads.value = uploads.value.filter(x => x.id !== id); - }) as (ev: ProgressEvent<EventTarget>) => any; - - xhr.upload.onprogress = ev => { - if (ev.lengthComputable) { - ctx.progressMax = ev.total; - ctx.progressValue = ev.loaded; - } - }; - - xhr.send(formData); - }; - reader.readAsArrayBuffer(file); - }); -} diff --git a/packages/frontend/src/scripts/upload/compress-config.ts b/packages/frontend/src/scripts/upload/compress-config.ts deleted file mode 100644 index 3046b7f518..0000000000 --- a/packages/frontend/src/scripts/upload/compress-config.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import isAnimated from 'is-file-animated'; -import { isWebpSupported } from './isWebpSupported.js'; -import type { BrowserImageResizerConfigWithConvertedOutput } from '@misskey-dev/browser-image-resizer'; - -const compressTypeMap = { - 'image/jpeg': { quality: 0.90, mimeType: 'image/webp' }, - 'image/png': { quality: 1, mimeType: 'image/webp' }, - 'image/webp': { quality: 0.90, mimeType: 'image/webp' }, - 'image/svg+xml': { quality: 1, mimeType: 'image/webp' }, -} as const; - -const compressTypeMapFallback = { - 'image/jpeg': { quality: 0.85, mimeType: 'image/jpeg' }, - 'image/png': { quality: 1, mimeType: 'image/png' }, - 'image/webp': { quality: 0.85, mimeType: 'image/jpeg' }, - 'image/svg+xml': { quality: 1, mimeType: 'image/png' }, -} as const; - -export async function getCompressionConfig(file: File): Promise<BrowserImageResizerConfigWithConvertedOutput | undefined> { - const imgConfig = (isWebpSupported() ? compressTypeMap : compressTypeMapFallback)[file.type]; - if (!imgConfig || await isAnimated(file)) { - return; - } - - return { - maxWidth: 2048, - maxHeight: 2048, - debug: true, - ...imgConfig, - }; -} diff --git a/packages/frontend/src/scripts/upload/isWebpSupported.ts b/packages/frontend/src/scripts/upload/isWebpSupported.ts deleted file mode 100644 index 2511236ecc..0000000000 --- a/packages/frontend/src/scripts/upload/isWebpSupported.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -let isWebpSupportedCache: boolean | undefined; -export function isWebpSupported() { - if (isWebpSupportedCache === undefined) { - const canvas = document.createElement('canvas'); - canvas.width = 1; - canvas.height = 1; - isWebpSupportedCache = canvas.toDataURL('image/webp').startsWith('data:image/webp'); - } - return isWebpSupportedCache; -} diff --git a/packages/frontend/src/scripts/use-chart-tooltip.ts b/packages/frontend/src/scripts/use-chart-tooltip.ts deleted file mode 100644 index bba64fc6ee..0000000000 --- a/packages/frontend/src/scripts/use-chart-tooltip.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { onUnmounted, onDeactivated, ref } from 'vue'; -import * as os from '@/os.js'; -import MkChartTooltip from '@/components/MkChartTooltip.vue'; - -export function useChartTooltip(opts: { position: 'top' | 'middle' } = { position: 'top' }) { - const tooltipShowing = ref(false); - const tooltipX = ref(0); - const tooltipY = ref(0); - const tooltipTitle = ref<string | null>(null); - const tooltipSeries = ref<{ - backgroundColor: string; - borderColor: string; - text: string; - }[] | null>(null); - const { dispose: disposeTooltipComponent } = os.popup(MkChartTooltip, { - showing: tooltipShowing, - x: tooltipX, - y: tooltipY, - title: tooltipTitle, - series: tooltipSeries, - }, {}); - - onUnmounted(() => { - disposeTooltipComponent(); - }); - - onDeactivated(() => { - tooltipShowing.value = false; - }); - - function handler(context) { - if (context.tooltip.opacity === 0) { - tooltipShowing.value = false; - return; - } - - tooltipTitle.value = context.tooltip.title[0]; - tooltipSeries.value = context.tooltip.body.map((b, i) => ({ - backgroundColor: context.tooltip.labelColors[i].backgroundColor, - borderColor: context.tooltip.labelColors[i].borderColor, - text: b.lines[0], - })); - - const rect = context.chart.canvas.getBoundingClientRect(); - - tooltipShowing.value = true; - tooltipX.value = rect.left + window.scrollX + context.tooltip.caretX; - if (opts.position === 'top') { - tooltipY.value = rect.top + window.scrollY; - } else if (opts.position === 'middle') { - tooltipY.value = rect.top + window.scrollY + context.tooltip.caretY; - } - } - - return { - handler, - }; -} diff --git a/packages/frontend/src/scripts/use-form.ts b/packages/frontend/src/scripts/use-form.ts deleted file mode 100644 index 0d505fe466..0000000000 --- a/packages/frontend/src/scripts/use-form.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { computed, Reactive, reactive, watch } from 'vue'; - -function copy<T>(v: T): T { - return JSON.parse(JSON.stringify(v)); -} - -function unwrapReactive<T>(v: Reactive<T>): T { - return JSON.parse(JSON.stringify(v)); -} - -export function useForm<T extends Record<string, any>>(initialState: T, save: (newState: T) => Promise<void>) { - const currentState = reactive<T>(copy(initialState)); - const previousState = reactive<T>(copy(initialState)); - - const modifiedStates = reactive<Record<keyof T, boolean>>({} as any); - for (const key in currentState) { - modifiedStates[key] = false; - } - const modified = computed(() => Object.values(modifiedStates).some(v => v)); - const modifiedCount = computed(() => Object.values(modifiedStates).filter(v => v).length); - - watch([currentState, previousState], () => { - for (const key in modifiedStates) { - modifiedStates[key] = currentState[key] !== previousState[key]; - } - }, { deep: true }); - - async function _save() { - await save(unwrapReactive(currentState)); - for (const key in currentState) { - previousState[key] = copy(currentState[key]); - } - } - - function discard() { - for (const key in currentState) { - currentState[key] = copy(previousState[key]); - } - } - - return { - state: currentState, - savedState: previousState, - modifiedStates, - modified, - modifiedCount, - save: _save, - discard, - }; -} diff --git a/packages/frontend/src/scripts/use-leave-guard.ts b/packages/frontend/src/scripts/use-leave-guard.ts deleted file mode 100644 index 5f7e56e8a9..0000000000 --- a/packages/frontend/src/scripts/use-leave-guard.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Ref } from 'vue'; - -export function useLeaveGuard(enabled: Ref<boolean>) { - /* TODO - const setLeaveGuard = inject('setLeaveGuard'); - - if (setLeaveGuard) { - setLeaveGuard(async () => { - if (!enabled.value) return false; - - const { canceled } = await os.confirm({ - type: 'warning', - text: i18n.ts.leaveConfirm, - }); - - return canceled; - }); - } else { - onBeforeRouteLeave(async (to, from) => { - if (!enabled.value) return true; - - const { canceled } = await os.confirm({ - type: 'warning', - text: i18n.ts.leaveConfirm, - }); - - return !canceled; - }); - } - */ - - /* - function onBeforeLeave(ev: BeforeUnloadEvent) { - if (enabled.value) { - ev.preventDefault(); - ev.returnValue = ''; - } - } - - window.addEventListener('beforeunload', onBeforeLeave); - onUnmounted(() => { - window.removeEventListener('beforeunload', onBeforeLeave); - }); - */ -} diff --git a/packages/frontend/src/scripts/use-note-capture.ts b/packages/frontend/src/scripts/use-note-capture.ts deleted file mode 100644 index d15d9043c2..0000000000 --- a/packages/frontend/src/scripts/use-note-capture.ts +++ /dev/null @@ -1,161 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { onUnmounted, Ref, ShallowRef } from 'vue'; -import * as Misskey from 'misskey-js'; -import { useStream } from '@/stream.js'; -import { $i } from '@/account.js'; -import { misskeyApi } from './misskey-api.js'; - -export function useNoteCapture(props: { - rootEl: ShallowRef<HTMLElement | undefined>; - note: Ref<Misskey.entities.Note>; - pureNote?: Ref<Misskey.entities.Note>; - isDeletedRef: Ref<boolean>; - onReplyCallback?: (replyNote: Misskey.entities.Note) => void | Promise<void>; - onDeleteCallback?: (id: Misskey.entities.Note['id']) => void | Promise<void>; -}) { - const note = props.note; - const pureNote = props.pureNote !== undefined ? props.pureNote : props.note; - const connection = $i ? useStream() : null; - - async function onStreamNoteUpdated(noteData): Promise<void> { - const { type, id, body } = noteData; - - if ((id !== note.value.id) && (id !== pureNote.value.id)) return; - - switch (type) { - case 'replied': { - if (!props.onReplyCallback) break; - - // notes/show may throw if the current user can't see the note - try { - const replyNote = await misskeyApi('notes/show', { - noteId: body.id, - }); - - await props.onReplyCallback(replyNote); - } catch { /* empty */ } - - break; - } - - case 'reacted': { - const reaction = body.reaction; - - if (body.emoji && !(body.emoji.name in note.value.reactionEmojis)) { - note.value.reactionEmojis[body.emoji.name] = body.emoji.url; - } - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (note.value.reactions || {})[reaction] || 0; - - note.value.reactions[reaction] = currentCount + 1; - note.value.reactionCount += 1; - - if ($i && (body.userId === $i.id)) { - note.value.myReaction = reaction; - } - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (note.value.reactions || {})[reaction] || 0; - - note.value.reactions[reaction] = Math.max(0, currentCount - 1); - note.value.reactionCount = Math.max(0, note.value.reactionCount - 1); - if (note.value.reactions[reaction] === 0) delete note.value.reactions[reaction]; - - if ($i && (body.userId === $i.id)) { - note.value.myReaction = null; - } - break; - } - - case 'pollVoted': { - const choice = body.choice; - - const choices = [...note.value.poll!.choices]; - choices[choice] = { - ...choices[choice], - votes: choices[choice].votes + 1, - ...($i && (body.userId === $i.id) ? { - isVoted: true, - } : {}), - }; - - note.value.poll!.choices = choices; - break; - } - - case 'deleted': { - props.isDeletedRef.value = true; - - if (props.onDeleteCallback) await props.onDeleteCallback(id); - break; - } - - case 'updated': { - try { - const editedNote = await misskeyApi('notes/show', { - noteId: id, - }); - - const keys = new Set<string>(); - Object.keys(editedNote) - .concat(Object.keys(note.value)) - .forEach((key) => keys.add(key)); - keys.forEach((key) => { - note.value[key] = editedNote[key]; - }); - } catch { /* empty */ } - - break; - } - } - } - - function capture(withHandler = false): void { - if (connection) { - // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する - connection.send(document.body.contains(props.rootEl.value ?? null as Node | null) ? 'sr' : 's', { id: note.value.id }); - if (pureNote.value.id !== note.value.id) connection.send('s', { id: pureNote.value.id }); - if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated); - } - } - - function decapture(withHandler = false): void { - if (connection) { - connection.send('un', { - id: note.value.id, - }); - if (pureNote.value.id !== note.value.id) { - connection.send('un', { - id: pureNote.value.id, - }); - } - if (withHandler) connection.off('noteUpdated', onStreamNoteUpdated); - } - } - - function onStreamConnected() { - capture(false); - } - - capture(true); - if (connection) { - connection.on('_connected_', onStreamConnected); - } - - onUnmounted(() => { - decapture(true); - if (connection) { - connection.off('_connected_', onStreamConnected); - } - }); -} diff --git a/packages/frontend/src/scripts/use-tooltip.ts b/packages/frontend/src/scripts/use-tooltip.ts deleted file mode 100644 index a26d08cce7..0000000000 --- a/packages/frontend/src/scripts/use-tooltip.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Ref, ref, watch, onUnmounted } from 'vue'; - -export function useTooltip( - elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>, - onShow: (showing: Ref<boolean>) => void, - delay = 300, -): void { - let isHovering = false; - - // iOS(Androidも?)では、要素をタップした直後に(おせっかいで)mouseoverイベントを発火させたりするため、それを無視するためのフラグ - // 無視しないと、画面に触れてないのにツールチップが出たりし、ユーザビリティが損なわれる - // TODO: 一度でもタップすると二度とマウスでツールチップ出せなくなるのをどうにかする 定期的にfalseに戻すとか...? - let shouldIgnoreMouseover = false; - - let timeoutId: number; - - let changeShowingState: (() => void) | null; - - let autoHidingTimer; - - const open = () => { - close(); - if (!isHovering) return; - if (elRef.value == null) return; - const el = elRef.value instanceof Element ? elRef.value : elRef.value.$el; - if (!document.body.contains(el)) return; // openしようとしたときに既に元要素がDOMから消えている場合があるため - - const showing = ref(true); - onShow(showing); - changeShowingState = () => { - showing.value = false; - }; - - autoHidingTimer = window.setInterval(() => { - if (elRef.value == null || !document.body.contains(elRef.value instanceof Element ? elRef.value : elRef.value.$el)) { - if (!isHovering) return; - isHovering = false; - window.clearTimeout(timeoutId); - close(); - window.clearInterval(autoHidingTimer); - } - }, 1000); - }; - - const close = () => { - if (changeShowingState != null) { - changeShowingState(); - changeShowingState = null; - } - }; - - const onMouseover = () => { - if (isHovering) return; - if (shouldIgnoreMouseover) return; - isHovering = true; - timeoutId = window.setTimeout(open, delay); - }; - - const onMouseleave = () => { - if (!isHovering) return; - isHovering = false; - window.clearTimeout(timeoutId); - window.clearInterval(autoHidingTimer); - close(); - }; - - const onTouchstart = () => { - shouldIgnoreMouseover = true; - if (isHovering) return; - isHovering = true; - timeoutId = window.setTimeout(open, delay); - }; - - const onTouchend = () => { - if (!isHovering) return; - isHovering = false; - window.clearTimeout(timeoutId); - window.clearInterval(autoHidingTimer); - close(); - }; - - const stop = watch(elRef, () => { - if (elRef.value) { - stop(); - const el = elRef.value instanceof Element ? elRef.value : elRef.value.$el; - el.addEventListener('mouseover', onMouseover, { passive: true }); - el.addEventListener('mouseleave', onMouseleave, { passive: true }); - el.addEventListener('touchstart', onTouchstart, { passive: true }); - el.addEventListener('touchend', onTouchend, { passive: true }); - el.addEventListener('click', close, { passive: true }); - } - }, { - immediate: true, - flush: 'post', - }); - - onUnmounted(() => { - close(); - }); -} diff --git a/packages/frontend/src/scripts/warning-external-website.ts b/packages/frontend/src/scripts/warning-external-website.ts deleted file mode 100644 index 0c9b5ba806..0000000000 --- a/packages/frontend/src/scripts/warning-external-website.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { instance } from '@/instance.js'; -import { defaultStore } from '@/store.js'; -import * as os from '@/os.js'; -import MkUrlWarningDialog from '@/components/MkUrlWarningDialog.vue'; - -const isRegExp = /^\/(.+)\/(.*)$/; - -function extractHostname(maybeUrl: string): URL | null { - try { - const url = new URL(maybeUrl); - return url.host; - } catch { - return null; - } -} - -export async function warningExternalWebsite(url: string) { - const hostname = extractHostname(url); - - if (!hostname) return false; - - const isTrustedByInstance = instance.trustedLinkUrlPatterns.some(expression => { - const r = isRegExp.exec(expression); - - if (r) { - return new RegExp(r[1], r[2]).test(url); - } else if (expression.includes(' ')) { - return expression.split(' ').every(keyword => url.includes(keyword)); - } else { - return `.${hostname}`.endsWith(`.${expression}`); - } - }); - - const isTrustedByUser = defaultStore.reactiveState.trustedDomains.value.includes(hostname); - const isDisabledByUser = !defaultStore.reactiveState.warnExternalUrl.value; - - if (!isTrustedByInstance && !isTrustedByUser && !isDisabledByUser) { - const confirm = await new Promise<{ canceled: boolean }>(resolve => { - const { dispose } = os.popup(MkUrlWarningDialog, { - url, - }, { - done: result => { - resolve(result ?? { canceled: true }); - }, - closed: () => dispose(), - }); - }); - - if (confirm.canceled) return false; - - return window.open(url, '_blank', 'nofollow noopener popup=false'); - } - - return window.open(url, '_blank', 'nofollow noopener popup=false'); -} |