diff options
| author | tamaina <tamaina@hotmail.co.jp> | 2022-06-21 07:49:52 +0000 |
|---|---|---|
| committer | tamaina <tamaina@hotmail.co.jp> | 2022-06-21 07:49:52 +0000 |
| commit | f33654fb9ae562a43745b612a3007c248d988f2b (patch) | |
| tree | 25b8599f7d28bf02cd0d3970b735d9db5e84742b /packages/client/src/scripts | |
| parent | Merge branch 'develop' into pizzax-indexeddb (diff) | |
| parent | refactor(client): use composition api (diff) | |
| download | sharkey-f33654fb9ae562a43745b612a3007c248d988f2b.tar.gz sharkey-f33654fb9ae562a43745b612a3007c248d988f2b.tar.bz2 sharkey-f33654fb9ae562a43745b612a3007c248d988f2b.zip | |
Merge branch 'develop' into pizzax-indexeddb
Diffstat (limited to 'packages/client/src/scripts')
| -rw-r--r-- | packages/client/src/scripts/format-time-string.ts | 6 | ||||
| -rw-r--r-- | packages/client/src/scripts/get-note-menu.ts | 44 | ||||
| -rw-r--r-- | packages/client/src/scripts/get-user-menu.ts | 60 | ||||
| -rw-r--r-- | packages/client/src/scripts/hpml/evaluator.ts | 2 | ||||
| -rw-r--r-- | packages/client/src/scripts/navigate.ts | 34 | ||||
| -rw-r--r-- | packages/client/src/scripts/page-metadata.ts | 41 | ||||
| -rw-r--r-- | packages/client/src/scripts/physics.ts | 6 | ||||
| -rw-r--r-- | packages/client/src/scripts/popout.ts | 5 | ||||
| -rw-r--r-- | packages/client/src/scripts/scroll.ts | 15 | ||||
| -rw-r--r-- | packages/client/src/scripts/search.ts | 16 | ||||
| -rw-r--r-- | packages/client/src/scripts/theme-editor.ts | 2 | ||||
| -rw-r--r-- | packages/client/src/scripts/theme.ts | 49 | ||||
| -rw-r--r-- | packages/client/src/scripts/twemoji-base.ts | 11 | ||||
| -rw-r--r-- | packages/client/src/scripts/use-leave-guard.ts | 1 |
14 files changed, 182 insertions, 110 deletions
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-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index aeb09ef97a..283c90362c 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -1,4 +1,4 @@ -import { defineAsyncComponent, Ref } from 'vue'; +import { defineAsyncComponent, Ref, inject } from 'vue'; import * as misskey from 'misskey-js'; import { $i } from '@/account'; import { i18n } from '@/i18n'; @@ -14,6 +14,8 @@ export function getNoteMenu(props: { menuButton: Ref<HTMLElement>; translation: Ref<any>; translating: Ref<boolean>; + isDeleted: Ref<boolean>; + currentClipPage?: Ref<misskey.entities.Clip>; }) { const isRenote = ( props.note.renote != null && @@ -22,7 +24,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({ @@ -125,12 +127,37 @@ export function getNoteMenu(props: { }, null, ...clips.map(clip => ({ text: clip.name, action: () => { - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); + os.promiseDialog( + os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }), + null, + async (err) => { + if (err.id === '734806c4-542c-463a-9311-15c512803965') { + const confirm = await os.confirm({ + type: 'warning', + text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }), + }); + if (!confirm.canceled) { + os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id }); + if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true; + } + } else { + os.alert({ + type: 'error', + text: err.message + '\n' + err.id, + }); + } + } + ); } }))], props.menuButton.value, { }).then(focus); } + async function unclip(): Promise<void> { + os.apiWithDialog('clips/remove-note', { clipId: props.currentClipPage.value.id, noteId: appearNote.id }); + props.isDeleted.value = true; + } + async function promote(): Promise<void> { const { canceled, result: days } = await os.inputNumber({ title: i18n.ts.numberOfDays, @@ -169,7 +196,16 @@ export function getNoteMenu(props: { noteId: appearNote.id }); - menu = [{ + menu = [ + ...( + props.currentClipPage?.value.userId === $i.id ? [{ + icon: 'fas fa-circle-minus', + text: i18n.ts.unclip, + danger: true, + action: unclip, + }, null] : [] + ), + { icon: 'fas fa-copy', text: i18n.ts.copyContent, action: copyContent diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts index 1d2b761117..25bcd90e9f 100644 --- a/packages/client/src/scripts/get-user-menu.ts +++ b/packages/client/src/scripts/get-user-menu.ts @@ -1,12 +1,12 @@ +import * as Acct from 'misskey-js/built/acct'; +import { defineAsyncComponent } from 'vue'; import { i18n } from '@/i18n'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { host } from '@/config'; -import * as Acct from 'misskey-js/built/acct'; import * as os from '@/os'; import { userActions } from '@/store'; -import { router } from '@/router'; import { $i, iAmModerator } from '@/account'; -import { defineAsyncComponent } from 'vue'; +import { mainRouter } from '@/router'; export function getUserMenu(user) { const meId = $i ? $i.id : null; @@ -17,20 +17,20 @@ export function getUserMenu(user) { if (lists.length === 0) { os.alert({ type: 'error', - text: i18n.ts.youHaveNoLists + text: i18n.ts.youHaveNoLists, }); return; } const { canceled, result: listId } = await os.select({ title: t, items: lists.map(list => ({ - value: list.id, text: list.name - })) + value: list.id, text: list.name, + })), }); if (canceled) return; os.apiWithDialog('users/lists/push', { listId: listId, - userId: user.id + userId: user.id, }); } @@ -39,20 +39,20 @@ export function getUserMenu(user) { if (groups.length === 0) { os.alert({ type: 'error', - text: i18n.ts.youHaveNoGroups + text: i18n.ts.youHaveNoGroups, }); return; } const { canceled, result: groupId } = await os.select({ title: i18n.ts.group, items: groups.map(group => ({ - value: group.id, text: group.name - })) + value: group.id, text: group.name, + })), }); if (canceled) return; os.apiWithDialog('users/groups/invite', { groupId: groupId, - userId: user.id + userId: user.id, }); } @@ -101,7 +101,7 @@ export function getUserMenu(user) { if (!await getConfirmed(user.isBlocking ? i18n.ts.unblockConfirm : i18n.ts.blockConfirm)) return; os.apiWithDialog(user.isBlocking ? 'blocking/delete' : 'blocking/create', { - userId: user.id + userId: user.id, }).then(() => { user.isBlocking = !user.isBlocking; }); @@ -111,7 +111,7 @@ export function getUserMenu(user) { if (!await getConfirmed(i18n.t(user.isSilenced ? 'unsilenceConfirm' : 'silenceConfirm'))) return; os.apiWithDialog(user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', { - userId: user.id + userId: user.id, }).then(() => { user.isSilenced = !user.isSilenced; }); @@ -121,7 +121,7 @@ export function getUserMenu(user) { if (!await getConfirmed(i18n.t(user.isSuspended ? 'unsuspendConfirm' : 'suspendConfirm'))) return; os.apiWithDialog(user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', { - userId: user.id + userId: user.id, }).then(() => { user.isSuspended = !user.isSuspended; }); @@ -145,10 +145,10 @@ export function getUserMenu(user) { async function invalidateFollow() { os.apiWithDialog('following/invalidate', { - userId: user.id + userId: user.id, }).then(() => { user.isFollowed = !user.isFollowed; - }) + }); } let menu = [{ @@ -156,19 +156,19 @@ export function getUserMenu(user) { text: i18n.ts.copyUsername, action: () => { copyToClipboard(`@${user.username}@${user.host || host}`); - } + }, }, { icon: 'fas fa-info-circle', text: i18n.ts.info, action: () => { os.pageWindow(`/user-info/${user.id}`); - } + }, }, { icon: 'fas fa-envelope', text: i18n.ts.sendMessage, action: () => { os.post({ specified: user }); - } + }, }, meId !== user.id ? { type: 'link', icon: 'fas fa-comments', @@ -177,47 +177,47 @@ export function getUserMenu(user) { } : undefined, null, { icon: 'fas fa-list-ul', text: i18n.ts.addToList, - action: pushList + action: pushList, }, meId !== user.id ? { icon: 'fas fa-users', text: i18n.ts.inviteToGroup, - action: inviteGroup + action: inviteGroup, } : undefined] as any; 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, - action: toggleMute + action: toggleMute, }, { icon: 'fas fa-ban', text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block, - action: toggleBlock + action: toggleBlock, }]); if (user.isFollowed) { menu = menu.concat([{ icon: 'fas fa-unlink', text: i18n.ts.breakFollow, - action: invalidateFollow + action: invalidateFollow, }]); } menu = menu.concat([null, { icon: 'fas fa-exclamation-circle', text: i18n.ts.reportAbuse, - action: reportAbuse + action: reportAbuse, }]); if (iAmModerator) { menu = menu.concat([null, { icon: 'fas fa-microphone-slash', text: user.isSilenced ? i18n.ts.unsilence : i18n.ts.silence, - action: toggleSilence + action: toggleSilence, }, { icon: 'fas fa-snowflake', text: user.isSuspended ? i18n.ts.unsuspend : i18n.ts.suspend, - action: toggleSuspend + action: toggleSuspend, }]); } } @@ -227,8 +227,8 @@ export function getUserMenu(user) { icon: 'fas fa-pencil-alt', text: i18n.ts.editProfile, action: () => { - router.push('/settings/profile'); - } + mainRouter.push('/settings/profile'); + }, }]); } @@ -238,7 +238,7 @@ export function getUserMenu(user) { text: action.title, action: () => { action.handler(user); - } + }, }))]); } diff --git a/packages/client/src/scripts/hpml/evaluator.ts b/packages/client/src/scripts/hpml/evaluator.ts index 0469a31cbb..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({ diff --git a/packages/client/src/scripts/navigate.ts b/packages/client/src/scripts/navigate.ts deleted file mode 100644 index 08b891ec5b..0000000000 --- a/packages/client/src/scripts/navigate.ts +++ /dev/null @@ -1,34 +0,0 @@ -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/page-metadata.ts b/packages/client/src/scripts/page-metadata.ts new file mode 100644 index 0000000000..0db8369f9d --- /dev/null +++ b/packages/client/src/scripts/page-metadata.ts @@ -0,0 +1,41 @@ +import * as misskey from 'misskey-js'; +import { ComputedRef, inject, isRef, onActivated, onMounted, provide, ref, Ref } from 'vue'; + +export const setPageMetadata = Symbol('setPageMetadata'); +export const pageMetadataProvider = Symbol('pageMetadataProvider'); + +export type PageMetadata = { + title: string; + subtitle?: string; + icon?: string | null; + avatar?: misskey.entities.User | null; + userName?: misskey.entities.User | null; + bg?: string; +}; + +export function definePageMetadata(metadata: PageMetadata | null | Ref<PageMetadata | null> | ComputedRef<PageMetadata | null>): void { + const _metadata = isRef(metadata) ? metadata : ref(metadata); + + provide(pageMetadataProvider, _metadata); + + const set = inject(setPageMetadata) as any; + if (set) { + set(_metadata); + + onMounted(() => { + set(_metadata); + }); + + onActivated(() => { + set(_metadata); + }); + } +} + +export function provideMetadataReceiver(callback: (info: ComputedRef<PageMetadata>) => void): void { + provide(setPageMetadata, callback); +} + +export function injectPageMetadata(): PageMetadata | undefined { + return inject(pageMetadataProvider); +} 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/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/scroll.ts b/packages/client/src/scripts/scroll.ts index 621fe88105..0643bad2fb 100644 --- a/packages/client/src/scripts/scroll.ts +++ b/packages/client/src/scripts/scroll.ts @@ -1,9 +1,13 @@ type ScrollBehavior = 'auto' | 'smooth' | 'instant'; -export function getScrollContainer(el: Element | null): Element | null { - if (el == null || el.tagName === 'BODY') return null; +export function getScrollContainer(el: HTMLElement | null): HTMLElement | null { + if (el == null || el.tagName === 'HTML') return null; const overflow = window.getComputedStyle(el).getPropertyValue('overflow'); - if (overflow.endsWith('auto')) { // xとyを個別に指定している場合、hidden auto みたいな値になる + if ( + // xとyを個別に指定している場合、`hidden scroll`みたいな値になる + overflow.endsWith('scroll') || + overflow.endsWith('auto') + ) { return el; } else { return getScrollContainer(el.parentElement); @@ -22,6 +26,11 @@ export function isTopVisible(el: Element | null): boolean { return scrollTop <= topPosition; } +export function isBottomVisible(el: HTMLElement, tolerance = 1, container = getScrollContainer(el)) { + if (container) return el.scrollHeight <= container.clientHeight + Math.abs(container.scrollTop) + tolerance; + return el.scrollHeight <= window.innerHeight + window.scrollY + tolerance; +} + export function onScrollTop(el: Element, cb) { const container = getScrollContainer(el) || window; const onScroll = ev => { diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts index 0aedee9c98..64914d3d65 100644 --- a/packages/client/src/scripts/search.ts +++ b/packages/client/src/scripts/search.ts @@ -1,6 +1,6 @@ import * as os from '@/os'; import { i18n } from '@/i18n'; -import { router } from '@/router'; +import { mainRouter } from '@/router'; export async function search() { const { canceled, result: query } = await os.inputText({ @@ -11,12 +11,12 @@ export async function search() { const q = query.trim(); if (q.startsWith('@') && !q.includes(' ')) { - router.push(`/${q}`); + mainRouter.push(`/${q}`); return; } if (q.startsWith('#')) { - router.push(`/tags/${encodeURIComponent(q.substr(1))}`); + mainRouter.push(`/tags/${encodeURIComponent(q.substr(1))}`); return; } @@ -36,14 +36,14 @@ export async function search() { //v.$root.$emit('warp', date); os.alert({ icon: 'fas fa-history', - iconOnly: true, autoClose: true + iconOnly: true, autoClose: true, }); return; } if (q.startsWith('https://')) { const promise = os.api('ap/show', { - uri: q + uri: q, }); os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject); @@ -51,13 +51,13 @@ export async function search() { const res = await promise; if (res.type === 'User') { - router.push(`/@${res.object.username}@${res.object.host}`); + mainRouter.push(`/@${res.object.username}@${res.object.host}`); } else if (res.type === 'Note') { - router.push(`/notes/${res.object.id}`); + mainRouter.push(`/notes/${res.object.id}`); } return; } - router.push(`/search?q=${encodeURIComponent(q)}`); + mainRouter.push(`/search?q=${encodeURIComponent(q)}`); } 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 b61b1684a8..dec9fb355c 100644 --- a/packages/client/src/scripts/theme.ts +++ b/packages/client/src/scripts/theme.ts @@ -1,3 +1,4 @@ +import { ref } from 'vue'; import { globalEvents } from '@/events'; import tinycolor from 'tinycolor2'; @@ -10,30 +11,38 @@ export type Theme = { props: Record<string, string>; }; -export const lightTheme: Theme = await import('@/themes/_light.json5'); -export const darkTheme: Theme = await import('@/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 = [ - await import('@/themes/l-light.json5'), - await import('@/themes/l-coffee.json5'), - await import('@/themes/l-apricot.json5'), - await import('@/themes/l-rainy.json5'), - await import('@/themes/l-vivid.json5'), - await import('@/themes/l-cherry.json5'), - await import('@/themes/l-sushi.json5'), +export const getBuiltinThemes = () => Promise.all( + [ + 'l-light', + 'l-coffee', + 'l-apricot', + 'l-rainy', + 'l-vivid', + 'l-cherry', + 'l-sushi', - await import('@/themes/d-dark.json5'), - await import('@/themes/d-persimmon.json5'), - await import('@/themes/d-astro.json5'), - await import('@/themes/d-future.json5'), - await import('@/themes/d-botanical.json5'), - await import('@/themes/d-cherry.json5'), - await import('@/themes/d-ice.json5'), - await import('@/themes/d-pumpkin.json5'), - await import('@/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/twemoji-base.ts b/packages/client/src/scripts/twemoji-base.ts index cd50311b15..638aae3284 100644 --- a/packages/client/src/scripts/twemoji-base.ts +++ b/packages/client/src/scripts/twemoji-base.ts @@ -1 +1,12 @@ export const twemojiSvgBase = '/twemoji'; + +export function char2fileName(char: string): string { + let codes = Array.from(char).map(x => x.codePointAt(0)?.toString(16)); + if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f'); + codes = codes.filter(x => x && x.length); + return codes.join('-'); +} + +export function char2filePath(char: string): string { + return `${twemojiSvgBase}/${char2fileName(char)}.svg`; +} diff --git a/packages/client/src/scripts/use-leave-guard.ts b/packages/client/src/scripts/use-leave-guard.ts index 33eea6b522..379a7e577c 100644 --- a/packages/client/src/scripts/use-leave-guard.ts +++ b/packages/client/src/scripts/use-leave-guard.ts @@ -1,5 +1,4 @@ import { inject, onUnmounted, Ref } from 'vue'; -import { onBeforeRouteLeave } from 'vue-router'; import { i18n } from '@/i18n'; import * as os from '@/os'; |