diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-06-11 19:31:03 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-06-11 19:31:03 +0900 |
| commit | 182a1bf653ecfbcf76e4530b3077c6252b0d4827 (patch) | |
| tree | 45d1472747d4cac017e96616f844292f5785ccdd /packages/client/src/scripts | |
| parent | 12.110.1 (diff) | |
| parent | 12.111.0 (diff) | |
| download | sharkey-182a1bf653ecfbcf76e4530b3077c6252b0d4827.tar.gz sharkey-182a1bf653ecfbcf76e4530b3077c6252b0d4827.tar.bz2 sharkey-182a1bf653ecfbcf76e4530b3077c6252b0d4827.zip | |
Merge pull request #8783 from misskey-dev/develop
Release: 12.111.0
Diffstat (limited to 'packages/client/src/scripts')
29 files changed, 294 insertions, 134 deletions
diff --git a/packages/client/src/scripts/2fa.ts b/packages/client/src/scripts/2fa.ts index 00363cffa6..d1b9581e72 100644 --- a/packages/client/src/scripts/2fa.ts +++ b/packages/client/src/scripts/2fa.ts @@ -1,11 +1,11 @@ -export function byteify(data: string, encoding: 'ascii' | 'base64' | 'hex') { +export function byteify(string: string, encoding: 'ascii' | 'base64' | 'hex') { switch (encoding) { case 'ascii': - return Uint8Array.from(data, c => c.charCodeAt(0)); + return Uint8Array.from(string, c => c.charCodeAt(0)); case 'base64': return Uint8Array.from( atob( - data + string .replace(/-/g, '+') .replace(/_/g, '/') ), @@ -13,7 +13,7 @@ export function byteify(data: string, encoding: 'ascii' | 'base64' | 'hex') { ); case 'hex': return new Uint8Array( - data + string .match(/.{1,2}/g) .map(byte => parseInt(byte, 16)) ); diff --git a/packages/client/src/scripts/autocomplete.ts b/packages/client/src/scripts/autocomplete.ts index f4a3a4c0fc..8d9bdee8f5 100644 --- a/packages/client/src/scripts/autocomplete.ts +++ b/packages/client/src/scripts/autocomplete.ts @@ -1,5 +1,5 @@ -import { nextTick, Ref, ref } from 'vue'; -import * as getCaretCoordinates from 'textarea-caret'; +import { nextTick, Ref, ref, defineAsyncComponent } from 'vue'; +import getCaretCoordinates from 'textarea-caret'; import { toASCII } from 'punycode/'; import { popup } from '@/os'; @@ -74,21 +74,21 @@ export class Autocomplete { emojiIndex, mfmTagIndex); - if (max == -1) { + if (max === -1) { this.close(); return; } - const isMention = mentionIndex != -1; - const isHashtag = hashtagIndex != -1; - const isMfmTag = mfmTagIndex != -1; - const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); + const isMention = mentionIndex !== -1; + const isHashtag = hashtagIndex !== -1; + const isMfmTag = mfmTagIndex !== -1; + const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); let opened = false; if (isMention) { const username = text.substr(mentionIndex + 1); - if (username != '' && username.match(/^[a-zA-Z0-9_]+$/)) { + if (username !== '' && username.match(/^[a-zA-Z0-9_]+$/)) { this.open('user', username); opened = true; } else if (username === '') { @@ -130,7 +130,7 @@ export class Autocomplete { * サジェストを提示します。 */ private async open(type: string, q: string | null) { - if (type != this.currentType) { + if (type !== this.currentType) { this.close(); } if (this.opening) return; @@ -157,7 +157,7 @@ export class Autocomplete { const _y = ref(y); const _q = ref(q); - const { dispose } = await popup(import('@/components/autocomplete.vue'), { + const { dispose } = await popup(defineAsyncComponent(() => import('@/components/autocomplete.vue')), { textarea: this.textarea, close: this.close, type: type, @@ -201,7 +201,7 @@ export class Autocomplete { const caret = this.textarea.selectionStart; - if (type == 'user') { + if (type === 'user') { const source = this.text; const before = source.substr(0, caret); @@ -219,7 +219,7 @@ export class Autocomplete { const pos = trimmedBefore.length + (acct.length + 2); this.textarea.setSelectionRange(pos, pos); }); - } else if (type == 'hashtag') { + } else if (type === 'hashtag') { const source = this.text; const before = source.substr(0, caret); @@ -235,7 +235,7 @@ export class Autocomplete { const pos = trimmedBefore.length + (value.length + 2); this.textarea.setSelectionRange(pos, pos); }); - } else if (type == 'emoji') { + } else if (type === 'emoji') { const source = this.text; const before = source.substr(0, caret); @@ -251,7 +251,7 @@ export class Autocomplete { const pos = trimmedBefore.length + value.length; this.textarea.setSelectionRange(pos, pos); }); - } else if (type == 'mfmTag') { + } else if (type === 'mfmTag') { const source = this.text; const before = source.substr(0, caret); diff --git a/packages/client/src/scripts/contains.ts b/packages/client/src/scripts/contains.ts index 770bda63bb..256e09d293 100644 --- a/packages/client/src/scripts/contains.ts +++ b/packages/client/src/scripts/contains.ts @@ -2,7 +2,7 @@ export default (parent, child, checkSame = true) => { if (checkSame && parent === child) return true; let node = child.parentNode; while (node) { - if (node == parent) return true; + if (node === parent) return true; node = node.parentNode; } return false; diff --git a/packages/client/src/scripts/emojilist.ts b/packages/client/src/scripts/emojilist.ts index bd8689e4f8..4196170d24 100644 --- a/packages/client/src/scripts/emojilist.ts +++ b/packages/client/src/scripts/emojilist.ts @@ -8,4 +8,4 @@ export type UnicodeEmojiDef = { } // initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb -export const emojilist = require('../emojilist.json') as UnicodeEmojiDef[]; +export const emojilist = (await import('../emojilist.json')).default as UnicodeEmojiDef[]; diff --git a/packages/client/src/scripts/extract-avg-color-from-blurhash.ts b/packages/client/src/scripts/extract-avg-color-from-blurhash.ts index 123ab7a06d..af517f2672 100644 --- a/packages/client/src/scripts/extract-avg-color-from-blurhash.ts +++ b/packages/client/src/scripts/extract-avg-color-from-blurhash.ts @@ -1,5 +1,5 @@ export function extractAvgColorFromBlurhash(hash: string) { - return typeof hash == 'string' + return typeof hash === 'string' ? '#' + [...hash.slice(2, 6)] .map(x => '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~'.indexOf(x)) .reduce((a, c) => a * 83 + c, 0) diff --git a/packages/client/src/scripts/format-time-string.ts b/packages/client/src/scripts/format-time-string.ts index bfb2c397ae..fb4718c007 100644 --- a/packages/client/src/scripts/format-time-string.ts +++ b/packages/client/src/scripts/format-time-string.ts @@ -13,7 +13,7 @@ const defaultLocaleStringFormats: {[index: string]: string} = { 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]}); + return date.toLocaleString(window.navigator.language, { [kind]: option ? option : defaultLocaleStringFormats[kind] }); } else { return match; } @@ -24,8 +24,8 @@ 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(/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)) diff --git a/packages/client/src/scripts/get-account-from-id.ts b/packages/client/src/scripts/get-account-from-id.ts index ba3adceecc..1da897f176 100644 --- a/packages/client/src/scripts/get-account-from-id.ts +++ b/packages/client/src/scripts/get-account-from-id.ts @@ -3,5 +3,5 @@ import { get } from '@/scripts/idb-proxy'; 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(e => e.id === id); + return accounts.find(account => account.id === id); } diff --git a/packages/client/src/scripts/get-md5.ts b/packages/client/src/scripts/get-md5.ts deleted file mode 100644 index b002d762b1..0000000000 --- a/packages/client/src/scripts/get-md5.ts +++ /dev/null @@ -1,10 +0,0 @@ -// スクリプトサイズがデカい -//import * as crypto from 'crypto'; - -export default (data: ArrayBuffer) => { - //const buf = new Buffer(data); - //const hash = crypto.createHash('md5'); - //hash.update(buf); - //return hash.digest('hex'); - return ''; -}; diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index b19656d3cc..78749ad6bb 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -1,4 +1,4 @@ -import { Ref } from 'vue'; +import { defineAsyncComponent, Ref } from 'vue'; import * as misskey from 'misskey-js'; import { $i } from '@/account'; import { i18n } from '@/i18n'; @@ -22,7 +22,7 @@ export function getNoteMenu(props: { props.note.poll == null ); - let appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note; + const appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note; function del(): void { os.confirm({ @@ -83,8 +83,8 @@ export function getNoteMenu(props: { function togglePin(pin: boolean): void { os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { noteId: appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { + }, undefined, null, res => { + if (res.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { os.alert({ type: 'error', text: i18n.ts.pinLimitExceeded @@ -209,7 +209,7 @@ export function getNoteMenu(props: { text: i18n.ts.clip, action: () => clip() }, - (appearNote.userId != $i.id) ? statePromise.then(state => state.isWatching ? { + (appearNote.userId !== $i.id) ? statePromise.then(state => state.isWatching ? { icon: 'fas fa-eye-slash', text: i18n.ts.unwatch, action: () => toggleWatch(false) @@ -227,7 +227,7 @@ export function getNoteMenu(props: { text: i18n.ts.muteThread, action: () => toggleThreadMute(true) }), - appearNote.userId == $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { + appearNote.userId === $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { icon: 'fas fa-thumbtack', text: i18n.ts.unpin, action: () => togglePin(false) @@ -246,14 +246,14 @@ export function getNoteMenu(props: { }] : [] ),*/ - ...(appearNote.userId != $i.id ? [ + ...(appearNote.userId !== $i.id ? [ null, { icon: 'fas fa-exclamation-circle', text: i18n.ts.reportAbuse, action: () => { const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), { user: appearNote.user, initialComment: `Note: ${u}\n-----\n` }, {}, 'closed'); @@ -261,9 +261,9 @@ export function getNoteMenu(props: { }] : [] ), - ...(appearNote.userId == $i.id || $i.isModerator || $i.isAdmin ? [ + ...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [ null, - appearNote.userId == $i.id ? { + appearNote.userId === $i.id ? { icon: 'fas fa-edit', text: i18n.ts.deleteAndEdit, action: delEdit diff --git a/packages/client/src/scripts/get-note-summary.ts b/packages/client/src/scripts/get-note-summary.ts index 54b8d109d6..d57e1c3029 100644 --- a/packages/client/src/scripts/get-note-summary.ts +++ b/packages/client/src/scripts/get-note-summary.ts @@ -24,7 +24,7 @@ export const getNoteSummary = (note: misskey.entities.Note): string => { } // ファイルが添付されているとき - if ((note.files || []).length != 0) { + if ((note.files || []).length !== 0) { summary += ` (${i18n.t('withNFiles', { n: note.files.length })})`; } diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts index 192d14b83e..091338efd6 100644 --- a/packages/client/src/scripts/get-user-menu.ts +++ b/packages/client/src/scripts/get-user-menu.ts @@ -6,6 +6,7 @@ import * as os from '@/os'; import { userActions } from '@/store'; import { router } from '@/router'; import { $i, iAmModerator } from '@/account'; +import { defineAsyncComponent } from 'vue'; export function getUserMenu(user) { const meId = $i ? $i.id : null; @@ -127,7 +128,7 @@ export function getUserMenu(user) { } function reportAbuse() { - os.popup(import('@/components/abuse-report-window.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), { user: user, }, {}, 'closed'); } @@ -147,7 +148,7 @@ export function getUserMenu(user) { userId: user.id }).then(() => { user.isFollowed = !user.isFollowed; - }) + }); } let menu = [{ @@ -168,7 +169,7 @@ export function getUserMenu(user) { action: () => { os.post({ specified: user }); } - }, meId != user.id ? { + }, meId !== user.id ? { type: 'link', icon: 'fas fa-comments', text: i18n.ts.startMessaging, @@ -177,13 +178,13 @@ export function getUserMenu(user) { icon: 'fas fa-list-ul', text: i18n.ts.addToList, action: pushList - }, meId != user.id ? { + }, meId !== user.id ? { icon: 'fas fa-users', text: i18n.ts.inviteToGroup, action: inviteGroup } : undefined] as any; - if ($i && meId != user.id) { + if ($i && meId !== user.id) { menu = menu.concat([null, { icon: user.isMuted ? 'fas fa-eye' : 'fas fa-eye-slash', text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute, diff --git a/packages/client/src/scripts/get-user-name.ts b/packages/client/src/scripts/get-user-name.ts new file mode 100644 index 0000000000..d499ea0203 --- /dev/null +++ b/packages/client/src/scripts/get-user-name.ts @@ -0,0 +1,3 @@ +export default function(user: { name?: string | null, username: string }): string { + return user.name || user.username; +} diff --git a/packages/client/src/scripts/hotkey.ts b/packages/client/src/scripts/hotkey.ts index 2b3f491fd8..fd9c74f6c8 100644 --- a/packages/client/src/scripts/hotkey.ts +++ b/packages/client/src/scripts/hotkey.ts @@ -53,34 +53,34 @@ const parseKeymap = (keymap: Keymap) => Object.entries(keymap).map(([patterns, c const ignoreElemens = ['input', 'textarea']; -function match(e: KeyboardEvent, patterns: Action['patterns']): boolean { - const key = e.code.toLowerCase(); +function match(ev: KeyboardEvent, patterns: Action['patterns']): boolean { + const key = ev.code.toLowerCase(); return patterns.some(pattern => pattern.which.includes(key) && - pattern.ctrl === e.ctrlKey && - pattern.shift === e.shiftKey && - pattern.alt === e.altKey && - !e.metaKey + pattern.ctrl === ev.ctrlKey && + pattern.shift === ev.shiftKey && + pattern.alt === ev.altKey && + !ev.metaKey ); } export const makeHotkey = (keymap: Keymap) => { const actions = parseKeymap(keymap); - return (e: KeyboardEvent) => { + return (ev: KeyboardEvent) => { if (document.activeElement) { if (ignoreElemens.some(el => document.activeElement!.matches(el))) return; if (document.activeElement.attributes['contenteditable']) return; } for (const action of actions) { - const matched = match(e, action.patterns); + const matched = match(ev, action.patterns); if (matched) { - if (!action.allowRepeat && e.repeat) return; + if (!action.allowRepeat && ev.repeat) return; - e.preventDefault(); - e.stopPropagation(); - action.callback(e); + ev.preventDefault(); + ev.stopPropagation(); + action.callback(ev); break; } } diff --git a/packages/client/src/scripts/hpml/evaluator.ts b/packages/client/src/scripts/hpml/evaluator.ts index 6329c0860e..8106687b61 100644 --- a/packages/client/src/scripts/hpml/evaluator.ts +++ b/packages/client/src/scripts/hpml/evaluator.ts @@ -36,7 +36,7 @@ export class Hpml { if (this.opts.enableAiScript) { this.aiscript = markRaw(new AiScript({ ...createAiScriptEnv({ storageKey: 'pages:' + this.page.id - }), ...initAiLib(this)}, { + }), ...initAiLib(this) }, { in: (q) => { return new Promise(ok => { os.inputText({ @@ -85,7 +85,7 @@ export class Hpml { public eval() { try { this.vars.value = this.evaluateVars(); - } catch (e) { + } catch (err) { //this.onError(e); } } @@ -103,7 +103,7 @@ export class Hpml { public callAiScript(fn: string) { try { if (this.aiscript) this.aiscript.execFn(this.aiscript.scope.get(fn), []); - } catch (e) {} + } catch (err) {} } @autobind @@ -185,7 +185,7 @@ export class Hpml { if (this.aiscript) { try { return utils.valToJs(this.aiscript.scope.get(expr.value)); - } catch (e) { + } catch (err) { return null; } } else { @@ -194,7 +194,7 @@ export class Hpml { } // Define user function - if (expr.type == 'fn') { + if (expr.type === 'fn') { return { slots: expr.value.slots.map(x => x.name), exec: (slotArg: Record<string, any>) => { diff --git a/packages/client/src/scripts/hpml/lib.ts b/packages/client/src/scripts/hpml/lib.ts index 2a1ac73a40..01a44ffcdf 100644 --- a/packages/client/src/scripts/hpml/lib.ts +++ b/packages/client/src/scripts/hpml/lib.ts @@ -1,9 +1,9 @@ -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; import { Hpml } from './evaluator'; import { values, utils } from '@syuilo/aiscript'; import { Fn, HpmlScope } from '.'; import { Expr } from './expr'; -import * as seedrandom from 'seedrandom'; +import seedrandom from 'seedrandom'; /* TODO: https://www.chartjs.org/docs/latest/configuration/canvas-background.html#color // https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs diff --git a/packages/client/src/scripts/idb-proxy.ts b/packages/client/src/scripts/idb-proxy.ts index 5f76ae30bb..d462a0d7ce 100644 --- a/packages/client/src/scripts/idb-proxy.ts +++ b/packages/client/src/scripts/idb-proxy.ts @@ -13,8 +13,8 @@ let idbAvailable = typeof window !== 'undefined' ? !!window.indexedDB : true; if (idbAvailable) { try { await iset('idb-test', 'test'); - } catch (e) { - console.error('idb error', e); + } catch (err) { + console.error('idb error', err); idbAvailable = false; } } diff --git a/packages/client/src/scripts/initialize-sw.ts b/packages/client/src/scripts/initialize-sw.ts index d6dbd5dbd4..7bacfbdf00 100644 --- a/packages/client/src/scripts/initialize-sw.ts +++ b/packages/client/src/scripts/initialize-sw.ts @@ -4,26 +4,26 @@ import { api } from '@/os'; import { lang } from '@/config'; export async function initializeSw() { - if (instance.swPublickey && - ('serviceWorker' in navigator) && - ('PushManager' in window) && - $i && $i.token) { - navigator.serviceWorker.register(`/sw.js`); + if (!('serviceWorker' in navigator)) return; - navigator.serviceWorker.ready.then(registration => { - registration.active?.postMessage({ - msg: 'initialize', - lang, - }); + navigator.serviceWorker.register(`/sw.js`, { scope: '/', type: 'classic' }); + navigator.serviceWorker.ready.then(registration => { + registration.active?.postMessage({ + msg: 'initialize', + lang, + }); + + if (instance.swPublickey && ('PushManager' in window) && $i && $i.token) { // SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(instance.swPublickey) - }).then(subscription => { + }) + .then(subscription => { function encode(buffer: ArrayBuffer | null) { return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))); } - + // Register api('sw/register', { endpoint: subscription.endpoint, @@ -37,15 +37,15 @@ export async function initializeSw() { if (err.name === 'NotAllowedError') { return; } - + // 違うapplicationServerKey (または gcm_sender_id)のサブスクリプションが // 既に存在していることが原因でエラーになった可能性があるので、 // そのサブスクリプションを解除しておく const subscription = await registration.pushManager.getSubscription(); if (subscription) subscription.unsubscribe(); }); - }); - } + } + }); } /** diff --git a/packages/client/src/scripts/lookup-user.ts b/packages/client/src/scripts/lookup-user.ts index 8de5c84ce8..2d00e51621 100644 --- a/packages/client/src/scripts/lookup-user.ts +++ b/packages/client/src/scripts/lookup-user.ts @@ -25,12 +25,12 @@ export async function lookupUser() { _notFound = true; } }; - usernamePromise.then(show).catch(e => { - if (e.code === 'NO_SUCH_USER') { + usernamePromise.then(show).catch(err => { + if (err.code === 'NO_SUCH_USER') { notFound(); } }); - idPromise.then(show).catch(e => { + idPromise.then(show).catch(err => { notFound(); }); } diff --git a/packages/client/src/scripts/navigate.ts b/packages/client/src/scripts/navigate.ts new file mode 100644 index 0000000000..08b891ec5b --- /dev/null +++ b/packages/client/src/scripts/navigate.ts @@ -0,0 +1,34 @@ +import { inject } from 'vue'; +import { router } from '@/router'; +import { defaultStore } from '@/store'; + +export type Navigate = (path: string, record?: boolean) => void; + +export class MisskeyNavigator { + public readonly navHook: Navigate | null = null; + public readonly sideViewHook: Navigate | null = null; + + // It should be constructed during vue creating in order for inject function to work + constructor() { + this.navHook = inject<Navigate | null>('navHook', null); + this.sideViewHook = inject<Navigate | null>('sideViewHook', null); + } + + // Use this method instead of router.push() + public push(path: string, record = true) { + if (this.navHook) { + this.navHook(path, record); + } else { + if (defaultStore.state.defaultSideView && this.sideViewHook && path !== '/') { + return this.sideViewHook(path, record); + } + + if (router.currentRoute.value.path === path) { + window.scroll({ top: 0, behavior: 'smooth' }); + } else { + if (record) router.push(path); + else router.replace(path); + } + } + } +} diff --git a/packages/client/src/scripts/physics.ts b/packages/client/src/scripts/physics.ts index 36e476b6f9..9e657906c2 100644 --- a/packages/client/src/scripts/physics.ts +++ b/packages/client/src/scripts/physics.ts @@ -41,9 +41,9 @@ export function physics(container: HTMLElement) { const groundThickness = 1024; const ground = Matter.Bodies.rectangle(containerCenterX, containerHeight + (groundThickness / 2), containerWidth, groundThickness, { - isStatic: true, - restitution: 0.1, - friction: 2 + isStatic: true, + restitution: 0.1, + friction: 2 }); //const wallRight = Matter.Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, wallopts); diff --git a/packages/client/src/scripts/please-login.ts b/packages/client/src/scripts/please-login.ts index aeaafa124b..e21a6d2ed3 100644 --- a/packages/client/src/scripts/please-login.ts +++ b/packages/client/src/scripts/please-login.ts @@ -1,14 +1,21 @@ +import { defineAsyncComponent } from 'vue'; import { $i } from '@/account'; import { i18n } from '@/i18n'; -import { alert } from '@/os'; +import { popup } from '@/os'; -export function pleaseLogin() { +export function pleaseLogin(path?: string) { if ($i) return; - alert({ - title: i18n.ts.signinRequired, - text: null - }); + popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), { + autoSet: true, + message: i18n.ts.signinRequired + }, { + cancelled: () => { + if (path) { + window.location.href = path; + } + }, + }, 'closed'); throw new Error('signin required'); } diff --git a/packages/client/src/scripts/popout.ts b/packages/client/src/scripts/popout.ts index b8286a2a76..580031d0a3 100644 --- a/packages/client/src/scripts/popout.ts +++ b/packages/client/src/scripts/popout.ts @@ -1,8 +1,9 @@ import * as config from '@/config'; +import { appendQuery } from './url'; export function popout(path: string, w?: HTMLElement) { - let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + "/" + path; - url += '?zen'; + 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); diff --git a/packages/client/src/scripts/reaction-picker.ts b/packages/client/src/scripts/reaction-picker.ts index 3ac1f63430..b7699cae4a 100644 --- a/packages/client/src/scripts/reaction-picker.ts +++ b/packages/client/src/scripts/reaction-picker.ts @@ -1,4 +1,4 @@ -import { Ref, ref } from 'vue'; +import { defineAsyncComponent, Ref, ref } from 'vue'; import { popup } from '@/os'; class ReactionPicker { @@ -12,7 +12,7 @@ class ReactionPicker { } public async init() { - await popup(import('@/components/emoji-picker-dialog.vue'), { + await popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), { src: this.src, asReactionPicker: true, manualShowing: this.manualShowing diff --git a/packages/client/src/scripts/select-file.ts b/packages/client/src/scripts/select-file.ts index 23df4edf54..461d613b42 100644 --- a/packages/client/src/scripts/select-file.ts +++ b/packages/client/src/scripts/select-file.ts @@ -4,6 +4,7 @@ import { stream } from '@/stream'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { DriveFile } from 'misskey-js/built/entities'; +import { uploadFile } from '@/scripts/upload'; function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> { return new Promise((res, rej) => { @@ -14,14 +15,14 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv input.type = 'file'; input.multiple = multiple; input.onchange = () => { - const promises = Array.from(input.files).map(file => os.upload(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value)); + const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value)); Promise.all(promises).then(driveFiles => { res(multiple ? driveFiles : driveFiles[0]); - }).catch(e => { + }).catch(err => { os.alert({ type: 'error', - text: e + text: err }); }); @@ -53,9 +54,9 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv const marker = Math.random().toString(); // TODO: UUIDとか使う const connection = stream.useChannel('main'); - connection.on('urlUploadFinished', data => { - if (data.marker === marker) { - res(multiple ? [data.file] : data.file); + connection.on('urlUploadFinished', urlResponse => { + if (urlResponse.marker === marker) { + res(multiple ? [urlResponse.file] : urlResponse.file); connection.dispose(); } }); diff --git a/packages/client/src/scripts/theme-editor.ts b/packages/client/src/scripts/theme-editor.ts index 3d69d2836a..2c917e280d 100644 --- a/packages/client/src/scripts/theme-editor.ts +++ b/packages/client/src/scripts/theme-editor.ts @@ -1,4 +1,4 @@ -import { v4 as uuid} from 'uuid'; +import { v4 as uuid } from 'uuid'; import { themeProps, Theme } from './theme'; diff --git a/packages/client/src/scripts/theme.ts b/packages/client/src/scripts/theme.ts index 2cb78fae5c..dec9fb355c 100644 --- a/packages/client/src/scripts/theme.ts +++ b/packages/client/src/scripts/theme.ts @@ -1,5 +1,6 @@ +import { ref } from 'vue'; import { globalEvents } from '@/events'; -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; export type Theme = { id: string; @@ -10,30 +11,38 @@ export type Theme = { props: Record<string, string>; }; -export const lightTheme: Theme = require('@/themes/_light.json5'); -export const darkTheme: Theme = require('@/themes/_dark.json5'); +import lightTheme from '@/themes/_light.json5'; +import darkTheme from '@/themes/_dark.json5'; export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); -export const builtinThemes = [ - require('@/themes/l-light.json5'), - require('@/themes/l-coffee.json5'), - require('@/themes/l-apricot.json5'), - require('@/themes/l-rainy.json5'), - require('@/themes/l-vivid.json5'), - require('@/themes/l-cherry.json5'), - require('@/themes/l-sushi.json5'), +export const getBuiltinThemes = () => Promise.all( + [ + 'l-light', + 'l-coffee', + 'l-apricot', + 'l-rainy', + 'l-vivid', + 'l-cherry', + 'l-sushi', - require('@/themes/d-dark.json5'), - require('@/themes/d-persimmon.json5'), - require('@/themes/d-astro.json5'), - require('@/themes/d-future.json5'), - require('@/themes/d-botanical.json5'), - require('@/themes/d-cherry.json5'), - require('@/themes/d-ice.json5'), - require('@/themes/d-pumpkin.json5'), - require('@/themes/d-black.json5'), -] as Theme[]; + 'd-dark', + 'd-persimmon', + 'd-astro', + 'd-future', + 'd-botanical', + 'd-cherry', + 'd-ice', + 'd-pumpkin', + 'd-black', + ].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; +}; let timeout = null; diff --git a/packages/client/src/scripts/upload.ts b/packages/client/src/scripts/upload.ts new file mode 100644 index 0000000000..2f7b30b58d --- /dev/null +++ b/packages/client/src/scripts/upload.ts @@ -0,0 +1,114 @@ +import { reactive, ref } from 'vue'; +import { defaultStore } from '@/store'; +import { apiUrl } from '@/config'; +import * as Misskey from 'misskey-js'; +import { $i } from '@/account'; +import { readAndCompressImage } from 'browser-image-resizer'; +import { alert } from '@/os'; + +type Uploading = { + id: string; + name: string; + progressMax: number | undefined; + progressValue: number | undefined; + img: string; +}; +export const uploads = ref<Uploading[]>([]); + +const compressTypeMap = { + 'image/jpeg': { quality: 0.85, mimeType: 'image/jpeg' }, + 'image/webp': { quality: 0.85, mimeType: 'image/jpeg' }, + 'image/svg+xml': { quality: 1, mimeType: 'image/png' }, +} as const; + +const mimeTypeMap = { + 'image/webp': 'webp', + 'image/jpeg': 'jpg', + 'image/png': 'png', +} as const; + +export function uploadFile( + file: File, + folder?: any, + name?: string, + keepOriginal: boolean = defaultStore.state.keepOriginalUploading +): Promise<Misskey.entities.DriveFile> { + if (folder && typeof folder === 'object') folder = folder.id; + + return new Promise((resolve, reject) => { + const id = Math.random().toString(); + + const reader = new FileReader(); + reader.onload = async (ev) => { + const ctx = reactive<Uploading>({ + id: id, + name: name || file.name || 'untitled', + progressMax: undefined, + progressValue: undefined, + img: window.URL.createObjectURL(file) + }); + + uploads.value.push(ctx); + + let resizedImage: any; + if (!keepOriginal && file.type in compressTypeMap) { + const imgConfig = compressTypeMap[file.type]; + + const config = { + maxWidth: 2048, + maxHeight: 2048, + debug: true, + ...imgConfig, + }; + + try { + resizedImage = await readAndCompressImage(file, config); + ctx.name = file.type !== imgConfig.mimeType ? `${ctx.name}.${mimeTypeMap[compressTypeMap[file.type].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) => { + if (xhr.status !== 200 || ev.target == null || ev.target.response == null) { + // TODO: 消すのではなくて再送できるようにしたい + uploads.value = uploads.value.filter(x => x.id !== id); + + 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); + }; + + 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/client/src/scripts/url.ts b/packages/client/src/scripts/url.ts index c7f2b7c1e7..542b00e0f0 100644 --- a/packages/client/src/scripts/url.ts +++ b/packages/client/src/scripts/url.ts @@ -4,7 +4,7 @@ export function query(obj: {}): string { .reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>); return Object.entries(params) - .map((e) => `${e[0]}=${encodeURIComponent(e[1])}`) + .map((p) => `${p[0]}=${encodeURIComponent(p[1])}`) .join('&'); } diff --git a/packages/client/src/scripts/use-note-capture.ts b/packages/client/src/scripts/use-note-capture.ts index b2a96062c7..f1f976693e 100644 --- a/packages/client/src/scripts/use-note-capture.ts +++ b/packages/client/src/scripts/use-note-capture.ts @@ -11,8 +11,8 @@ export function useNoteCapture(props: { const note = props.note; const connection = $i ? stream : null; - function onStreamNoteUpdated(data): void { - const { type, id, body } = data; + function onStreamNoteUpdated(noteData): void { + const { type, id, body } = noteData; if (id !== note.value.id) return; |