From 9384f5399da39e53855beb8e7f8ded1aa56bf72e Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 27 Dec 2022 14:36:33 +0900 Subject: rename: client -> frontend --- packages/frontend/src/scripts/get-note-menu.ts | 341 +++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 packages/frontend/src/scripts/get-note-menu.ts (limited to 'packages/frontend/src/scripts/get-note-menu.ts') diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts new file mode 100644 index 0000000000..7656770894 --- /dev/null +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -0,0 +1,341 @@ +import { defineAsyncComponent, Ref, inject } from 'vue'; +import * as misskey from 'misskey-js'; +import { pleaseLogin } from './please-login'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import * as os from '@/os'; +import copyToClipboard from '@/scripts/copy-to-clipboard'; +import { url } from '@/config'; +import { noteActions } from '@/store'; +import { notePage } from '@/filters/note'; + +export function getNoteMenu(props: { + note: misskey.entities.Note; + menuButton: Ref; + translation: Ref; + translating: Ref; + isDeleted: Ref; + currentClipPage?: Ref; +}) { + 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; + + function del(): void { + os.confirm({ + type: 'warning', + text: i18n.ts.noteDeleteConfirm, + }).then(({ canceled }) => { + if (canceled) return; + + os.api('notes/delete', { + noteId: appearNote.id, + }); + }); + } + + function delEdit(): void { + os.confirm({ + type: 'warning', + text: i18n.ts.deleteAndEditConfirm, + }).then(({ canceled }) => { + if (canceled) return; + + os.api('notes/delete', { + noteId: appearNote.id, + }); + + os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel }); + }); + } + + function toggleFavorite(favorite: boolean): void { + 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 copyLink(): void { + copyToClipboard(`${url}/notes/${appearNote.id}`); + os.success(); + } + + function togglePin(pin: boolean): void { + os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { + noteId: appearNote.id, + }, undefined, null, res => { + if (res.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { + os.alert({ + type: 'error', + text: i18n.ts.pinLimitExceeded, + }); + } + }); + } + + async function clip(): Promise { + const clips = await os.api('clips/list'); + os.popupMenu([{ + icon: 'ti ti-plus', + text: i18n.ts.createNew, + action: async () => { + const { canceled, result } = await os.form(i18n.ts.createNewClip, { + name: { + type: 'string', + label: i18n.ts.name, + }, + description: { + type: 'string', + required: false, + multiline: true, + label: i18n.ts.description, + }, + isPublic: { + type: 'boolean', + label: i18n.ts.public, + default: false, + }, + }); + if (canceled) return; + + const clip = await os.apiWithDialog('clips/create', result); + + os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); + }, + }, null, ...clips.map(clip => ({ + text: clip.name, + action: () => { + 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 { + os.apiWithDialog('clips/remove-note', { clipId: props.currentClipPage.value.id, noteId: appearNote.id }); + props.isDeleted.value = true; + } + + async function promote(): Promise { + const { canceled, result: days } = await os.inputNumber({ + title: i18n.ts.numberOfDays, + }); + + if (canceled) return; + + os.apiWithDialog('admin/promo/create', { + noteId: appearNote.id, + expiresAt: Date.now() + (86400000 * days), + }); + } + + function share(): void { + navigator.share({ + title: i18n.t('noteOf', { user: appearNote.user.name }), + text: appearNote.text, + url: `${url}/notes/${appearNote.id}`, + }); + } + function notedetails(): void { + os.pageWindow(`/notes/${appearNote.id}`); + } + async function translate(): Promise { + if (props.translation.value != null) return; + props.translating.value = true; + const res = await os.api('notes/translate', { + noteId: appearNote.id, + targetLang: localStorage.getItem('lang') || navigator.language, + }); + props.translating.value = false; + props.translation.value = res; + } + + let menu; + if ($i) { + const statePromise = os.api('notes/state', { + noteId: appearNote.id, + }); + + menu = [ + ...( + props.currentClipPage?.value.userId === $i.id ? [{ + icon: 'ti ti-backspace', + text: i18n.ts.unclip, + danger: true, + action: unclip, + }, null] : [] + ), { + icon: 'ti ti-external-link', + text: i18n.ts.details, + action: notedetails, + }, { + icon: 'ti ti-copy', + text: i18n.ts.copyContent, + action: copyContent, + }, { + icon: 'ti ti-link', + text: i18n.ts.copyLink, + action: copyLink, + }, (appearNote.url || appearNote.uri) ? { + icon: 'ti ti-external-link', + text: i18n.ts.showOnRemote, + action: () => { + window.open(appearNote.url || appearNote.uri, '_blank'); + }, + } : undefined, + { + icon: 'ti ti-share', + text: i18n.ts.share, + action: share, + }, + instance.translatorAvailable ? { + icon: 'ti ti-language-hiragana', + text: i18n.ts.translate, + action: translate, + } : undefined, + null, + 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), + }), + { + icon: 'ti ti-paperclip', + text: i18n.ts.clip, + action: () => clip(), + }, + 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), + }), + appearNote.userId === $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { + icon: 'ti ti-pinned-off', + text: i18n.ts.unpin, + action: () => togglePin(false), + } : { + icon: 'ti ti-pin', + text: i18n.ts.pin, + action: () => togglePin(true), + } : undefined, + /* + ...($i.isModerator || $i.isAdmin ? [ + null, + { + icon: 'fas fa-bullhorn', + text: i18n.ts.promote, + action: promote + }] + : [] + ),*/ + ...(appearNote.userId !== $i.id ? [ + null, + { + icon: 'ti ti-exclamation-circle', + text: i18n.ts.reportAbuse, + action: () => { + const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`; + os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), { + user: appearNote.user, + initialComment: `Note: ${u}\n-----\n`, + }, {}, 'closed'); + }, + }] + : [] + ), + ...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [ + null, + appearNote.userId === $i.id ? { + icon: 'ti ti-edit', + text: i18n.ts.deleteAndEdit, + action: delEdit, + } : undefined, + { + icon: 'ti ti-trash', + text: i18n.ts.delete, + danger: true, + action: del, + }] + : [] + )] + .filter(x => x !== undefined); + } else { + menu = [{ + icon: 'ti ti-external-link', + text: i18n.ts.detailed, + action: openDetail, + }, { + icon: 'ti ti-copy', + text: i18n.ts.copyContent, + action: copyContent, + }, { + icon: 'ti ti-link', + text: i18n.ts.copyLink, + action: copyLink, + }, (appearNote.url || appearNote.uri) ? { + icon: 'ti ti-external-link', + text: i18n.ts.showOnRemote, + action: () => { + window.open(appearNote.url || appearNote.uri, '_blank'); + }, + } : undefined] + .filter(x => x !== undefined); + } + + if (noteActions.length > 0) { + menu = menu.concat([null, ...noteActions.map(action => ({ + icon: 'ti ti-plug', + text: action.title, + action: () => { + action.handler(appearNote); + }, + }))]); + } + + return menu; +} -- cgit v1.2.3-freya From 91503405b4e58cb9d39c0441d86ff540a27931e6 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Jan 2023 10:13:02 +0900 Subject: refactor(client): typed localStorage --- packages/frontend/src/account.ts | 9 ++++--- .../frontend/src/components/MkAutocomplete.vue | 3 ++- packages/frontend/src/components/MkFolder.vue | 7 ++--- packages/frontend/src/components/MkPostForm.vue | 17 ++++++------ packages/frontend/src/config.ts | 10 ++++--- packages/frontend/src/init.ts | 24 ++++++++++------- packages/frontend/src/instance.ts | 5 ++-- packages/frontend/src/local-storage.ts | 31 ++++++++++++++++++++++ packages/frontend/src/navbar.ts | 7 ++--- packages/frontend/src/pages/_error_.vue | 3 ++- .../src/pages/messaging/messaging-room.form.vue | 11 ++++---- packages/frontend/src/pages/scratchpad.vue | 5 ++-- .../frontend/src/pages/settings/custom-css.vue | 5 ++-- packages/frontend/src/pages/settings/general.vue | 19 ++++++------- packages/frontend/src/pages/settings/index.vue | 5 ++-- .../src/pages/settings/preferences-backups.vue | 19 ++++++------- packages/frontend/src/pages/settings/theme.vue | 7 ++--- packages/frontend/src/scripts/aiscript/api.ts | 5 ++-- packages/frontend/src/scripts/get-note-menu.ts | 3 ++- packages/frontend/src/scripts/idb-proxy.ts | 6 ++--- packages/frontend/src/scripts/theme.ts | 5 ++-- packages/frontend/src/store.ts | 17 +++++++++--- packages/frontend/src/theme-store.ts | 14 ++++++---- packages/frontend/src/ui/classic.vue | 5 ++-- packages/frontend/src/ui/universal.vue | 3 ++- 25 files changed, 157 insertions(+), 88 deletions(-) create mode 100644 packages/frontend/src/local-storage.ts (limited to 'packages/frontend/src/scripts/get-note-menu.ts') diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index 0e991cdfb5..93916ccf2f 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -6,12 +6,13 @@ import { del, get, set } from '@/scripts/idb-proxy'; import { apiUrl } from '@/config'; import { waiting, api, popup, popupMenu, success, alert } from '@/os'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload'; +import { miLocalStorage } from './local-storage'; // TODO: 他のタブと永続化されたstateを同期 type Account = misskey.entities.MeDetailed; -const accountData = localStorage.getItem('account'); +const accountData = miLocalStorage.getItem('account'); // TODO: 外部からはreadonlyに export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null; @@ -21,7 +22,7 @@ export const iAmAdmin = $i != null && $i.isAdmin; export async function signout() { waiting(); - localStorage.removeItem('account'); + miLocalStorage.removeItem('account'); await removeAccount($i.id); @@ -119,7 +120,7 @@ export function updateAccount(accountData) { for (const [key, value] of Object.entries(accountData)) { $i[key] = value; } - localStorage.setItem('account', JSON.stringify($i)); + miLocalStorage.setItem('account', JSON.stringify($i)); } export function refreshAccount() { @@ -130,7 +131,7 @@ export async function login(token: Account['token'], redirect?: string) { waiting(); if (_DEV_) console.log('logging as token ', token); const me = await fetchAccount(token); - localStorage.setItem('account', JSON.stringify(me)); + miLocalStorage.setItem('account', JSON.stringify(me)); document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う await addAccount(me.id, token); diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 08e2c29de2..8ed60bc5dc 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -46,6 +46,7 @@ import { defaultStore } from '@/store'; import { emojilist } from '@/scripts/emojilist'; import { instance } from '@/instance'; import { i18n } from '@/i18n'; +import { miLocalStorage } from '@/local-storage'; type EmojiDef = { emoji: string; @@ -208,7 +209,7 @@ function exec() { } } else if (props.type === 'hashtag') { if (!props.q || props.q === '') { - hashtags.value = JSON.parse(localStorage.getItem('hashtags') || '[]'); + hashtags.value = JSON.parse(miLocalStorage.getItem('hashtags') || '[]'); fetching.value = false; } else { const cacheKey = `autocomplete:hashtag:${props.q}`; diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 5a406c8635..dc10c7d3f3 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -25,8 +25,9 @@