diff --git a/locales/index.d.ts b/locales/index.d.ts index 13ffc8470a..a3289e301d 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5338,6 +5338,36 @@ export interface Locale extends ILocale { * 同期の有効化をキャンセル */ "preferenceSyncConflictChoiceCancel": string; + /** + * ペースト + */ + "paste": string; + /** + * 絵文字パレット + */ + "emojiPalette": string; + /** + * 投稿フォーム + */ + "postForm": string; + "_emojiPalette": { + /** + * パレット + */ + "palettes": string; + /** + * パレットのデバイス間同期を有効にする + */ + "enableSyncBetweenDevicesForPalettes": string; + /** + * メインで使用するパレット + */ + "paletteForMain": string; + /** + * リアクションで使用するパレット + */ + "paletteForReaction": string; + }; "_settings": { /** * ドライブの管理と設定、使用量の確認、ファイルをアップロードする際の設定を行えます。 @@ -5372,7 +5402,7 @@ export interface Locale extends ILocale { */ "accountData": string; /** - * アカウントのデータをエクスポート/インポートして管理できます。 + * アカウントデータのアーカイブをエクスポート/インポートして管理できます。 */ "accountDataBanner": string; /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index af50f0738a..12816e0987 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1330,6 +1330,15 @@ preferenceSyncConflictText: "同期が有効にされた設定項目は設定値 preferenceSyncConflictChoiceServer: "サーバーの設定値" preferenceSyncConflictChoiceDevice: "デバイスの設定値" preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル" +paste: "ペースト" +emojiPalette: "絵文字パレット" +postForm: "投稿フォーム" + +_emojiPalette: + palettes: "パレット" + enableSyncBetweenDevicesForPalettes: "パレットのデバイス間同期を有効にする" + paletteForMain: "メインで使用するパレット" + paletteForReaction: "リアクションで使用するパレット" _settings: driveBanner: "ドライブの管理と設定、使用量の確認、ファイルをアップロードする際の設定を行えます。" @@ -1340,7 +1349,7 @@ _settings: serviceConnection: "サービス連携" serviceConnectionBanner: "外部のアプリ・サービスと連携するためのアクセストークンやWebhookの管理と設定が行えます。" accountData: "アカウントのデータ" - accountDataBanner: "アカウントのデータをエクスポート/インポートして管理できます。" + accountDataBanner: "アカウントデータのアーカイブをエクスポート/インポートして管理できます。" muteAndBlockBanner: "非表示にするコンテンツの設定や、特定のユーザーからのアクションを制限する設定と管理を行えます。" accessibilityBanner: "クライアントの視覚や動作に関するパーソナライズを行い、より最適に使用できるように設定できます。" privacyBanner: "コンテンツの公開範囲、見つけやすさ、フォローの承認制などアカウントのプライバシーに関する設定を行えます。" diff --git a/package.json b/package.json index f817991f6c..916f63c57f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.3.1-pari-alpha.9", + "version": "2025.3.1-pari-alpha.10", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts deleted file mode 100644 index 2e7572733f..0000000000 --- a/packages/frontend/src/account.ts +++ /dev/null @@ -1,391 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { defineAsyncComponent, reactive, ref } from 'vue'; -import * as Misskey from 'misskey-js'; -import { apiUrl } from '@@/js/config.js'; -import type { MenuItem, MenuButton } from '@/types/menu.js'; -import { defaultMemoryStorage } from '@/memory-storage'; -import { showSuspendedDialog } from '@/utility/show-suspended-dialog.js'; -import { i18n } from '@/i18n.js'; -import { miLocalStorage } from '@/local-storage.js'; -import { del, get, set } from '@/utility/idb-proxy.js'; -import { waiting, popup, popupMenu, success, alert } from '@/os.js'; -import { misskeyApi } from '@/utility/misskey-api.js'; -import { unisonReload, reloadChannel } from '@/utility/unison-reload.js'; - -// TODO: 他のタブと永続化されたstateを同期 -// TODO: accountsはpreferences管理にする(tokenは別管理) - -type Account = Misskey.entities.MeDetailed & { token: string }; - -const accountData = miLocalStorage.getItem('account'); - -// TODO: 外部からはreadonlyに -export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null; - -export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true); -export const iAmAdmin = $i != null && $i.isAdmin; - -export function signinRequired() { - if ($i == null) throw new Error('signin required'); - return $i; -} - -export let notesCount = $i == null ? 0 : $i.notesCount; -export function incNotesCount() { - notesCount++; -} - -export async function signout() { - if (!$i) return; - - defaultMemoryStorage.clear(); - - waiting(); - document.cookie.split(';').forEach((cookie) => { - const cookieName = cookie.split('=')[0].trim(); - if (cookieName === 'token') { - document.cookie = `${cookieName}=; max-age=0; path=/`; - } - }); - miLocalStorage.removeItem('account'); - await removeAccount($i.id); - document.cookie = `token=; path=/; max-age=0${ location.protocol === 'https:' ? '; Secure' : ''}`; - const accounts = await getAccounts(); - - //#region Remove service worker registration - try { - if (navigator.serviceWorker.controller) { - const registration = await navigator.serviceWorker.ready; - const push = await registration.pushManager.getSubscription(); - if (push) { - await window.fetch(`${apiUrl}/sw/unregister`, { - method: 'POST', - body: JSON.stringify({ - i: $i.token, - endpoint: push.endpoint, - }), - headers: { - 'Content-Type': 'application/json', - }, - }); - } - } - - if (accounts.length === 0) { - await navigator.serviceWorker.getRegistrations() - .then(registrations => { - return Promise.all(registrations.map(registration => registration.unregister())); - }); - } - } catch (err) {} - //#endregion - - if (accounts.length > 0) login(accounts[0].token); - else unisonReload('/'); -} - -export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> { - return (await get('accounts')) || []; -} - -export async function addAccount(id: Account['id'], token: Account['token']) { - const accounts = await getAccounts(); - if (!accounts.some(x => x.id === id)) { - await set('accounts', accounts.concat([{ id, token }])); - } -} - -export async function removeAccount(idOrToken: Account['id']) { - const accounts = await getAccounts(); - const i = accounts.findIndex(x => x.id === idOrToken || x.token === idOrToken); - if (i !== -1) accounts.splice(i, 1); - - if (accounts.length > 0) { - await set('accounts', accounts); - } else { - await del('accounts'); - } -} - -function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Promise<Account> { - document.cookie = 'token=; path=/; max-age=0'; - document.cookie = `token=${token}; path=/queue; max-age=86400; SameSite=Strict; Secure`; // bull dashboardの認証とかで使う - - return new Promise((done, fail) => { - window.fetch(`${apiUrl}/i`, { - method: 'POST', - body: JSON.stringify({ - i: token, - }), - headers: { - 'Content-Type': 'application/json', - }, - }) - .then(res => new Promise<Account | { error: Record<string, any> }>((done2, fail2) => { - if (res.status >= 500 && res.status < 600) { - // サーバーエラー(5xx)の場合をrejectとする - // (認証エラーなど4xxはresolve) - return fail2(res); - } - res.json().then(done2, fail2); - })) - .then(async res => { - if ('error' in res) { - if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') { - // SUSPENDED - if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { - await showSuspendedDialog(); - } - } else if (res.error.id === 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a') { - // USER_IS_DELETED - // アカウントが削除されている - if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { - await alert({ - type: 'error', - title: i18n.ts.accountDeleted, - text: i18n.ts.accountDeletedDescription, - }); - } - } else if (res.error.id === 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14') { - // AUTHENTICATION_FAILED - // トークンが無効化されていたりアカウントが削除されたりしている - if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { - await alert({ - type: 'error', - title: i18n.ts.tokenRevoked, - text: i18n.ts.tokenRevokedDescription, - }); - } - } else { - await alert({ - type: 'error', - title: i18n.ts.failedToFetchAccountInformation, - text: JSON.stringify(res.error), - }); - } - - // rejectかつ理由がtrueの場合、削除対象であることを示す - fail(true); - } else { - (res as Account).token = token; - done(res as Account); - } - }) - .catch(fail); - }); -} - -export function updateAccount(accountData: Account) { - if (!$i) return; - for (const key of Object.keys($i)) { - delete $i[key]; - } - for (const [key, value] of Object.entries(accountData)) { - $i[key] = value; - } - miLocalStorage.setItem('account', JSON.stringify($i)); -} - -export function updateAccountPartial(accountData: Partial<Account>) { - if (!$i) return; - for (const [key, value] of Object.entries(accountData)) { - $i[key] = value; - } - miLocalStorage.setItem('account', JSON.stringify($i)); -} - -export async function refreshAccount() { - if (!$i) return; - return fetchAccount($i.token, $i.id) - .then(updateAccount, reason => { - if (reason === true) return signout(); - return; - }); -} - -export async function login(token: Account['token'], redirect?: string) { - const showing = ref(true); - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkWaitingDialog.vue')), { - success: false, - showing: showing, - }, { - closed: () => dispose(), - }); - if (_DEV_) console.log('logging as token ', token); - const me = await fetchAccount(token, undefined, true) - .catch(reason => { - if (reason === true) { - // 削除対象の場合 - removeAccount(token); - } - - showing.value = false; - throw reason; - }); - miLocalStorage.setItem('account', JSON.stringify(me)); - await addAccount(me.id, token); - - if (redirect) { - // 他のタブは再読み込みするだけ - reloadChannel.postMessage(null); - // このページはredirectで指定された先に移動 - location.href = redirect; - return; - } - - unisonReload(); -} - -export async function openAccountMenu(opts: { - includeCurrentAccount?: boolean; - withExtraOperation: boolean; - active?: Misskey.entities.UserDetailed['id']; - onChoose?: (account: Misskey.entities.UserDetailed) => void; -}, ev: MouseEvent) { - if (!$i) return; - - async function switchAccount(account: Misskey.entities.UserDetailed) { - const storedAccounts = await getAccounts(); - const found = storedAccounts.find(x => x.id === account.id); - if (found == null) return; - switchAccountWithToken(found.token); - } - - function switchAccountWithToken(token: string) { - login(token); - } - - const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id)); - const accountsPromise = misskeyApi('users/show', { userIds: storedAccounts.map(x => x.id) }); - - function createItem(account: Misskey.entities.UserDetailed) { - return { - type: 'user' as const, - user: account, - active: opts.active != null ? opts.active === account.id : false, - action: () => { - if (opts.onChoose) { - opts.onChoose(account); - } else { - switchAccount(account); - } - }, - }; - } - - const accountItemPromises = storedAccounts.map(a => new Promise<ReturnType<typeof createItem> | MenuButton>(res => { - accountsPromise.then(accounts => { - const account = accounts.find(x => x.id === a.id); - if (account == null) return res({ - type: 'button' as const, - text: a.id, - action: () => { - switchAccountWithToken(a.token); - }, - }); - - res(createItem(account)); - }); - })); - - const menuItems: MenuItem[] = []; - - if (opts.withExtraOperation) { - menuItems.push({ - type: 'link', - text: i18n.ts.profile, - to: `/@${$i.username}`, - avatar: $i, - }, { - type: 'divider', - }); - - if (opts.includeCurrentAccount) { - menuItems.push(createItem($i)); - } - - menuItems.push(...accountItemPromises); - - menuItems.push({ - type: 'parent', - icon: 'ti ti-plus', - text: i18n.ts.addAccount, - children: [{ - text: i18n.ts.existingAccount, - action: () => { - getAccountWithSigninDialog().then(res => { - if (res != null) { - success(); - } - }); - }, - }, { - text: i18n.ts.createAccount, - action: () => { - getAccountWithSignupDialog().then(res => { - if (res != null) { - switchAccountWithToken(res.token); - } - }); - }, - }], - }, { - type: 'link', - icon: 'ti ti-users', - text: i18n.ts.manageAccounts, - to: '/settings/accounts', - }); - } else { - if (opts.includeCurrentAccount) { - menuItems.push(createItem($i)); - } - - menuItems.push(...accountItemPromises); - } - - popupMenu(menuItems, ev.currentTarget ?? ev.target, { - align: 'left', - }); -} - -export function getAccountWithSigninDialog(): Promise<{ id: string, token: string } | null> { - return new Promise((resolve) => { - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, { - done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => { - await addAccount(res.id, res.i); - resolve({ id: res.id, token: res.i }); - }, - cancelled: () => { - resolve(null); - }, - closed: () => { - dispose(); - }, - }); - }); -} - -export function getAccountWithSignupDialog(): Promise<{ id: string, token: string } | null> { - return new Promise((resolve) => { - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, { - done: async (res: Misskey.entities.SignupResponse) => { - await addAccount(res.id, res.token); - resolve({ id: res.id, token: res.token }); - }, - cancelled: () => { - resolve(null); - }, - closed: () => { - dispose(); - }, - }); - }); -} - -if (_DEV_) { - (window as any).$i = $i; -} diff --git a/packages/frontend/src/accounts.ts b/packages/frontend/src/accounts.ts new file mode 100644 index 0000000000..2382a8ec32 --- /dev/null +++ b/packages/frontend/src/accounts.ts @@ -0,0 +1,341 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { defineAsyncComponent, ref } from 'vue'; +import * as Misskey from 'misskey-js'; +import { apiUrl, host } from '@@/js/config.js'; +import type { MenuItem } from '@/types/menu.js'; +import { showSuspendedDialog } from '@/utility/show-suspended-dialog.js'; +import { i18n } from '@/i18n.js'; +import { miLocalStorage } from '@/local-storage.js'; +import { waiting, popup, popupMenu, success, alert } from '@/os.js'; +import { unisonReload, reloadChannel } from '@/utility/unison-reload.js'; +import { prefer } from '@/preferences.js'; +import { store } from '@/store.js'; +import { $i } from '@/i.js'; +import { signout } from '@/signout.js'; + +// TODO: 他のタブと永続化されたstateを同期 + +type AccountWithToken = Misskey.entities.MeDetailed & { token: string }; + +export async function getAccounts(): Promise<{ + host: string; + user: Misskey.entities.User; + token: string | null; +}[]> { + const tokens = store.s.accountTokens; + const accounts = prefer.s.accounts; + return accounts.map(([host, user]) => ({ + host, + user, + token: tokens[host + '/' + user.id] ?? null, + })); +} + +async function addAccount(host: string, user: Misskey.entities.User, token: AccountWithToken['token']) { + if (!prefer.s.accounts.some(x => x[0] === host && x[1].id === user.id)) { + store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + user.id]: token }); + prefer.commit('accounts', [...prefer.s.accounts, [host, user]]); + } +} + +export async function removeAccount(host: string, id: AccountWithToken['id']) { + const tokens = JSON.parse(JSON.stringify(store.s.accountTokens)); + delete tokens[host + '/' + id]; + store.set('accountTokens', tokens); + prefer.commit('accounts', prefer.s.accounts.filter(x => x[0] !== host || x[1].id !== id)); +} + +const isAccountDeleted = Symbol('isAccountDeleted'); + +function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Promise<Misskey.entities.MeDetailed> { + return new Promise((done, fail) => { + window.fetch(`${apiUrl}/i`, { + method: 'POST', + body: JSON.stringify({ + i: token, + }), + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(res => new Promise<Misskey.entities.MeDetailed | { error: Record<string, any> }>((done2, fail2) => { + if (res.status >= 500 && res.status < 600) { + // サーバーエラー(5xx)の場合をrejectとする + // (認証エラーなど4xxはresolve) + return fail2(res); + } + res.json().then(done2, fail2); + })) + .then(async res => { + if ('error' in res) { + if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') { + // SUSPENDED + if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { + await showSuspendedDialog(); + } + } else if (res.error.id === 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a') { + // USER_IS_DELETED + // アカウントが削除されている + if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { + await alert({ + type: 'error', + title: i18n.ts.accountDeleted, + text: i18n.ts.accountDeletedDescription, + }); + } + } else if (res.error.id === 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14') { + // AUTHENTICATION_FAILED + // トークンが無効化されていたりアカウントが削除されたりしている + if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { + await alert({ + type: 'error', + title: i18n.ts.tokenRevoked, + text: i18n.ts.tokenRevokedDescription, + }); + } + } else { + await alert({ + type: 'error', + title: i18n.ts.failedToFetchAccountInformation, + text: JSON.stringify(res.error), + }); + } + + fail(isAccountDeleted); + } else { + done(res); + } + }) + .catch(fail); + }); +} + +export function updateCurrentAccount(accountData: Misskey.entities.MeDetailed) { + if (!$i) return; + const token = $i.token; + for (const key of Object.keys($i)) { + delete $i[key]; + } + for (const [key, value] of Object.entries(accountData)) { + $i[key] = value; + } + prefer.commit('accounts', prefer.s.accounts.map(([host, user]) => { + // TODO: $iのホストも比較したいけど通常null + if (user.id === $i.id) { + return [host, $i]; + } else { + return [host, user]; + } + })); + $i.token = token; + miLocalStorage.setItem('account', JSON.stringify($i)); +} + +export function updateCurrentAccountPartial(accountData: Partial<Misskey.entities.MeDetailed>) { + if (!$i) return; + for (const [key, value] of Object.entries(accountData)) { + $i[key] = value; + } + prefer.commit('accounts', prefer.s.accounts.map(([host, user]) => { + // TODO: $iのホストも比較したいけど通常null + if (user.id === $i.id) { + const newUser = JSON.parse(JSON.stringify($i)); + for (const [key, value] of Object.entries(accountData)) { + newUser[key] = value; + } + return [host, newUser]; + } + return [host, user]; + })); + miLocalStorage.setItem('account', JSON.stringify($i)); +} + +export async function refreshCurrentAccount() { + if (!$i) return; + return fetchAccount($i.token, $i.id).then(updateCurrentAccount).catch(reason => { + if (reason === isAccountDeleted) { + removeAccount(host, $i.id); + if (Object.keys(store.s.accountTokens).length > 0) { + login(Object.values(store.s.accountTokens)[0]); + } else { + signout(); + } + } + }); +} + +export async function login(token: AccountWithToken['token'], redirect?: string) { + const showing = ref(true); + const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkWaitingDialog.vue')), { + success: false, + showing: showing, + }, { + closed: () => dispose(), + }); + + const me = await fetchAccount(token, undefined, true).catch(reason => { + showing.value = false; + throw reason; + }); + + miLocalStorage.setItem('account', JSON.stringify({ + ...me, + token, + })); + + await addAccount(host, me, token); + + if (redirect) { + // 他のタブは再読み込みするだけ + reloadChannel.postMessage(null); + // このページはredirectで指定された先に移動 + location.href = redirect; + return; + } + + unisonReload(); +} + +export async function switchAccount(host: string, id: string) { + const token = store.s.accountTokens[host + '/' + id]; + if (token) { + login(token); + } else { + const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, { + done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => { + store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + res.id]: res.i }); + login(res.i); + }, + closed: () => { + dispose(); + }, + }); + } +} + +export async function openAccountMenu(opts: { + includeCurrentAccount?: boolean; + withExtraOperation: boolean; + active?: Misskey.entities.User['id']; + onChoose?: (account: Misskey.entities.User) => void; +}, ev: MouseEvent) { + if (!$i) return; + + function createItem(host: string, account: Misskey.entities.User): MenuItem { + return { + type: 'user' as const, + user: account, + active: opts.active != null ? opts.active === account.id : false, + action: async () => { + if (opts.onChoose) { + opts.onChoose(account); + } else { + switchAccount(host, account.id); + } + }, + }; + } + + const menuItems: MenuItem[] = []; + + // TODO: $iのホストも比較したいけど通常null + const accountItems = (await getAccounts().then(accounts => accounts.filter(x => x.user.id !== $i.id))).map(a => createItem(a.host, a.user)); + + if (opts.withExtraOperation) { + menuItems.push({ + type: 'link', + text: i18n.ts.profile, + to: `/@${$i.username}`, + avatar: $i, + }, { + type: 'divider', + }); + + if (opts.includeCurrentAccount) { + menuItems.push(createItem(host, $i)); + } + + menuItems.push(...accountItems); + + menuItems.push({ + type: 'parent', + icon: 'ti ti-plus', + text: i18n.ts.addAccount, + children: [{ + text: i18n.ts.existingAccount, + action: () => { + getAccountWithSigninDialog().then(res => { + if (res != null) { + success(); + } + }); + }, + }, { + text: i18n.ts.createAccount, + action: () => { + getAccountWithSignupDialog().then(res => { + if (res != null) { + switchAccount(host, res.id); + } + }); + }, + }], + }, { + type: 'link', + icon: 'ti ti-users', + text: i18n.ts.manageAccounts, + to: '/settings/accounts', + }); + } else { + if (opts.includeCurrentAccount) { + menuItems.push(createItem(host, $i)); + } + + menuItems.push(...accountItems); + } + + popupMenu(menuItems, ev.currentTarget ?? ev.target, { + align: 'left', + }); +} + +export function getAccountWithSigninDialog(): Promise<{ id: string, token: string } | null> { + return new Promise((resolve) => { + const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, { + done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => { + const user = await fetchAccount(res.i, res.id, true); + await addAccount(host, user, res.i); + resolve({ id: res.id, token: res.i }); + }, + cancelled: () => { + resolve(null); + }, + closed: () => { + dispose(); + }, + }); + }); +} + +export function getAccountWithSignupDialog(): Promise<{ id: string, token: string } | null> { + return new Promise((resolve) => { + const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, { + done: async (res: Misskey.entities.SignupResponse) => { + const user = JSON.parse(JSON.stringify(res)); + delete user.token; + await addAccount(host, user, res.token); + resolve({ id: res.id, token: res.token }); + }, + cancelled: () => { + resolve(null); + }, + closed: () => { + dispose(); + }, + }); + }); +} diff --git a/packages/frontend/src/aiscript/api.ts b/packages/frontend/src/aiscript/api.ts index 3acc1127c9..e7e396023d 100644 --- a/packages/frontend/src/aiscript/api.ts +++ b/packages/frontend/src/aiscript/api.ts @@ -9,7 +9,7 @@ import { url, lang } from '@@/js/config.js'; import { assertStringAndIsIn } from './common.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { miLocalStorage } from '@/local-storage.js'; import { customEmojis } from '@/custom-emojis.js'; diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 0848dad3b5..c2812baaff 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -15,7 +15,7 @@ import components from '@/components/index.js'; import { applyTheme } from '@/theme.js'; import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js'; import { updateI18n, i18n } from '@/i18n.js'; -import { $i, refreshAccount, login } from '@/account.js'; +import { refreshCurrentAccount, login } from '@/accounts.js'; import { store } from '@/store.js'; import { fetchInstance, instance } from '@/instance.js'; import { deviceKind, updateDeviceKind } from '@/utility/device-kind.js'; @@ -30,6 +30,7 @@ import { setupRouter } from '@/router/main.js'; import { createMainRouter } from '@/router/definition.js'; import { loadFontStyle } from '@/utility/load-font.js'; import { prefer } from '@/preferences.js'; +import { $i } from '@/i.js'; export async function common(createVue: () => App<Element>) { console.info(`Misskey v${version}`); @@ -39,11 +40,6 @@ export async function common(createVue: () => App<Element>) { console.info(`vue ${vueVersion}`); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (window as any).$i = $i; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (window as any).$store = store; - window.addEventListener('error', event => { console.error(event); /* @@ -245,7 +241,7 @@ export async function common(createVue: () => App<Element>) { console.log('account cache found. refreshing...'); } - refreshAccount(); + refreshCurrentAccount(); } //#endregion @@ -335,6 +331,7 @@ export async function common(createVue: () => App<Element>) { return { isClientUpdated, + lastVersion, app, }; } diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 2053adaa03..f590a85c90 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -7,6 +7,7 @@ import { createApp, defineAsyncComponent, markRaw } from 'vue'; import { ui } from '@@/js/config.js'; import * as Misskey from 'misskey-js'; import { v4 as uuid } from 'uuid'; +import { compareVersions } from 'compare-versions'; import { common } from './common.js'; import type { Component } from 'vue'; import type { Keymap } from '@/utility/hotkey.js'; @@ -15,7 +16,7 @@ import { i18n } from '@/i18n.js'; import { alert, confirm, popup, post, toast } from '@/os.js'; import { useStream } from '@/stream.js'; import * as sound from '@/utility/sound.js'; -import { $i, signout, updateAccountPartial } from '@/account.js'; +import { $i } from '@/i.js'; import { instance } from '@/instance.js'; import { ColdDeviceStorage, store } from '@/store.js'; import { reactionPicker } from '@/utility/reaction-picker.js'; @@ -30,9 +31,12 @@ import { prefer } from '@/preferences.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { deckStore } from '@/ui/deck/deck-store.js'; import { launchPlugins } from '@/plugin.js'; +import { unisonReload } from '@/utility/unison-reload.js'; +import { updateCurrentAccountPartial } from '@/accounts.js'; +import { signout } from '@/signout.js'; export async function mainBoot() { - const { isClientUpdated } = await common(() => { + const { isClientUpdated, lastVersion } = await common(() => { let uiStyle = ui; const searchParams = new URLSearchParams(window.location.search); @@ -72,6 +76,137 @@ export async function mainBoot() { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, { closed: () => dispose(), }); + + // prefereces migration + // TODO: そのうち消す + if (lastVersion && (compareVersions('2025.3.2-alpha.0', lastVersion) === 1)) { + console.log('Preferences migration'); + + store.loaded.then(async () => { + const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []); + if (themes.length > 0) { + prefer.commit('themes', themes); + } + + const plugins = ColdDeviceStorage.get('plugins'); + prefer.commit('plugins', plugins.map(p => ({ + ...p, + installId: (p as any).id, + id: undefined, + }))); + + prefer.commit('deck.profile', deckStore.s.profile); + misskeyApi('i/registry/keys', { + scope: ['client', 'deck', 'profiles'], + }).then(async keys => { + const profiles: DeckProfile[] = []; + for (const key of keys) { + const deck = await misskeyApi('i/registry/get', { + scope: ['client', 'deck', 'profiles'], + key: key, + }); + profiles.push({ + id: uuid(), + name: key, + columns: deck.columns, + layout: deck.layout, + }); + } + prefer.commit('deck.profiles', profiles); + }); + + prefer.commit('lightTheme', ColdDeviceStorage.get('lightTheme')); + prefer.commit('darkTheme', ColdDeviceStorage.get('darkTheme')); + prefer.commit('syncDeviceDarkMode', ColdDeviceStorage.get('syncDeviceDarkMode')); + prefer.commit('emojiPalettes', [{ + id: 'reactions', + name: '', + emojis: store.s.reactions, + }, { + id: 'pinnedEmojis', + name: '', + emojis: store.s.pinnedEmojis, + }]); + prefer.commit('emojiPaletteForMain', 'pinnedEmojis'); + prefer.commit('emojiPaletteForReaction', 'reactions'); + prefer.commit('overridedDeviceKind', store.s.overridedDeviceKind); + prefer.commit('widgets', store.s.widgets); + prefer.commit('keepCw', store.s.keepCw); + prefer.commit('collapseRenotes', store.s.collapseRenotes); + prefer.commit('rememberNoteVisibility', store.s.rememberNoteVisibility); + prefer.commit('uploadFolder', store.s.uploadFolder); + prefer.commit('keepOriginalUploading', store.s.keepOriginalUploading); + prefer.commit('menu', store.s.menu); + prefer.commit('statusbars', store.s.statusbars); + prefer.commit('pinnedUserLists', store.s.pinnedUserLists); + prefer.commit('serverDisconnectedBehavior', store.s.serverDisconnectedBehavior); + prefer.commit('nsfw', store.s.nsfw); + prefer.commit('highlightSensitiveMedia', store.s.highlightSensitiveMedia); + prefer.commit('animation', store.s.animation); + prefer.commit('animatedMfm', store.s.animatedMfm); + prefer.commit('advancedMfm', store.s.advancedMfm); + prefer.commit('showReactionsCount', store.s.showReactionsCount); + prefer.commit('enableQuickAddMfmFunction', store.s.enableQuickAddMfmFunction); + prefer.commit('loadRawImages', store.s.loadRawImages); + prefer.commit('imageNewTab', store.s.imageNewTab); + prefer.commit('disableShowingAnimatedImages', store.s.disableShowingAnimatedImages); + prefer.commit('emojiStyle', store.s.emojiStyle); + prefer.commit('menuStyle', store.s.menuStyle); + prefer.commit('useBlurEffectForModal', store.s.useBlurEffectForModal); + prefer.commit('useBlurEffect', store.s.useBlurEffect); + prefer.commit('showFixedPostForm', store.s.showFixedPostForm); + prefer.commit('showFixedPostFormInChannel', store.s.showFixedPostFormInChannel); + prefer.commit('enableInfiniteScroll', store.s.enableInfiniteScroll); + prefer.commit('useReactionPickerForContextMenu', store.s.useReactionPickerForContextMenu); + prefer.commit('showGapBetweenNotesInTimeline', store.s.showGapBetweenNotesInTimeline); + prefer.commit('instanceTicker', store.s.instanceTicker); + prefer.commit('emojiPickerScale', store.s.emojiPickerScale); + prefer.commit('emojiPickerWidth', store.s.emojiPickerWidth); + prefer.commit('emojiPickerHeight', store.s.emojiPickerHeight); + prefer.commit('emojiPickerStyle', store.s.emojiPickerStyle); + prefer.commit('reportError', store.s.reportError); + prefer.commit('squareAvatars', store.s.squareAvatars); + prefer.commit('showAvatarDecorations', store.s.showAvatarDecorations); + prefer.commit('numberOfPageCache', store.s.numberOfPageCache); + prefer.commit('showNoteActionsOnlyHover', store.s.showNoteActionsOnlyHover); + prefer.commit('showClipButtonInNoteFooter', store.s.showClipButtonInNoteFooter); + prefer.commit('reactionsDisplaySize', store.s.reactionsDisplaySize); + prefer.commit('limitWidthOfReaction', store.s.limitWidthOfReaction); + prefer.commit('forceShowAds', store.s.forceShowAds); + prefer.commit('aiChanMode', store.s.aiChanMode); + prefer.commit('devMode', store.s.devMode); + prefer.commit('mediaListWithOneImageAppearance', store.s.mediaListWithOneImageAppearance); + prefer.commit('notificationPosition', store.s.notificationPosition); + prefer.commit('notificationStackAxis', store.s.notificationStackAxis); + prefer.commit('enableCondensedLine', store.s.enableCondensedLine); + prefer.commit('keepScreenOn', store.s.keepScreenOn); + prefer.commit('disableStreamingTimeline', store.s.disableStreamingTimeline); + prefer.commit('useGroupedNotifications', store.s.useGroupedNotifications); + prefer.commit('dataSaver', store.s.dataSaver); + prefer.commit('enableSeasonalScreenEffect', store.s.enableSeasonalScreenEffect); + prefer.commit('enableHorizontalSwipe', store.s.enableHorizontalSwipe); + prefer.commit('useNativeUiForVideoAudioPlayer', store.s.useNativeUIForVideoAudioPlayer); + prefer.commit('keepOriginalFilename', store.s.keepOriginalFilename); + prefer.commit('alwaysConfirmFollow', store.s.alwaysConfirmFollow); + prefer.commit('confirmWhenRevealingSensitiveMedia', store.s.confirmWhenRevealingSensitiveMedia); + prefer.commit('contextMenu', store.s.contextMenu); + prefer.commit('skipNoteRender', store.s.skipNoteRender); + prefer.commit('showSoftWordMutedWord', store.s.showSoftWordMutedWord); + prefer.commit('confirmOnReact', store.s.confirmOnReact); + prefer.commit('defaultFollowWithReplies', store.s.defaultWithReplies); + prefer.commit('sound.masterVolume', store.s.sound_masterVolume); + prefer.commit('sound.notUseSound', store.s.sound_notUseSound); + prefer.commit('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive); + prefer.commit('sound.on.note', store.s.sound_note as any); + prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any); + prefer.commit('sound.on.notification', store.s.sound_notification as any); + prefer.commit('sound.on.reaction', store.s.sound_reaction as any); + + window.setTimeout(() => { + unisonReload(); + }, 5000); + }); + } } const stream = useStream(); @@ -138,118 +273,6 @@ export async function mainBoot() { if ($i) { store.loaded.then(async () => { - // prefereces migration - // TODO: そのうち消す - if (store.s.menu.length > 0) { - const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []); - if (themes.length > 0) { - prefer.commit('themes', themes); - } - - const plugins = ColdDeviceStorage.get('plugins'); - prefer.commit('plugins', plugins.map(p => ({ - ...p, - installId: (p as any).id, - id: undefined, - }))); - - prefer.commit('deck.profile', deckStore.s.profile); - misskeyApi('i/registry/keys', { - scope: ['client', 'deck', 'profiles'], - }).then(async keys => { - const profiles: DeckProfile[] = []; - for (const key of keys) { - const deck = await misskeyApi('i/registry/get', { - scope: ['client', 'deck', 'profiles'], - key: key, - }); - profiles.push({ - id: uuid(), - name: key, - columns: deck.columns, - layout: deck.layout, - }); - } - prefer.commit('deck.profiles', profiles); - }); - - prefer.commit('lightTheme', ColdDeviceStorage.get('lightTheme')); - prefer.commit('darkTheme', ColdDeviceStorage.get('darkTheme')); - prefer.commit('syncDeviceDarkMode', ColdDeviceStorage.get('syncDeviceDarkMode')); - prefer.commit('overridedDeviceKind', store.s.overridedDeviceKind); - prefer.commit('widgets', store.s.widgets); - prefer.commit('keepCw', store.s.keepCw); - prefer.commit('collapseRenotes', store.s.collapseRenotes); - prefer.commit('rememberNoteVisibility', store.s.rememberNoteVisibility); - prefer.commit('uploadFolder', store.s.uploadFolder); - prefer.commit('keepOriginalUploading', store.s.keepOriginalUploading); - prefer.commit('menu', store.s.menu); - prefer.commit('statusbars', store.s.statusbars); - prefer.commit('pinnedUserLists', store.s.pinnedUserLists); - prefer.commit('serverDisconnectedBehavior', store.s.serverDisconnectedBehavior); - prefer.commit('nsfw', store.s.nsfw); - prefer.commit('highlightSensitiveMedia', store.s.highlightSensitiveMedia); - prefer.commit('animation', store.s.animation); - prefer.commit('animatedMfm', store.s.animatedMfm); - prefer.commit('advancedMfm', store.s.advancedMfm); - prefer.commit('showReactionsCount', store.s.showReactionsCount); - prefer.commit('enableQuickAddMfmFunction', store.s.enableQuickAddMfmFunction); - prefer.commit('loadRawImages', store.s.loadRawImages); - prefer.commit('imageNewTab', store.s.imageNewTab); - prefer.commit('disableShowingAnimatedImages', store.s.disableShowingAnimatedImages); - prefer.commit('emojiStyle', store.s.emojiStyle); - prefer.commit('menuStyle', store.s.menuStyle); - prefer.commit('useBlurEffectForModal', store.s.useBlurEffectForModal); - prefer.commit('useBlurEffect', store.s.useBlurEffect); - prefer.commit('showFixedPostForm', store.s.showFixedPostForm); - prefer.commit('showFixedPostFormInChannel', store.s.showFixedPostFormInChannel); - prefer.commit('enableInfiniteScroll', store.s.enableInfiniteScroll); - prefer.commit('useReactionPickerForContextMenu', store.s.useReactionPickerForContextMenu); - prefer.commit('showGapBetweenNotesInTimeline', store.s.showGapBetweenNotesInTimeline); - prefer.commit('instanceTicker', store.s.instanceTicker); - prefer.commit('emojiPickerScale', store.s.emojiPickerScale); - prefer.commit('emojiPickerWidth', store.s.emojiPickerWidth); - prefer.commit('emojiPickerHeight', store.s.emojiPickerHeight); - prefer.commit('emojiPickerStyle', store.s.emojiPickerStyle); - prefer.commit('reportError', store.s.reportError); - prefer.commit('squareAvatars', store.s.squareAvatars); - prefer.commit('showAvatarDecorations', store.s.showAvatarDecorations); - prefer.commit('numberOfPageCache', store.s.numberOfPageCache); - prefer.commit('showNoteActionsOnlyHover', store.s.showNoteActionsOnlyHover); - prefer.commit('showClipButtonInNoteFooter', store.s.showClipButtonInNoteFooter); - prefer.commit('reactionsDisplaySize', store.s.reactionsDisplaySize); - prefer.commit('limitWidthOfReaction', store.s.limitWidthOfReaction); - prefer.commit('forceShowAds', store.s.forceShowAds); - prefer.commit('aiChanMode', store.s.aiChanMode); - prefer.commit('devMode', store.s.devMode); - prefer.commit('mediaListWithOneImageAppearance', store.s.mediaListWithOneImageAppearance); - prefer.commit('notificationPosition', store.s.notificationPosition); - prefer.commit('notificationStackAxis', store.s.notificationStackAxis); - prefer.commit('enableCondensedLine', store.s.enableCondensedLine); - prefer.commit('keepScreenOn', store.s.keepScreenOn); - prefer.commit('disableStreamingTimeline', store.s.disableStreamingTimeline); - prefer.commit('useGroupedNotifications', store.s.useGroupedNotifications); - prefer.commit('dataSaver', store.s.dataSaver); - prefer.commit('enableSeasonalScreenEffect', store.s.enableSeasonalScreenEffect); - prefer.commit('enableHorizontalSwipe', store.s.enableHorizontalSwipe); - prefer.commit('useNativeUiForVideoAudioPlayer', store.s.useNativeUIForVideoAudioPlayer); - prefer.commit('keepOriginalFilename', store.s.keepOriginalFilename); - prefer.commit('alwaysConfirmFollow', store.s.alwaysConfirmFollow); - prefer.commit('confirmWhenRevealingSensitiveMedia', store.s.confirmWhenRevealingSensitiveMedia); - prefer.commit('contextMenu', store.s.contextMenu); - prefer.commit('skipNoteRender', store.s.skipNoteRender); - prefer.commit('showSoftWordMutedWord', store.s.showSoftWordMutedWord); - prefer.commit('confirmOnReact', store.s.confirmOnReact); - prefer.commit('sound.masterVolume', store.s.sound_masterVolume); - prefer.commit('sound.notUseSound', store.s.sound_notUseSound); - prefer.commit('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive); - prefer.commit('sound.on.note', store.s.sound_note as any); - prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any); - prefer.commit('sound.on.notification', store.s.sound_notification as any); - prefer.commit('sound.on.reaction', store.s.sound_reaction as any); - store.set('menu', []); - } - if (store.s.accountSetupWizard !== -1) { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, { closed: () => dispose(), @@ -459,11 +482,11 @@ export async function mainBoot() { // 自分の情報が更新されたとき main.on('meUpdated', i => { - updateAccountPartial(i); + updateCurrentAccountPartial(i); }); main.on('readAllNotifications', () => { - updateAccountPartial({ + updateCurrentAccountPartial({ hasUnreadNotification: false, unreadNotificationsCount: 0, }); @@ -471,39 +494,39 @@ export async function mainBoot() { main.on('unreadNotification', () => { const unreadNotificationsCount = ($i?.unreadNotificationsCount ?? 0) + 1; - updateAccountPartial({ + updateCurrentAccountPartial({ hasUnreadNotification: true, unreadNotificationsCount, }); }); main.on('unreadMention', () => { - updateAccountPartial({ hasUnreadMentions: true }); + updateCurrentAccountPartial({ hasUnreadMentions: true }); }); main.on('readAllUnreadMentions', () => { - updateAccountPartial({ hasUnreadMentions: false }); + updateCurrentAccountPartial({ hasUnreadMentions: false }); }); main.on('unreadSpecifiedNote', () => { - updateAccountPartial({ hasUnreadSpecifiedNotes: true }); + updateCurrentAccountPartial({ hasUnreadSpecifiedNotes: true }); }); main.on('readAllUnreadSpecifiedNotes', () => { - updateAccountPartial({ hasUnreadSpecifiedNotes: false }); + updateCurrentAccountPartial({ hasUnreadSpecifiedNotes: false }); }); main.on('readAllAntennas', () => { - updateAccountPartial({ hasUnreadAntenna: false }); + updateCurrentAccountPartial({ hasUnreadAntenna: false }); }); main.on('unreadAntenna', () => { - updateAccountPartial({ hasUnreadAntenna: true }); - sound.playMisskeySfx('note'); + updateCurrentAccountPartial({ hasUnreadAntenna: true }); + sound.playMisskeySfx('antenna'); }); main.on('readAllAnnouncements', () => { - updateAccountPartial({ hasUnreadAnnouncement: false }); + updateCurrentAccountPartial({ hasUnreadAnnouncement: false }); }); // 個人宛てお知らせが発行されたとき diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue index 41fd2564d8..582bb137bc 100644 --- a/packages/frontend/src/components/MkAnnouncementDialog.vue +++ b/packages/frontend/src/components/MkAnnouncementDialog.vue @@ -29,7 +29,8 @@ import { misskeyApi } from '@/utility/misskey-api.js'; import MkModal from '@/components/MkModal.vue'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; -import { $i, updateAccountPartial } from '@/account.js'; +import { $i } from '@/i.js'; +import { updateCurrentAccountPartial } from '@/accounts.js'; const props = withDefaults(defineProps<{ announcement: Misskey.entities.Announcement; @@ -51,7 +52,7 @@ async function ok() { modal.value?.close(); misskeyApi('i/read-announcement', { announcementId: props.announcement.id }); - updateAccountPartial({ + updateCurrentAccountPartial({ unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== props.announcement.id), }); } diff --git a/packages/frontend/src/components/MkAuthConfirm.vue b/packages/frontend/src/components/MkAuthConfirm.vue index 090c31044e..00bf8e68d9 100644 --- a/packages/frontend/src/components/MkAuthConfirm.vue +++ b/packages/frontend/src/components/MkAuthConfirm.vue @@ -117,10 +117,9 @@ SPDX-License-Identifier: AGPL-3.0-only <script setup lang="ts"> import { ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; - import MkButton from '@/components/MkButton.vue'; - -import { $i, getAccounts, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/account.js'; +import { $i } from '@/i.js'; +import { getAccounts, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/accounts.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { getProxiedImageUrl } from '@/utility/media-proxy.js'; @@ -158,7 +157,7 @@ async function init() { const accounts = await getAccounts(); - const accountIdsToFetch = accounts.map(a => a.id).filter(id => !users.value.has(id)); + const accountIdsToFetch = accounts.map(a => a.user.id).filter(id => !users.value.has(id)); if (accountIdsToFetch.length > 0) { const usersRes = await misskeyApi('users/show', { @@ -170,7 +169,7 @@ async function init() { users.value.set(user.id, { ...user, - token: accounts.find(a => a.id === user.id)!.token, + token: accounts.find(a => a.user.id === user.id)!.token, }); } } diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index 6ffb14ad53..6234fa1d12 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-if="!link" ref="el" class="_button" - :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]" + :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike, [$style.iconOnly]: iconOnly }]" :type="type" :name="name" :value="value" @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only </button> <MkA v-else class="_button" - :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]" + :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike, [$style.iconOnly]: iconOnly }]" :to="to ?? '#'" :behavior="linkBehavior" @mousedown="onMousedown" @@ -57,6 +57,7 @@ const props = defineProps<{ name?: string; value?: string; disabled?: boolean; + iconOnly?: boolean; }>(); const emit = defineEmits<{ @@ -147,6 +148,11 @@ function onMousedown(evt: MouseEvent): void { background: var(--MI_THEME-buttonHoverBg); } + &.iconOnly { + padding: 7px; + min-width: auto; + } + &.small { font-size: 90%; padding: 6px 12px; diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index c1a55906ae..d9f4558014 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -55,7 +55,7 @@ import { Chart } from 'chart.js'; import * as Misskey from 'misskey-js'; import { misskeyApiGet } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { chartVLine } from '@/utility/chart-vline.js'; import { alpha } from '@/utility/color.js'; import date from '@/filters/date.js'; diff --git a/packages/frontend/src/components/MkClipPreview.vue b/packages/frontend/src/components/MkClipPreview.vue index 5b09ec90dd..2154c08ab3 100644 --- a/packages/frontend/src/components/MkClipPreview.vue +++ b/packages/frontend/src/components/MkClipPreview.vue @@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only import * as Misskey from 'misskey-js'; import { computed } from 'vue'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import number from '@/filters/number.js'; const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 3c41d597de..4a89fb30ca 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -38,7 +38,7 @@ import tinycolor from 'tinycolor2'; import { apiUrl } from '@@/js/config.js'; import MkModalWindow from '@/components/MkModalWindow.vue'; import * as os from '@/os.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; import { getProxiedImageUrl } from '@/utility/media-proxy.js'; import { prefer } from '@/preferences.js'; diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index 733d50728e..f02a767186 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -44,7 +44,7 @@ import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; import bytes from '@/filters/bytes.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js'; import { deviceKind } from '@/utility/device-kind.js'; import { useRouter } from '@/router/supplier.js'; diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 384682277e..c0883b1342 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -136,7 +136,7 @@ import { deviceKind } from '@/utility/device-kind.js'; import { i18n } from '@/i18n.js'; import { store } from '@/store.js'; import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js'; import { prefer } from '@/preferences.js'; diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 3d5d0ec5ab..b62494fa20 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -44,8 +44,7 @@ import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; import { claimAchievement } from '@/utility/achievements.js'; import { pleaseLogin } from '@/utility/please-login.js'; -import { $i } from '@/account.js'; -import { store } from '@/store.js'; +import { $i } from '@/i.js'; import { prefer } from '@/preferences.js'; const props = withDefaults(defineProps<{ @@ -121,11 +120,11 @@ async function onClick() { } else { await misskeyApi('following/create', { userId: props.user.id, - withReplies: store.s.defaultWithReplies, + withReplies: prefer.s.defaultFollowWithReplies, }); emit('update:user', { ...props.user, - withReplies: store.s.defaultWithReplies, + withReplies: prefer.s.defaultFollowWithReplies, }); hasPendingFollowRequestFromYou.value = true; diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue index 8339e68b07..8a84bfd541 100644 --- a/packages/frontend/src/components/MkHeatmap.vue +++ b/packages/frontend/src/components/MkHeatmap.vue @@ -18,7 +18,7 @@ import { Chart } from 'chart.js'; import * as Misskey from 'misskey-js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { alpha } from '@/utility/color.js'; import { initChart } from '@/utility/init-chart.js'; diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index 9d475bc8aa..3113c6fad6 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -88,8 +88,8 @@ import { onMounted, ref, computed, shallowRef } from 'vue'; import { Chart } from 'chart.js'; import MkSelect from '@/components/MkSelect.vue'; import MkChart from '@/components/MkChart.vue'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; -import { $i } from '@/account.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; +import { $i } from '@/i.js'; import * as os from '@/os.js'; import { misskeyApiGet } from '@/utility/misskey-api.js'; import { instance } from '@/instance.js'; diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index 252adb6ae9..21c922c980 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent, ref } from 'vue'; import { url as local } from '@@/js/config.js'; -import { useTooltip } from '@/utility/use-tooltip.js'; +import { useTooltip } from '@/use/use-tooltip.js'; import * as os from '@/os.js'; import { isEnabledUrlPreview } from '@/instance.js'; import type { MkABehavior } from '@/components/global/MkA.vue'; diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index 33d4d269e7..096c51bbd6 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -98,7 +98,7 @@ import * as os from '@/os.js'; import bytes from '@/filters/bytes.js'; import { hms } from '@/filters/hms.js'; import MkMediaRange from '@/components/MkMediaRange.vue'; -import { $i, iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/i.js'; import { prefer } from '@/preferences.js'; const props = defineProps<{ diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 6029b1e0b6..20ac1a917e 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -60,7 +60,7 @@ import bytes from '@/filters/bytes.js'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; -import { $i, iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/i.js'; import { prefer } from '@/preferences.js'; const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 74c1aefc3a..403ec61736 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -121,7 +121,7 @@ import * as os from '@/os.js'; import { exitFullscreen, requestFullscreen } from '@/utility/fullscreen.js'; import hasAudio from '@/utility/media-has-audio.js'; import MkMediaRange from '@/components/MkMediaRange.vue'; -import { $i, iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/i.js'; import { prefer } from '@/preferences.js'; const props = defineProps<{ diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue index a43161867f..afeb0d2a92 100644 --- a/packages/frontend/src/components/MkMention.vue +++ b/packages/frontend/src/components/MkMention.vue @@ -18,7 +18,7 @@ import { toUnicode } from 'punycode.js'; import { computed } from 'vue'; import { host as localHost } from '@@/js/config.js'; import type { MkABehavior } from '@/components/global/MkA.vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { getStaticImageUrl } from '@/utility/media-proxy.js'; import { prefer } from '@/preferences.js'; diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index c07bb48408..25f95bad85 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -222,12 +222,12 @@ import * as sound from '@/utility/sound.js'; import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js'; import { reactionPicker } from '@/utility/reaction-picker.js'; import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js'; -import { useNoteCapture } from '@/utility/use-note-capture.js'; +import { useNoteCapture } from '@/use/use-note-capture.js'; import { deepClone } from '@/utility/clone.js'; -import { useTooltip } from '@/utility/use-tooltip.js'; +import { useTooltip } from '@/use/use-tooltip.js'; import { claimAchievement } from '@/utility/achievements.js'; import { getNoteSummary } from '@/utility/get-note-summary.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 80b5240531..e481e38758 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -276,12 +276,12 @@ import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js'; import * as sound from '@/utility/sound.js'; import { reactionPicker } from '@/utility/reaction-picker.js'; import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js'; -import { useNoteCapture } from '@/utility/use-note-capture.js'; +import { useNoteCapture } from '@/use/use-note-capture.js'; import { deepClone } from '@/utility/clone.js'; -import { useTooltip } from '@/utility/use-tooltip.js'; +import { useTooltip } from '@/use/use-tooltip.js'; import { claimAchievement } from '@/utility/achievements.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/utility/show-moved-dialog.js'; diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index bb7347cd26..4fd1c210cb 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -48,7 +48,7 @@ import MkCwButton from '@/components/MkCwButton.vue'; import { notePage } from '@/filters/note.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { userPage } from '@/filters/user.js'; import { checkWordMute } from '@/utility/check-word-mute.js'; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 5d096cf92d..b2380a5e0e 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -169,7 +169,7 @@ import { notePage } from '@/filters/note.js'; import { userPage } from '@/filters/user.js'; import { i18n } from '@/i18n.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { infoImageUrl } from '@/instance.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/components/MkPasswordDialog.vue b/packages/frontend/src/components/MkPasswordDialog.vue index 4d1787d420..24750012d0 100644 --- a/packages/frontend/src/components/MkPasswordDialog.vue +++ b/packages/frontend/src/components/MkPasswordDialog.vue @@ -44,7 +44,7 @@ import MkInput from '@/components/MkInput.vue'; import MkButton from '@/components/MkButton.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; import { i18n } from '@/i18n.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 09bb4d7b3d..15e9ede5fa 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -127,7 +127,8 @@ import { store } from '@/store.js'; import MkInfo from '@/components/MkInfo.vue'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { signinRequired, notesCount, incNotesCount, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account.js'; +import { signinRequired, notesCount, incNotesCount } from '@/i.js'; +import { getAccounts, openAccountMenu as openAccountMenu_ } from '@/accounts.js'; import { uploadFile } from '@/utility/upload.js'; import { deepClone } from '@/utility/clone.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; @@ -909,7 +910,7 @@ async function post(ev?: MouseEvent) { if (postAccount.value) { const storedAccounts = await getAccounts(); - token = storedAccounts.find(x => x.id === postAccount.value?.id)?.token; + token = storedAccounts.find(x => x.user.id === postAccount.value?.id)?.token; } posting.value = true; diff --git a/packages/frontend/src/components/MkPreferenceContainer.vue b/packages/frontend/src/components/MkPreferenceContainer.vue index acdd2a8d3b..70b111513c 100644 --- a/packages/frontend/src/components/MkPreferenceContainer.vue +++ b/packages/frontend/src/components/MkPreferenceContainer.vue @@ -57,7 +57,7 @@ function showMenu(ev: MouseEvent, contextmenu?: boolean) { display: flex; &:hover { - &::after { + &::before { content: ''; position: absolute; top: -8px; diff --git a/packages/frontend/src/components/MkPreview.vue b/packages/frontend/src/components/MkPreview.vue index 6efd99d14b..d8dfbd1655 100644 --- a/packages/frontend/src/components/MkPreview.vue +++ b/packages/frontend/src/components/MkPreview.vue @@ -43,7 +43,7 @@ import MkTextarea from '@/components/MkTextarea.vue'; import MkRadio from '@/components/MkRadio.vue'; import * as os from '@/os.js'; import * as config from '@@/js/config.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; const text = ref(''); const flag = ref(true); diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue index 780f8bc6d0..9c37eb5e72 100644 --- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue +++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue @@ -42,12 +42,13 @@ SPDX-License-Identifier: AGPL-3.0-only <script setup lang="ts"> import { ref } from 'vue'; -import { $i, getAccounts } from '@/account.js'; +import { $i } from '@/i.js'; import MkButton from '@/components/MkButton.vue'; import { instance } from '@/instance.js'; import { apiWithDialog, promiseDialog } from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; +import { getAccounts } from '@/accounts.js'; defineProps<{ primary?: boolean; diff --git a/packages/frontend/src/components/MkReactionIcon.vue b/packages/frontend/src/components/MkReactionIcon.vue index cb25580bc2..7551524246 100644 --- a/packages/frontend/src/components/MkReactionIcon.vue +++ b/packages/frontend/src/components/MkReactionIcon.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent, shallowRef } from 'vue'; -import { useTooltip } from '@/utility/use-tooltip.js'; +import { useTooltip } from '@/use/use-tooltip.js'; import * as os from '@/os.js'; const props = defineProps<{ diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 20486c9611..f0b65d7e46 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -27,7 +27,7 @@ import MkReactionIcon from '@/components/MkReactionIcon.vue'; import * as os from '@/os.js'; import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js'; import { useTooltip } from '@/use/use-tooltip.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import MkReactionEffect from '@/components/MkReactionEffect.vue'; import { claimAchievement } from '@/utility/achievements.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue index c53bf98f67..a962b37e4a 100644 --- a/packages/frontend/src/components/MkRetentionHeatmap.vue +++ b/packages/frontend/src/components/MkRetentionHeatmap.vue @@ -17,7 +17,7 @@ import { onMounted, nextTick, shallowRef, ref } from 'vue'; import { Chart } from 'chart.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { alpha } from '@/utility/color.js'; import { initChart } from '@/utility/init-chart.js'; diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue index 9e03be3e7f..6fa19efed5 100644 --- a/packages/frontend/src/components/MkRetentionLineChart.vue +++ b/packages/frontend/src/components/MkRetentionLineChart.vue @@ -12,7 +12,7 @@ import { onMounted, shallowRef } from 'vue'; import { Chart } from 'chart.js'; import tinycolor from 'tinycolor2'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { chartVLine } from '@/utility/chart-vline.js'; import { alpha } from '@/utility/color.js'; import { initChart } from '@/utility/init-chart.js'; diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index 38186bd7b7..a0c4ef1497 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -67,20 +67,19 @@ SPDX-License-Identifier: AGPL-3.0-only import { nextTick, onBeforeUnmount, ref, shallowRef, useTemplateRef } from 'vue'; import * as Misskey from 'misskey-js'; import { supported as webAuthnSupported, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill'; - import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/browser-ponyfill'; import type { OpenOnRemoteOptions } from '@/utility/please-login.js'; +import type { PwResponse } from '@/components/MkSignin.password.vue'; import { misskeyApi } from '@/utility/misskey-api.js'; import { showSuspendedDialog } from '@/utility/show-suspended-dialog.js'; -import { login } from '@/account.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import XInput from '@/components/MkSignin.input.vue'; import XPassword from '@/components/MkSignin.password.vue'; -import type { PwResponse } from '@/components/MkSignin.password.vue'; import XTotp from '@/components/MkSignin.totp.vue'; import XPasskey from '@/components/MkSignin.passkey.vue'; +import { login } from '@/accounts.js'; const emit = defineEmits<{ (ev: 'login', v: Misskey.entities.SigninFlowResponse & { finished: true }): void; diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue index ebb362fd6a..858258baaf 100644 --- a/packages/frontend/src/components/MkSignupDialog.form.vue +++ b/packages/frontend/src/components/MkSignupDialog.form.vue @@ -90,13 +90,13 @@ import * as config from '@@/js/config.js'; import MkButton from './MkButton.vue'; import MkInput from './MkInput.vue'; import MkTextarea from './MkTextarea.vue'; -import MkCaptcha from '@/components/MkCaptcha.vue'; import type { Captcha } from '@/components/MkCaptcha.vue'; +import MkCaptcha from '@/components/MkCaptcha.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { login } from '@/account.js'; import { instance } from '@/instance.js'; import { i18n } from '@/i18n.js'; +import { login } from '@/accounts.js'; const props = withDefaults(defineProps<{ autoSet?: boolean; diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 7014239c39..ee42ee95e7 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -25,7 +25,7 @@ import MkNotes from '@/components/MkNotes.vue'; import MkPullToRefresh from '@/components/MkPullToRefresh.vue'; import { useStream } from '@/stream.js'; import * as sound from '@/utility/sound.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { instance } from '@/instance.js'; import { prefer } from '@/preferences.js'; diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue index 31ecb15ab8..bed15031cb 100644 --- a/packages/frontend/src/components/MkTokenGenerateWindow.vue +++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue @@ -55,7 +55,7 @@ import MkButton from './MkButton.vue'; import MkInfo from './MkInfo.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; import { i18n } from '@/i18n.js'; -import { iAmAdmin } from '@/account.js'; +import { iAmAdmin } from '@/i.js'; const props = withDefaults(defineProps<{ title?: string | null; diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue index b26a01737e..59e1b096ae 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Note.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue @@ -27,7 +27,7 @@ import * as Misskey from 'misskey-js'; import { ref, reactive } from 'vue'; import { i18n } from '@/i18n.js'; import { globalEvents } from '@/events.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import MkNote from '@/components/MkNote.vue'; const props = defineProps<{ diff --git a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue index f7b60fbc45..8ae6c1ceaa 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue @@ -31,7 +31,7 @@ import MkPostForm from '@/components/MkPostForm.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkNote from '@/components/MkNote.vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; const emit = defineEmits<{ (ev: 'succeeded'): void; diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue index eb189b446b..cff531b2ca 100644 --- a/packages/frontend/src/components/MkUserInfo.vue +++ b/packages/frontend/src/components/MkUserInfo.vue @@ -39,7 +39,7 @@ import MkFollowButton from '@/components/MkFollowButton.vue'; import number from '@/filters/number.js'; import { userPage } from '@/filters/user.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/utility/isFfVisibleForMe.js'; import { getStaticImageUrl } from '@/utility/media-proxy.js'; import { prefer } from '@/preferences.js'; diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue index 1d0ff1b6ce..278f9925b7 100644 --- a/packages/frontend/src/components/MkUserPopup.vue +++ b/packages/frontend/src/components/MkUserPopup.vue @@ -65,7 +65,7 @@ import { getUserMenu } from '@/utility/get-user-menu.js'; import number from '@/filters/number.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/utility/isFfVisibleForMe.js'; import { getStaticImageUrl } from '@/utility/media-proxy.js'; diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue index e5c6df267b..6bf3eb44dc 100644 --- a/packages/frontend/src/components/MkUserSelectDialog.vue +++ b/packages/frontend/src/components/MkUserSelectDialog.vue @@ -70,7 +70,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue'; import { misskeyApi } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { instance } from '@/instance.js'; const emit = defineEmits<{ diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue index 14acfd3f89..7a5e5772a4 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue @@ -39,7 +39,7 @@ import FormSlot from '@/components/form/slot.vue'; import MkInfo from '@/components/MkInfo.vue'; import { chooseFileFromPc } from '@/utility/select-file.js'; import * as os from '@/os.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue index 872d4201bb..4168876f08 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue @@ -19,7 +19,7 @@ import gradient from 'chartjs-plugin-gradient'; import tinycolor from 'tinycolor2'; import { misskeyApi } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { chartVLine } from '@/utility/chart-vline.js'; import { initChart } from '@/utility/init-chart.js'; diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue index c196519c15..b55069ca25 100644 --- a/packages/frontend/src/components/global/MkAd.vue +++ b/packages/frontend/src/components/global/MkAd.vue @@ -47,7 +47,7 @@ import { instance } from '@/instance.js'; import MkButton from '@/components/MkButton.vue'; import { store } from '@/store.js'; import * as os from '@/os.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { prefer } from '@/preferences.js'; type Ad = (typeof instance)['ads'][number]; diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index 271db7963e..71946932f8 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -35,7 +35,7 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import * as sound from '@/utility/sound.js'; import { i18n } from '@/i18n.js'; import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { prefer } from '@/preferences.js'; const props = defineProps<{ diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 728e37cf51..69bbd88cb6 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -50,7 +50,8 @@ import type { PageHeaderItem } from '@/types/page-header.js'; import type { PageMetadata } from '@/page.js'; import { globalEvents } from '@/events.js'; import { injectReactiveMetadata } from '@/page.js'; -import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js'; +import { openAccountMenu as openAccountMenu_ } from '@/accounts.js'; +import { $i } from '@/i.js'; const props = withDefaults(defineProps<{ overridePageMetadata?: PageMetadata; diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue index 08dd85b728..1aa9f99777 100644 --- a/packages/frontend/src/components/global/MkUrl.vue +++ b/packages/frontend/src/components/global/MkUrl.vue @@ -30,7 +30,7 @@ import { defineAsyncComponent, ref } from 'vue'; import { toUnicode as decodePunycode } from 'punycode.js'; import { url as local } from '@@/js/config.js'; import * as os from '@/os.js'; -import { useTooltip } from '@/utility/use-tooltip.js'; +import { useTooltip } from '@/use/use-tooltip.js'; import { isEnabledUrlPreview } from '@/instance.js'; import type { MkABehavior } from '@/components/global/MkA.vue'; diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue index f813bcb73f..f7f6f5c140 100644 --- a/packages/frontend/src/components/grid/MkDataCell.vue +++ b/packages/frontend/src/components/grid/MkDataCell.vue @@ -90,7 +90,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script setup lang="ts"> import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, shallowRef, toRefs, watch } from 'vue'; import { GridEventEmitter } from '@/components/grid/grid.js'; -import { useTooltip } from '@/utility/use-tooltip.js'; +import { useTooltip } from '@/use/use-tooltip.js'; import * as os from '@/os.js'; import { equalCellAddress, getCellAddress } from '@/components/grid/grid-utils.js'; import type { Size } from '@/components/grid/grid.js'; diff --git a/packages/frontend/src/i.ts b/packages/frontend/src/i.ts new file mode 100644 index 0000000000..aa84c6aa61 --- /dev/null +++ b/packages/frontend/src/i.ts @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { reactive } from 'vue'; +import * as Misskey from 'misskey-js'; +import { miLocalStorage } from '@/local-storage.js'; + +// TODO: 他のタブと永続化されたstateを同期 + +type AccountWithToken = Misskey.entities.MeDetailed & { token: string }; + +const accountData = miLocalStorage.getItem('account'); + +// TODO: 外部からはreadonlyに +export const $i = accountData ? reactive(JSON.parse(accountData) as AccountWithToken) : null; + +export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true); +export const iAmAdmin = $i != null && $i.isAdmin; + +export function signinRequired() { + if ($i == null) throw new Error('signin required'); + return $i; +} + +export let notesCount = $i == null ? 0 : $i.notesCount; +export function incNotesCount() { + notesCount++; +} + +if (_DEV_) { + (window as any).$i = $i; +} diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts index 804bd6bee8..536618f2ee 100644 --- a/packages/frontend/src/local-storage.ts +++ b/packages/frontend/src/local-storage.ts @@ -9,7 +9,6 @@ export type Keys = ( 'instance' | 'instanceCachedAt' | 'account' | - 'accounts' | 'latestDonationInfoShownAt' | 'neverShowDonationInfo' | 'neverShowLocalOnlyInfo' | diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index 11e0554d63..2f3f29a623 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -6,7 +6,7 @@ import { computed, reactive } from 'vue'; import { ui } from '@@/js/config.js'; import { clearCache } from './utility/clear-cache.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { miLocalStorage } from '@/local-storage.js'; import { openInstanceMenu, openToolsMenu } from '@/ui/_common_/common.js'; import { lookup } from '@/utility/lookup.js'; diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index f1eb23dfdc..62da72c4b3 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -145,11 +145,11 @@ import MkInfo from '@/components/MkInfo.vue'; import { physics } from '@/utility/physics.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { store } from '@/store.js'; import * as os from '@/os.js'; import { definePage } from '@/page.js'; import { claimAchievement, claimedAchievements } from '@/utility/achievements.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; +import { prefer } from '@/preferences.js'; const patronsWithIcon = [{ name: 'カイヤン', @@ -408,7 +408,7 @@ const easterEggEngine = ref<{ stop: () => void } | null>(null); const containerEl = shallowRef<HTMLElement>(); function iconLoaded() { - const emojis = store.s.reactions; + const emojis = prefer.s.emojiPalettes[0].emojis; const containerWidth = containerEl.value.offsetWidth; for (let i = 0; i < 32; i++) { easterEggEmojis.value.push({ diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index 288078835b..a7769d6e3d 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -44,7 +44,7 @@ import MkInput from '@/components/MkInput.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import { customEmojis, customEmojiCategories, getCustomEmojiTags } from '@/custom-emojis.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; const customEmojiTags = getCustomEmojiTags(); const q = ref(''); diff --git a/packages/frontend/src/pages/achievements.vue b/packages/frontend/src/pages/achievements.vue index 53ce75f9bf..ca2443cc5b 100644 --- a/packages/frontend/src/pages/achievements.vue +++ b/packages/frontend/src/pages/achievements.vue @@ -17,7 +17,7 @@ import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue'; import MkAchievements from '@/components/MkAchievements.vue'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { claimAchievement } from '@/utility/achievements.js'; let timer: number | null; diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue index 0af28e94fa..9274b90892 100644 --- a/packages/frontend/src/pages/admin-file.vue +++ b/packages/frontend/src/pages/admin-file.vue @@ -86,7 +86,7 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -import { iAmAdmin, iAmModerator } from '@/account.js'; +import { iAmAdmin, iAmModerator } from '@/i.js'; const tab = ref('overview'); const file = ref<Misskey.entities.DriveFile | null>(null); diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 30d99ab608..f553a269ec 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -235,7 +235,7 @@ import { misskeyApi } from '@/utility/misskey-api.js'; import { acct } from '@/filters/user.js'; import { definePage } from '@/page.js'; import { i18n } from '@/i18n.js'; -import { iAmAdmin, $i, iAmModerator } from '@/account.js'; +import { iAmAdmin, $i, iAmModerator } from '@/i.js'; import MkRolePreview from '@/components/MkRolePreview.vue'; import MkPagination from '@/components/MkPagination.vue'; diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue index 59b8435595..2bd734f7d3 100644 --- a/packages/frontend/src/pages/admin/bot-protection.vue +++ b/packages/frontend/src/pages/admin/bot-protection.vue @@ -163,7 +163,7 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { fetchInstance } from '@/instance.js'; import { i18n } from '@/i18n.js'; -import { useForm } from '@/utility/use-form.js'; +import { useForm } from '@/use/use-form.js'; import MkFormFooter from '@/components/MkFormFooter.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkInfo from '@/components/MkInfo.vue'; diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue index 7cada9b5be..5c50336b2c 100644 --- a/packages/frontend/src/pages/admin/overview.active-users.vue +++ b/packages/frontend/src/pages/admin/overview.active-users.vue @@ -18,7 +18,7 @@ import { Chart } from 'chart.js'; import gradient from 'chartjs-plugin-gradient'; import { misskeyApi } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { chartVLine } from '@/utility/chart-vline.js'; import { initChart } from '@/utility/init-chart.js'; diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue index bbfe60d205..1949201ca0 100644 --- a/packages/frontend/src/pages/admin/overview.ap-requests.vue +++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue @@ -25,7 +25,7 @@ import { Chart } from 'chart.js'; import gradient from 'chartjs-plugin-gradient'; import isChromatic from 'chromatic'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { chartVLine } from '@/utility/chart-vline.js'; import { store } from '@/store.js'; import { alpha } from '@/utility/color.js'; diff --git a/packages/frontend/src/pages/admin/overview.federation.vue b/packages/frontend/src/pages/admin/overview.federation.vue index 362f0974e0..6d6d431863 100644 --- a/packages/frontend/src/pages/admin/overview.federation.vue +++ b/packages/frontend/src/pages/admin/overview.federation.vue @@ -54,7 +54,7 @@ import { misskeyApiGet } from '@/utility/misskey-api.js'; import number from '@/filters/number.js'; import MkNumberDiff from '@/components/MkNumberDiff.vue'; import { i18n } from '@/i18n.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; const topSubInstancesForPie = ref<InstanceForPie[] | null>(null); const topPubInstancesForPie = ref<InstanceForPie[] | null>(null); diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue index 792565dd96..424bcdd51f 100644 --- a/packages/frontend/src/pages/admin/overview.pie.vue +++ b/packages/frontend/src/pages/admin/overview.pie.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, shallowRef } from 'vue'; import { Chart } from 'chart.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { initChart } from '@/utility/init-chart.js'; export type InstanceForPie = { diff --git a/packages/frontend/src/pages/admin/overview.queue.chart.vue b/packages/frontend/src/pages/admin/overview.queue.chart.vue index 708768a1e4..34c0945ddb 100644 --- a/packages/frontend/src/pages/admin/overview.queue.chart.vue +++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { onMounted, shallowRef } from 'vue'; import { Chart } from 'chart.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { chartVLine } from '@/utility/chart-vline.js'; import { alpha } from '@/utility/color.js'; import { initChart } from '@/utility/init-chart.js'; diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue index 8fa2c61613..6bb0918fea 100644 --- a/packages/frontend/src/pages/admin/performance.vue +++ b/packages/frontend/src/pages/admin/performance.vue @@ -119,7 +119,7 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkInput from '@/components/MkInput.vue'; import MkLink from '@/components/MkLink.vue'; -import { useForm } from '@/utility/use-form.js'; +import { useForm } from '@/use/use-form.js'; import MkFormFooter from '@/components/MkFormFooter.vue'; const meta = await misskeyApi('admin/meta'); diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/queue.chart.chart.vue index 071d4b2f51..9c7a83b1fb 100644 --- a/packages/frontend/src/pages/admin/queue.chart.chart.vue +++ b/packages/frontend/src/pages/admin/queue.chart.chart.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { onMounted, shallowRef } from 'vue'; import { Chart } from 'chart.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { chartVLine } from '@/utility/chart-vline.js'; import { alpha } from '@/utility/color.js'; import { initChart } from '@/utility/init-chart.js'; diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index 85dcec6b2e..13f57b8549 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -135,7 +135,7 @@ import { misskeyApi } from '@/utility/misskey-api.js'; import { fetchInstance } from '@/instance.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -import { useForm } from '@/utility/use-form.js'; +import { useForm } from '@/use/use-form.js'; import MkFormFooter from '@/components/MkFormFooter.vue'; const meta = await misskeyApi('admin/meta'); diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 8c89664671..c019bda32c 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -273,7 +273,7 @@ import { definePage } from '@/page.js'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; -import { useForm } from '@/utility/use-form.js'; +import { useForm } from '@/use/use-form.js'; import MkFormFooter from '@/components/MkFormFooter.vue'; import MkRadios from '@/components/MkRadios.vue'; diff --git a/packages/frontend/src/pages/announcement.vue b/packages/frontend/src/pages/announcement.vue index 977bbe0b47..6562610b12 100644 --- a/packages/frontend/src/pages/announcement.vue +++ b/packages/frontend/src/pages/announcement.vue @@ -55,8 +55,9 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -import { $i, updateAccountPartial } from '@/account.js'; +import { $i } from '@/i.js'; import { prefer } from '@/preferences.js'; +import { updateCurrentAccountPartial } from '@/accounts.js'; const props = defineProps<{ announcementId: string; @@ -90,7 +91,7 @@ async function read(target: Misskey.entities.Announcement): Promise<void> { target.isRead = true; await misskeyApi('i/read-announcement', { announcementId: target.id }); if ($i) { - updateAccountPartial({ + updateCurrentAccountPartial({ unreadAnnouncements: $i.unreadAnnouncements.filter((a: { id: string; }) => a.id !== target.id), }); } diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index 13f28d9b35..2387ac728b 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -56,7 +56,8 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -import { $i, updateAccountPartial } from '@/account.js'; +import { $i } from '@/i.js'; +import { updateCurrentAccountPartial } from '@/accounts.js'; const paginationCurrent = { endpoint: 'announcements' as const, @@ -94,7 +95,7 @@ async function read(target) { return a; }); misskeyApi('i/read-announcement', { announcementId: target.id }); - updateAccountPartial({ + updateCurrentAccountPartial({ unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id), }); } diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue index 8b0fde4a25..e4699379f0 100644 --- a/packages/frontend/src/pages/auth.vue +++ b/packages/frontend/src/pages/auth.vue @@ -47,9 +47,10 @@ import * as Misskey from 'misskey-js'; import XForm from './auth.form.vue'; import MkSignin from '@/components/MkSignin.vue'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { $i, login } from '@/account.js'; +import { $i } from '@/i.js'; import { definePage } from '@/page.js'; import { i18n } from '@/i18n.js'; +import { login } from '@/accounts.js'; const props = defineProps<{ token: string; diff --git a/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue index a3c5a36614..884429dfeb 100644 --- a/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue +++ b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue @@ -73,7 +73,7 @@ import { i18n } from '@/i18n.js'; import MkSwitch from '@/components/MkSwitch.vue'; import MkRolePreview from '@/components/MkRolePreview.vue'; import MkTextarea from '@/components/MkTextarea.vue'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue index eb1015b19e..b84b9efc1a 100644 --- a/packages/frontend/src/pages/avatar-decorations.vue +++ b/packages/frontend/src/pages/avatar-decorations.vue @@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed, defineAsyncComponent } from 'vue'; import * as Misskey from 'misskey-js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index a774aa6e44..c5951f0e13 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -82,7 +82,7 @@ import MkTimeline from '@/components/MkTimeline.vue'; import XChannelFollowButton from '@/components/MkChannelFollowButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { $i, iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/i.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { deviceKind } from '@/utility/device-kind.js'; diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue index 9765ebf216..590a506a55 100644 --- a/packages/frontend/src/pages/clip.vue +++ b/packages/frontend/src/pages/clip.vue @@ -36,7 +36,7 @@ import * as Misskey from 'misskey-js'; import { url } from '@@/js/config.js'; import type { MenuItem } from '@/types/menu.js'; import MkNotes from '@/components/MkNotes.vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index f760aca8ae..364006e9ad 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -208,7 +208,7 @@ import { claimAchievement } from '@/utility/achievements.js'; import { store } from '@/store.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import * as sound from '@/utility/sound.js'; import MkRange from '@/components/MkRange.vue'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue index 35a240b9ba..bedb0b64f9 100644 --- a/packages/frontend/src/pages/emojis.emoji.vue +++ b/packages/frontend/src/pages/emojis.emoji.vue @@ -22,7 +22,7 @@ import { misskeyApiGet } from '@/utility/misskey-api.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { i18n } from '@/i18n.js'; import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; const props = defineProps<{ emoji: Misskey.entities.EmojiSimple; diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 6bce6689d4..08ac913958 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -80,7 +80,7 @@ import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js'; import MkFolder from '@/components/MkFolder.vue'; import MkCode from '@/components/MkCode.vue'; import { prefer } from '@/preferences.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { isSupportShare } from '@/utility/navigator.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { pleaseLogin } from '@/utility/please-login.js'; diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue index bd48b882d2..7e496f522d 100644 --- a/packages/frontend/src/pages/follow-requests.vue +++ b/packages/frontend/src/pages/follow-requests.vue @@ -54,7 +54,7 @@ import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { infoImageUrl } from '@/instance.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>(); diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index 56ddb820cf..eb01aadbcc 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -77,7 +77,7 @@ import MkFollowButton from '@/components/MkFollowButton.vue'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { prefer } from '@/preferences.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { isSupportShare } from '@/utility/navigator.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { useRouter } from '@/router/supplier.js'; diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index eddeb4aba9..c4aed8d6df 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -148,7 +148,7 @@ import MkSwitch from '@/components/MkSwitch.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import number from '@/filters/number.js'; -import { iAmModerator, iAmAdmin } from '@/account.js'; +import { iAmModerator, iAmAdmin } from '@/i.js'; import { definePage } from '@/page.js'; import { i18n } from '@/i18n.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue index 8369927d85..352e1d9386 100644 --- a/packages/frontend/src/pages/invite.vue +++ b/packages/frontend/src/pages/invite.vue @@ -45,7 +45,7 @@ import type { Paging } from '@/components/MkPagination.vue'; import MkInviteCode from '@/components/MkInviteCode.vue'; import { definePage } from '@/page.js'; import { serverErrorImageUrl, instance } from '@/instance.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>(); const currentInviteLimit = ref<null | number>(null); diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue index 0bc9b3f3c2..cc701cb16b 100644 --- a/packages/frontend/src/pages/my-lists/index.vue +++ b/packages/frontend/src/pages/my-lists/index.vue @@ -37,7 +37,7 @@ import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { userListsCache } from '@/cache.js'; import { infoImageUrl } from '@/instance.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index fdee890cfd..6481c0da0c 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -66,7 +66,7 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkInput from '@/components/MkInput.vue'; import { userListsCache } from '@/cache.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import MkPagination from '@/components/MkPagination.vue'; import { mainRouter } from '@/router/main.js'; import { prefer } from '@/preferences.js'; diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index 6f53cba806..fb83993fee 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -65,7 +65,7 @@ import { prefer } from '@/preferences.js'; import { pleaseLogin } from '@/utility/please-login.js'; import { getAppearNote } from '@/utility/get-appear-note.js'; import { serverContext, assertServerContext } from '@/server-context.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; // contextは非ログイン状態の情報しかないためログイン時は利用できない const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null; diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue index ed701ed3c0..e2f6084252 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.vue @@ -75,7 +75,7 @@ import { misskeyApi } from '@/utility/misskey-api.js'; import { selectFile } from '@/utility/select-file.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { mainRouter } from '@/router/main.js'; import { getPageBlockList } from '@/pages/page-editor/common.js'; diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index 1c288442b5..00c664d2a0 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -115,7 +115,7 @@ import MkPagePreview from '@/components/MkPagePreview.vue'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { deepClone } from '@/utility/clone.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { isSupportShare } from '@/utility/navigator.js'; import { instance } from '@/instance.js'; import { getStaticImageUrl } from '@/utility/media-proxy.js'; diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index 71dd220cfe..ef9cc242c6 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -150,7 +150,7 @@ import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import { deepClone } from '@/utility/clone.js'; import { useInterval } from '@@/js/use-interval.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { url } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { misskeyApi } from '@/utility/misskey-api.js'; diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue index 03b75f89ae..2715b70b95 100644 --- a/packages/frontend/src/pages/reversi/game.setting.vue +++ b/packages/frontend/src/pages/reversi/game.setting.vue @@ -114,7 +114,7 @@ import { computed, watch, ref, onMounted, shallowRef, onUnmounted } from 'vue'; import * as Misskey from 'misskey-js'; import * as Reversi from 'misskey-reversi'; import { i18n } from '@/i18n.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { deepClone } from '@/utility/clone.js'; import MkButton from '@/components/MkButton.vue'; import MkRadios from '@/components/MkRadios.vue'; diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue index 053ec2aa08..662df00d9b 100644 --- a/packages/frontend/src/pages/reversi/game.vue +++ b/packages/frontend/src/pages/reversi/game.vue @@ -17,7 +17,7 @@ import GameBoard from './game.board.vue'; import { misskeyApi } from '@/utility/misskey-api.js'; import { definePage } from '@/page.js'; import { useStream } from '@/stream.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { useRouter } from '@/router/supplier.js'; import * as os from '@/os.js'; import { url } from '@@/js/config.js'; diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue index ff2e7e922f..d66ff8db05 100644 --- a/packages/frontend/src/pages/reversi/index.vue +++ b/packages/frontend/src/pages/reversi/index.vue @@ -113,7 +113,7 @@ import { useStream } from '@/stream.js'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import MkPagination from '@/components/MkPagination.vue'; import { useRouter } from '@/router/supplier.js'; import * as os from '@/os.js'; diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index ed5cd23b23..b0d3b5bbd2 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -66,7 +66,7 @@ import MkTextarea from '@/components/MkTextarea.vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue'; import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js'; import * as os from '@/os.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { registerAsUiLib } from '@/aiscript/ui.js'; diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue index 7f6b7e445f..50a81ea651 100644 --- a/packages/frontend/src/pages/search.note.vue +++ b/packages/frontend/src/pages/search.note.vue @@ -115,7 +115,7 @@ import { computed, ref, shallowRef, toRef } from 'vue'; import { host as localHost } from '@@/js/config.js'; import type * as Misskey from 'misskey-js'; import type { Paging } from '@/components/MkPagination.vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import * as os from '@/os.js'; diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue index 9093ffd7a9..41a2535813 100644 --- a/packages/frontend/src/pages/settings/2fa.qrdialog.vue +++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue @@ -117,7 +117,7 @@ import MkFolder from '@/components/MkFolder.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkLink from '@/components/MkLink.vue'; import { confetti } from '@/utility/confetti.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index 806599e801..20d1b0fe0f 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -92,8 +92,9 @@ import FormSection from '@/components/form/section.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkLink from '@/components/MkLink.vue'; import * as os from '@/os.js'; -import { signinRequired, updateAccountPartial } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { i18n } from '@/i18n.js'; +import { updateCurrentAccountPartial } from '@/accounts.js'; const $i = signinRequired(); @@ -131,7 +132,7 @@ async function unregisterTOTP(): Promise<void> { password: auth.result.password, token: auth.result.token, }).then(res => { - updateAccountPartial({ + updateCurrentAccountPartial({ twoFactorEnabled: false, }); }).catch(error => { diff --git a/packages/frontend/src/pages/settings/accessibility.vue b/packages/frontend/src/pages/settings/accessibility.vue index 3dbb039a17..f7b1e7d2a0 100644 --- a/packages/frontend/src/pages/settings/accessibility.vue +++ b/packages/frontend/src/pages/settings/accessibility.vue @@ -60,6 +60,17 @@ SPDX-License-Identifier: AGPL-3.0-only </SearchMarker> </div> + <SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']"> + <MkPreferenceContainer k="menuStyle"> + <MkSelect v-model="menuStyle"> + <template #label><SearchLabel>{{ i18n.ts.menuStyle }}</SearchLabel></template> + <option value="auto">{{ i18n.ts.auto }}</option> + <option value="popup">{{ i18n.ts.popup }}</option> + <option value="drawer">{{ i18n.ts.drawer }}</option> + </MkSelect> + </MkPreferenceContainer> + </SearchMarker> + <SearchMarker :keywords="['contextmenu', 'system', 'native']"> <MkPreferenceContainer k="contextMenu"> <MkSelect v-model="contextMenu"> @@ -70,6 +81,22 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSelect> </MkPreferenceContainer> </SearchMarker> + + <SearchMarker :keywords="['font', 'size']"> + <MkRadios v-model="fontSize"> + <template #label><SearchLabel>{{ i18n.ts.fontSize }}</SearchLabel></template> + <option :value="null"><span style="font-size: 14px;">Aa</span></option> + <option value="1"><span style="font-size: 15px;">Aa</span></option> + <option value="2"><span style="font-size: 16px;">Aa</span></option> + <option value="3"><span style="font-size: 17px;">Aa</span></option> + </MkRadios> + </SearchMarker> + + <SearchMarker :keywords="['font', 'system', 'native']"> + <MkSwitch v-model="useSystemFont"> + <template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template> + </MkSwitch> + </SearchMarker> </div> </SearchMarker> </template> @@ -84,6 +111,8 @@ import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; +import { miLocalStorage } from '@/local-storage.js'; +import MkRadios from '@/components/MkRadios.vue'; const reduceAnimation = prefer.model('animation', v => !v, v => !v); const animatedMfm = prefer.model('animatedMfm'); @@ -92,10 +121,32 @@ const keepScreenOn = prefer.model('keepScreenOn'); const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe'); const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer'); const contextMenu = prefer.model('contextMenu'); +const menuStyle = prefer.model('menuStyle'); + +const fontSize = ref(miLocalStorage.getItem('fontSize')); +const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); + +watch(fontSize, () => { + if (fontSize.value == null) { + miLocalStorage.removeItem('fontSize'); + } else { + miLocalStorage.setItem('fontSize', fontSize.value); + } +}); + +watch(useSystemFont, () => { + if (useSystemFont.value) { + miLocalStorage.setItem('useSystemFont', 't'); + } else { + miLocalStorage.removeItem('useSystemFont'); + } +}); watch([ keepScreenOn, contextMenu, + fontSize, + useSystemFont, ], async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); diff --git a/packages/frontend/src/pages/settings/account-data.vue b/packages/frontend/src/pages/settings/account-data.vue index 0987e4b248..e955deeae9 100644 --- a/packages/frontend/src/pages/settings/account-data.vue +++ b/packages/frontend/src/pages/settings/account-data.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<SearchMarker path="/settings/account-data" :label="i18n.ts._settings.accountData" :keywords="['import', 'export', 'data']" icon="ti ti-package"> +<SearchMarker path="/settings/account-data" :label="i18n.ts._settings.accountData" :keywords="['import', 'export', 'data', 'archive']" icon="ti ti-package"> <div class="_gaps_m"> <MkFeatureBanner icon="/client-assets/package_3d.png" color="#ff9100"> <SearchKeyword>{{ i18n.ts._settings.accountDataBanner }}</SearchKeyword> @@ -195,14 +195,14 @@ import { misskeyApi } from '@/utility/misskey-api.js'; import { selectFile } from '@/utility/select-file.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -import { $i } from '@/account.js'; -import { store } from '@/store.js'; +import { $i } from '@/i.js'; import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; +import { prefer } from '@/preferences.js'; const excludeMutingUsers = ref(false); const excludeInactiveUsers = ref(false); const noteType = ref(null); -const withReplies = ref(store.s.defaultWithReplies); +const withReplies = ref(prefer.s.defaultFollowWithReplies); const onExportSuccess = () => { os.alert({ diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue index 2cf65be2d0..749ae5147f 100644 --- a/packages/frontend/src/pages/settings/accounts.vue +++ b/packages/frontend/src/pages/settings/accounts.vue @@ -4,80 +4,51 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div class=""> - <FormSuspense :p="init"> - <div class="_gaps"> - <div class="_buttons"> - <MkButton primary @click="addAccount"><i class="ti ti-plus"></i> {{ i18n.ts.addAccount }}</MkButton> - <MkButton @click="init"><i class="ti ti-refresh"></i> {{ i18n.ts.reloadAccountsList }}</MkButton> - </div> - - <template v-for="[id, user] in accounts"> - <MkUserCardMini v-if="user != null" :key="user.id" :user="user" :class="$style.user" @click.prevent="menu(user, $event)"/> - <button v-else v-panel class="_button" :class="$style.unknownUser" @click="menu(id, $event)"> - <div :class="$style.unknownUserAvatarMock"><i class="ti ti-user-question"></i></div> - <div> - <div :class="$style.unknownUserTitle">{{ i18n.ts.unknown }}</div> - <div :class="$style.unknownUserSub">ID: <span class="_monospace">{{ id }}</span></div> - </div> - </button> - </template> +<SearchMarker path="/settings/accounts" :label="i18n.ts.accounts" :keywords="['accounts']" icon="ti ti-users"> + <div class="_gaps"> + <div class="_buttons"> + <MkButton primary @click="addAccount"><i class="ti ti-plus"></i> {{ i18n.ts.addAccount }}</MkButton> + <!--<MkButton @click="refreshAllAccounts"><i class="ti ti-refresh"></i></MkButton>--> </div> - </FormSuspense> -</div> + + <MkUserCardMini v-for="x in accounts" :key="x[0] + x[1].id" :user="x[1]" :class="$style.user" @click.prevent="menu(x[0], x[1], $event)"/> + </div> +</SearchMarker> </template> <script lang="ts" setup> import { ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; -import FormSuspense from '@/components/form/suspense.vue'; +import type { MenuItem } from '@/types/menu.js'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { getAccounts, removeAccount as _removeAccount, login, $i, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/account.js'; +import { $i } from '@/i.js'; +import { switchAccount, removeAccount, login, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/accounts.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; -import type { MenuItem } from '@/types/menu.js'; +import { prefer } from '@/preferences.js'; -const storedAccounts = ref<{ id: string, token: string }[] | null>(null); -const accounts = ref(new Map<string, Misskey.entities.UserDetailed | null>()); +const accounts = prefer.r.accounts; -const init = async () => { - getAccounts().then(accounts => { - storedAccounts.value = accounts.filter(x => x.id !== $i!.id); +function refreshAllAccounts() { + // TODO +} - return misskeyApi('users/show', { - userIds: storedAccounts.value.map(x => x.id), - }); - }).then(response => { - if (storedAccounts.value == null) return; - accounts.value = new Map(storedAccounts.value.map(x => [x.id, response.find((y: Misskey.entities.UserDetailed) => y.id === x.id) ?? null])); - }); -}; - -function menu(account: Misskey.entities.UserDetailed | string, ev: MouseEvent) { +function menu(host: string, account: Misskey.entities.UserDetailed, ev: MouseEvent) { let menu: MenuItem[]; - if (typeof account === 'string') { - menu = [{ - text: i18n.ts.logout, - icon: 'ti ti-trash', - danger: true, - action: () => removeAccount(account), - }]; - } else { - menu = [{ - text: i18n.ts.switch, - icon: 'ti ti-switch-horizontal', - action: () => switchAccount(account.id), - }, { - text: i18n.ts.logout, - icon: 'ti ti-trash', - danger: true, - action: () => removeAccount(account.id), - }]; - } + menu = [{ + text: i18n.ts.switch, + icon: 'ti ti-switch-horizontal', + action: () => switchAccount(host, account.id), + }, { + text: i18n.ts.logout, + icon: 'ti ti-trash', + danger: true, + action: () => removeAccount(host, account.id), + }]; os.popupMenu(menu, ev.currentTarget ?? ev.target); } @@ -92,16 +63,10 @@ function addAccount(ev: MouseEvent) { }], ev.currentTarget ?? ev.target); } -async function removeAccount(id: string) { - await _removeAccount(id); - accounts.value.delete(id); -} - function addExistingAccount() { getAccountWithSigninDialog().then((res) => { if (res != null) { os.success(); - init(); } }); } @@ -109,21 +74,11 @@ function addExistingAccount() { function createAccount() { getAccountWithSignupDialog().then((res) => { if (res != null) { - switchAccountWithToken(res.token); + login(res.token); } }); } -async function switchAccount(id: string) { - const fetchedAccounts = await getAccounts(); - const token = fetchedAccounts.find(x => x.id === id)!.token; - switchAccountWithToken(token); -} - -function switchAccountWithToken(token: string) { - login(token); -} - const headerActions = computed(() => []); const headerTabs = computed(() => []); diff --git a/packages/frontend/src/pages/settings/appearance.vue b/packages/frontend/src/pages/settings/appearance.vue deleted file mode 100644 index f68a77de67..0000000000 --- a/packages/frontend/src/pages/settings/appearance.vue +++ /dev/null @@ -1,326 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<SearchMarker path="/settings/appearance" :label="i18n.ts.appearance" :keywords="['appearance']" icon="ti ti-device-desktop"> - <div class="_gaps_m"> - <MkFeatureBanner icon="/client-assets/desktop_computer_3d.png" color="#eaff00"> - <SearchKeyword>{{ i18n.ts._settings.appearanceBanner }}</SearchKeyword> - </MkFeatureBanner> - - <FormSection first> - <div class="_gaps_m"> - <div class="_gaps_s"> - <SearchMarker :keywords="['blur']"> - <MkPreferenceContainer k="useBlurEffect"> - <MkSwitch v-model="useBlurEffect"> - <template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['blur', 'modal']"> - <MkPreferenceContainer k="useBlurEffectForModal"> - <MkSwitch v-model="useBlurEffectForModal"> - <template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail']"> - <MkPreferenceContainer k="highlightSensitiveMedia"> - <MkSwitch v-model="highlightSensitiveMedia"> - <template #label><SearchLabel>{{ i18n.ts.highlightSensitiveMedia }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['avatar', 'icon', 'square']"> - <MkPreferenceContainer k="squareAvatars"> - <MkSwitch v-model="squareAvatars"> - <template #label><SearchLabel>{{ i18n.ts.squareAvatars }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']"> - <MkPreferenceContainer k="showAvatarDecorations"> - <MkSwitch v-model="showAvatarDecorations"> - <template #label><SearchLabel>{{ i18n.ts.showAvatarDecorations }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['note', 'timeline', 'gap']"> - <MkPreferenceContainer k="showGapBetweenNotesInTimeline"> - <MkSwitch v-model="showGapBetweenNotesInTimeline"> - <template #label><SearchLabel>{{ i18n.ts.showGapBetweenNotesInTimeline }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['effect', 'show']"> - <MkPreferenceContainer k="enableSeasonalScreenEffect"> - <MkSwitch v-model="enableSeasonalScreenEffect"> - <template #label><SearchLabel>{{ i18n.ts.seasonalScreenEffect }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - </div> - - <SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']"> - <MkPreferenceContainer k="menuStyle"> - <MkSelect v-model="menuStyle"> - <template #label><SearchLabel>{{ i18n.ts.menuStyle }}</SearchLabel></template> - <option value="auto">{{ i18n.ts.auto }}</option> - <option value="popup">{{ i18n.ts.popup }}</option> - <option value="drawer">{{ i18n.ts.drawer }}</option> - </MkSelect> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['emoji', 'style', 'native', 'system', 'fluent', 'twemoji']"> - <MkPreferenceContainer k="emojiStyle"> - <div> - <MkRadios v-model="emojiStyle"> - <template #label><SearchLabel>{{ i18n.ts.emojiStyle }}</SearchLabel></template> - <option value="native">{{ i18n.ts.native }}</option> - <option value="fluentEmoji">Fluent Emoji</option> - <option value="twemoji">Twemoji</option> - </MkRadios> - <div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div> - </div> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['font', 'size']"> - <MkRadios v-model="fontSize"> - <template #label><SearchLabel>{{ i18n.ts.fontSize }}</SearchLabel></template> - <option :value="null"><span style="font-size: 14px;">Aa</span></option> - <option value="1"><span style="font-size: 15px;">Aa</span></option> - <option value="2"><span style="font-size: 16px;">Aa</span></option> - <option value="3"><span style="font-size: 17px;">Aa</span></option> - </MkRadios> - </SearchMarker> - - <SearchMarker :keywords="['font', 'system', 'native']"> - <MkSwitch v-model="useSystemFont"> - <template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template> - </MkSwitch> - </SearchMarker> - </div> - </FormSection> - - <SearchMarker :keywords="['note', 'display']"> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.displayOfNote }}</SearchLabel></template> - - <div class="_gaps_m"> - <SearchMarker :keywords="['reaction', 'size', 'scale', 'display']"> - <MkPreferenceContainer k="reactionsDisplaySize"> - <MkRadios v-model="reactionsDisplaySize"> - <template #label><SearchLabel>{{ i18n.ts.reactionsDisplaySize }}</SearchLabel></template> - <option value="small">{{ i18n.ts.small }}</option> - <option value="medium">{{ i18n.ts.medium }}</option> - <option value="large">{{ i18n.ts.large }}</option> - </MkRadios> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['reaction', 'size', 'scale', 'display', 'width', 'limit']"> - <MkPreferenceContainer k="limitWidthOfReaction"> - <MkSwitch v-model="limitWidthOfReaction"> - <template #label><SearchLabel>{{ i18n.ts.limitWidthOfReaction }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height']"> - <MkPreferenceContainer k="mediaListWithOneImageAppearance"> - <MkRadios v-model="mediaListWithOneImageAppearance"> - <template #label><SearchLabel>{{ i18n.ts.mediaListWithOneImageAppearance }}</SearchLabel></template> - <option value="expand">{{ i18n.ts.default }}</option> - <option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option> - <option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option> - <option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option> - </MkRadios> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation']"> - <MkPreferenceContainer k="instanceTicker"> - <MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker"> - <template #label><SearchLabel>{{ i18n.ts.instanceTicker }}</SearchLabel></template> - <option value="none">{{ i18n.ts._instanceTicker.none }}</option> - <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> - <option value="always">{{ i18n.ts._instanceTicker.always }}</option> - </MkSelect> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility']"> - <MkPreferenceContainer k="nsfw"> - <MkSelect v-model="nsfw"> - <template #label><SearchLabel>{{ i18n.ts.displayOfSensitiveMedia }}</SearchLabel></template> - <option value="respect">{{ i18n.ts._displayOfSensitiveMedia.respect }}</option> - <option value="ignore">{{ i18n.ts._displayOfSensitiveMedia.ignore }}</option> - <option value="force">{{ i18n.ts._displayOfSensitiveMedia.force }}</option> - </MkSelect> - </MkPreferenceContainer> - </SearchMarker> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['notification', 'display']"> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.notificationDisplay }}</SearchLabel></template> - - <div class="_gaps_m"> - <SearchMarker :keywords="['position']"> - <MkPreferenceContainer k="notificationPosition"> - <MkRadios v-model="notificationPosition"> - <template #label><SearchLabel>{{ i18n.ts.position }}</SearchLabel></template> - <option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option> - <option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option> - <option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option> - <option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option> - <option value="close"><i class="ti ti-x"></i> {{ i18n.ts.close }}</option> - </MkRadios> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['stack', 'axis', 'direction']"> - <MkPreferenceContainer k="notificationStackAxis"> - <MkRadios v-model="notificationStackAxis"> - <template #label><SearchLabel>{{ i18n.ts.stackAxis }}</SearchLabel></template> - <option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option> - <option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option> - </MkRadios> - </MkPreferenceContainer> - </SearchMarker> - - <MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton> - </div> - </FormSection> - </SearchMarker> - - <FormSection> - <FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink> - </FormSection> - </div> -</SearchMarker> -</template> - -<script lang="ts" setup> -import { computed, ref, watch } from 'vue'; -import * as Misskey from 'misskey-js'; -import MkSwitch from '@/components/MkSwitch.vue'; -import MkSelect from '@/components/MkSelect.vue'; -import MkRadios from '@/components/MkRadios.vue'; -import { prefer } from '@/preferences.js'; -import { reloadAsk } from '@/utility/reload-ask.js'; -import { i18n } from '@/i18n.js'; -import { definePage } from '@/page.js'; -import { miLocalStorage } from '@/local-storage.js'; -import FormLink from '@/components/form/link.vue'; -import { globalEvents } from '@/events.js'; -import { claimAchievement } from '@/utility/achievements.js'; -import MkButton from '@/components/MkButton.vue'; -import FormSection from '@/components/form/section.vue'; -import { instance } from '@/instance.js'; -import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; -import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; - -const fontSize = ref(miLocalStorage.getItem('fontSize')); -const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); - -const showAvatarDecorations = prefer.model('showAvatarDecorations'); -const emojiStyle = prefer.model('emojiStyle'); -const menuStyle = prefer.model('menuStyle'); -const useBlurEffectForModal = prefer.model('useBlurEffectForModal'); -const useBlurEffect = prefer.model('useBlurEffect'); -const highlightSensitiveMedia = prefer.model('highlightSensitiveMedia'); -const squareAvatars = prefer.model('squareAvatars'); -const enableSeasonalScreenEffect = prefer.model('enableSeasonalScreenEffect'); -const showGapBetweenNotesInTimeline = prefer.model('showGapBetweenNotesInTimeline'); -const mediaListWithOneImageAppearance = prefer.model('mediaListWithOneImageAppearance'); -const reactionsDisplaySize = prefer.model('reactionsDisplaySize'); -const limitWidthOfReaction = prefer.model('limitWidthOfReaction'); -const notificationPosition = prefer.model('notificationPosition'); -const notificationStackAxis = prefer.model('notificationStackAxis'); -const nsfw = prefer.model('nsfw'); -const instanceTicker = prefer.model('instanceTicker'); - -watch(fontSize, () => { - if (fontSize.value == null) { - miLocalStorage.removeItem('fontSize'); - } else { - miLocalStorage.setItem('fontSize', fontSize.value); - } -}); - -watch(useSystemFont, () => { - if (useSystemFont.value) { - miLocalStorage.setItem('useSystemFont', 't'); - } else { - miLocalStorage.removeItem('useSystemFont'); - } -}); - -watch([ - fontSize, - useSystemFont, - squareAvatars, - highlightSensitiveMedia, - enableSeasonalScreenEffect, - showGapBetweenNotesInTimeline, - mediaListWithOneImageAppearance, - reactionsDisplaySize, - limitWidthOfReaction, - mediaListWithOneImageAppearance, - reactionsDisplaySize, - limitWidthOfReaction, - instanceTicker, -], async () => { - await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); -}); - -let smashCount = 0; -let smashTimer: number | null = null; - -function testNotification(): void { - const notification: Misskey.entities.Notification = { - id: Math.random().toString(), - createdAt: new Date().toUTCString(), - isRead: false, - type: 'test', - }; - - globalEvents.emit('clientNotification', notification); - - // セルフ通知破壊 実績関連 - smashCount++; - if (smashCount >= 10) { - claimAchievement('smashTestNotificationButton'); - smashCount = 0; - } - if (smashTimer) { - clearTimeout(smashTimer); - } - smashTimer = window.setTimeout(() => { - smashCount = 0; - }, 300); -} - -const headerActions = computed(() => []); - -const headerTabs = computed(() => []); - -definePage(() => ({ - title: i18n.ts.appearance, - icon: 'ti ti-device-desktop', -})); -</script> diff --git a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue index 3c9914b4e2..6b3bb1b513 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed } from 'vue'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue index 40542ad5b2..e4803eda2e 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue @@ -51,7 +51,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import { i18n } from '@/i18n.js'; import MkRange from '@/components/MkRange.vue'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue index ba25eee175..91549e5240 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.vue @@ -54,7 +54,7 @@ import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import MkInfo from '@/components/MkInfo.vue'; import { definePage } from '@/page.js'; diff --git a/packages/frontend/src/pages/settings/deck.vue b/packages/frontend/src/pages/settings/deck.vue index 2c4ec01344..9b2b40374e 100644 --- a/packages/frontend/src/pages/settings/deck.vue +++ b/packages/frontend/src/pages/settings/deck.vue @@ -6,19 +6,45 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <SearchMarker path="/settings/deck" :label="i18n.ts.deck" :keywords="['deck', 'ui']" icon="ti ti-columns"> <div class="_gaps_m"> - <MkSwitch :modelValue="profilesSyncEnabled" @update:modelValue="changeProfilesSyncEnabled">{{ i18n.ts._deck.enableSyncBetweenDevicesForProfiles }}</MkSwitch> + <SearchMarker :keywords="['sync', 'profiles', 'devices']"> + <MkSwitch :modelValue="profilesSyncEnabled" @update:modelValue="changeProfilesSyncEnabled"> + <template #label><SearchLabel>{{ i18n.ts._deck.enableSyncBetweenDevicesForProfiles }}</SearchLabel></template> + </MkSwitch> + </SearchMarker> - <MkSwitch v-model="useSimpleUiForNonRootPages">{{ i18n.ts._deck.useSimpleUiForNonRootPages }}</MkSwitch> + <SearchMarker :keywords="['ui', 'root', 'page']"> + <MkPreferenceContainer k="deck.useSimpleUiForNonRootPages"> + <MkSwitch v-model="useSimpleUiForNonRootPages"> + <template #label><SearchLabel>{{ i18n.ts._deck.useSimpleUiForNonRootPages }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> - <MkSwitch v-model="navWindow">{{ i18n.ts.defaultNavigationBehaviour }}: {{ i18n.ts.openInWindow }}</MkSwitch> + <SearchMarker :keywords="['default', 'navigation', 'behaviour', 'window']"> + <MkPreferenceContainer k="deck.navWindow"> + <MkSwitch v-model="navWindow"> + <template #label><SearchLabel>{{ i18n.ts.defaultNavigationBehaviour }}</SearchLabel>: {{ i18n.ts.openInWindow }}</template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> - <MkSwitch v-model="alwaysShowMainColumn">{{ i18n.ts._deck.alwaysShowMainColumn }}</MkSwitch> + <SearchMarker :keywords="['always', 'show', 'main', 'column']"> + <MkPreferenceContainer k="deck.alwaysShowMainColumn"> + <MkSwitch v-model="alwaysShowMainColumn"> + <template #label><SearchLabel>{{ i18n.ts._deck.alwaysShowMainColumn }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> - <MkRadios v-model="columnAlign"> - <template #label>{{ i18n.ts._deck.columnAlign }}</template> - <option value="left">{{ i18n.ts.left }}</option> - <option value="center">{{ i18n.ts.center }}</option> - </MkRadios> + <SearchMarker :keywords="['column', 'align']"> + <MkPreferenceContainer k="deck.columnAlign"> + <MkRadios v-model="columnAlign"> + <template #label><SearchLabel>{{ i18n.ts._deck.columnAlign }}</SearchLabel></template> + <option value="left">{{ i18n.ts.left }}</option> + <option value="center">{{ i18n.ts.center }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> </div> </SearchMarker> </template> @@ -30,6 +56,7 @@ import MkRadios from '@/components/MkRadios.vue'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { prefer } from '@/preferences.js'; +import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; const navWindow = prefer.model('deck.navWindow'); const useSimpleUiForNonRootPages = prefer.model('deck.useSimpleUiForNonRootPages'); diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index 34941d5af0..0b25ee5e37 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -104,7 +104,7 @@ import bytes from '@/filters/bytes.js'; import MkChart from '@/components/MkChart.vue'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { prefer } from '@/preferences.js'; import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue index 0cbda44882..10e2a000d4 100644 --- a/packages/frontend/src/pages/settings/email.vue +++ b/packages/frontend/src/pages/settings/email.vue @@ -67,7 +67,7 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkDisableSection from '@/components/MkDisableSection.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { instance } from '@/instance.js'; diff --git a/packages/frontend/src/pages/settings/emoji-palette.palette.vue b/packages/frontend/src/pages/settings/emoji-palette.palette.vue new file mode 100644 index 0000000000..33d1d7c9fa --- /dev/null +++ b/packages/frontend/src/pages/settings/emoji-palette.palette.vue @@ -0,0 +1,166 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkFolder :defaultOpen="true"> + <template #icon><i class="ti ti-palette"></i></template> + <template #label>{{ palette.name === '' ? '(' + i18n.ts.noName + ')' : palette.name }}</template> + <template #footer> + <div class="_buttons"> + <MkButton @click="rename"><i class="ti ti-pencil"></i> {{ i18n.ts.rename }}</MkButton> + <MkButton @click="copy"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton> + <MkButton danger @click="paste"><i class="ti ti-clipboard"></i> {{ i18n.ts.paste }}</MkButton> + <MkButton danger iconOnly style="margin-left: auto;" @click="del"><i class="ti ti-trash"></i></MkButton> + </div> + </template> + + <div> + <div v-panel style="border-radius: 6px;"> + <Sortable + v-model="emojis" + :class="$style.emojis" + :itemKey="item => item" + :animation="150" + :delay="100" + :delayOnTouchOnly="true" + :group="{ name: 'SortableEmojiPalettes' }" + > + <template #item="{element}"> + <button class="_button" :class="$style.emojisItem" @click="remove(element, $event)"> + <MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true" :fallbackToImage="true"/> + <MkEmoji v-else :emoji="element" :normal="true"/> + </button> + </template> + <template #footer> + <button class="_button" :class="$style.emojisAdd" @click="pick"> + <i class="ti ti-plus"></i> + </button> + </template> + </Sortable> + </div> + <div :class="$style.editorCaption">{{ i18n.ts.reactionSettingDescription2 }}</div> + </div> +</MkFolder> +</template> + +<script lang="ts" setup> +import { ref, watch } from 'vue'; +import Sortable from 'vuedraggable'; +import MkButton from '@/components/MkButton.vue'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { deepClone } from '@/utility/clone.js'; +import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; +import MkEmoji from '@/components/global/MkEmoji.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; + +const props = defineProps<{ + palette: { + id: string; + name: string; + emojis: string[]; + }; +}>(); + +const emit = defineEmits<{ + (ev: 'updateEmojis', emojis: string[]): void, + (ev: 'updateName', name: string): void, + (ev: 'del'): void, +}>(); + +const emojis = ref<string[]>(deepClone(props.palette.emojis)); + +watch(emojis, () => { + emit('updateEmojis', emojis.value); +}, { deep: true }); + +function remove(reaction: string, ev: MouseEvent) { + os.popupMenu([{ + text: i18n.ts.remove, + action: () => { + emojis.value = emojis.value.filter(x => x !== reaction); + }, + }], getHTMLElement(ev)); +} + +function pick(ev: MouseEvent) { + os.pickEmoji(getHTMLElement(ev), { + showPinned: false, + }).then(it => { + const emoji = it; + if (!emojis.value.includes(emoji)) { + emojis.value.push(emoji); + } + }); +} + +function getHTMLElement(ev: MouseEvent): HTMLElement { + const target = ev.currentTarget ?? ev.target; + return target as HTMLElement; +} + +function rename() { + os.inputText({ + title: i18n.ts.rename, + default: props.palette.name, + }).then(({ canceled, result: name }) => { + if (canceled) return; + if (name != null) { + emit('updateName', name); + } + }); +} + +function copy() { + copyToClipboard(emojis.value.join(' ')); +} + +function paste() { + // TODO: validate + navigator.clipboard.readText().then(text => { + emojis.value = text.split(' '); + }); +} + +function del(ev: MouseEvent) { + os.popupMenu([{ + text: i18n.ts.delete, + action: () => { + emit('del'); + }, + }], ev.currentTarget ?? ev.target); +} +</script> + +<style lang="scss" module> +.tab { + margin: calc(var(--MI-margin) / 2) 0; + padding: calc(var(--MI-margin) / 2) 0; + background: var(--MI_THEME-bg); +} + +.emojis { + padding: 12px; + font-size: 1.1em; +} + +.emojisItem { + display: inline-block; + padding: 8px; + cursor: move; +} + +.emojisAdd { + display: inline-block; + padding: 8px; +} + +.editorCaption { + font-size: 0.85em; + padding: 8px 0 0 0; + color: var(--MI_THEME-fgTransparentWeak); +} +</style> diff --git a/packages/frontend/src/pages/settings/emoji-palette.vue b/packages/frontend/src/pages/settings/emoji-palette.vue new file mode 100644 index 0000000000..27523e470e --- /dev/null +++ b/packages/frontend/src/pages/settings/emoji-palette.vue @@ -0,0 +1,285 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<SearchMarker path="/settings/emoji-palette" :label="i18n.ts.emojiPalette" :keywords="['emoji', 'palette']" icon="ti ti-mood-happy"> + <div class="_gaps_m"> + <FormSection first> + <template #label>{{ i18n.ts._emojiPalette.palettes }}</template> + + <div class="_gaps_s"> + <XPalette + v-for="palette in prefer.r.emojiPalettes.value" + :key="palette.id" + :palette="palette" + @updateEmojis="emojis => updatePaletteEmojis(palette.id, emojis)" + @updateName="name => updatePaletteName(palette.id, name)" + @del="delPalette(palette.id)" + /> + <MkButton primary rounded style="margin: auto;" @click="addPalette"><i class="ti ti-plus"></i></MkButton> + </div> + </FormSection> + + <FormSection> + <div class="_gaps_m"> + <SearchMarker :keywords="['sync', 'palettes', 'devices']"> + <MkSwitch :modelValue="palettesSyncEnabled" @update:modelValue="changePalettesSyncEnabled"> + <template #label><SearchLabel>{{ i18n.ts._emojiPalette.enableSyncBetweenDevicesForPalettes }}</SearchLabel></template> + </MkSwitch> + </SearchMarker> + </div> + </FormSection> + + <FormSection> + <div class="_gaps_m"> + <SearchMarker :keywords="['main', 'palette']"> + <MkPreferenceContainer k="emojiPaletteForMain"> + <MkSelect v-model="emojiPaletteForMain"> + <template #label><SearchLabel>{{ i18n.ts._emojiPalette.paletteForMain }}</SearchLabel></template> + <option key="-" :value="null">({{ i18n.ts.auto }})</option> + <option v-for="palette in prefer.r.emojiPalettes.value" :key="palette.id" :value="palette.id">{{ palette.name === '' ? '(' + i18n.ts.noName + ')' : palette.name }}</option> + </MkSelect> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['reaction', 'palette']"> + <MkPreferenceContainer k="emojiPaletteForReaction"> + <MkSelect v-model="emojiPaletteForReaction"> + <template #label><SearchLabel>{{ i18n.ts._emojiPalette.paletteForReaction }}</SearchLabel></template> + <option key="-" :value="null">({{ i18n.ts.auto }})</option> + <option v-for="palette in prefer.r.emojiPalettes.value" :key="palette.id" :value="palette.id">{{ palette.name === '' ? '(' + i18n.ts.noName + ')' : palette.name }}</option> + </MkSelect> + </MkPreferenceContainer> + </SearchMarker> + </div> + </FormSection> + + <FormSection> + <div class="_gaps_m"> + <SearchMarker :keywords="['emoji', 'default', 'like']"> + <MkPreferenceContainer k="defaultLike"> + <FromSlot> + <template #label>{{ i18n.ts.defaultLike }}</template> + <MkCustomEmoji v-if="like && like.startsWith(':')" style="max-height: 3em; font-size: 1.1em;" :useOriginalSize="false" :name="like" :normal="true" :noStyle="true"/> + <MkEmoji v-else-if="like && !like.startsWith(':')" :emoji="like" style="max-height: 3em; font-size: 1.1em;" :normal="true" :noStyle="true"/> + <span v-else-if="!like">{{ i18n.ts.notSet }}</span> + <div class="_buttons" style="padding-top: 8px;"> + <MkButton rounded :small="true" inline @click="chooseNewLike"><i class="ti ti-plus"></i></MkButton> + <MkButton rounded :small="true" inline @click="resetLike"><i class="ti ti-refresh"></i></MkButton> + </div> + </FromSlot> + </MkPreferenceContainer> + </SearchMarker> + </div> + </FormSection> + + <SearchMarker :keywords="['emoji', 'picker', 'display']"> + <FormSection> + <template #label><SearchLabel>{{ i18n.ts.emojiPickerDisplay }}</SearchLabel></template> + + <div class="_gaps_m"> + <SearchMarker :keywords="['emoji', 'picker', 'scale', 'size']"> + <MkPreferenceContainer k="emojiPickerScale"> + <MkRadios v-model="emojiPickerScale"> + <template #label><SearchLabel>{{ i18n.ts.size }}</SearchLabel></template> + <option :value="1">{{ i18n.ts.small }}</option> + <option :value="2">{{ i18n.ts.medium }}</option> + <option :value="3">{{ i18n.ts.large }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['emoji', 'picker', 'width', 'column', 'size']"> + <MkPreferenceContainer k="emojiPickerWidth"> + <MkRadios v-model="emojiPickerWidth"> + <template #label><SearchLabel>{{ i18n.ts.numberOfColumn }}</SearchLabel></template> + <option :value="1">5</option> + <option :value="2">6</option> + <option :value="3">7</option> + <option :value="4">8</option> + <option :value="5">9</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['emoji', 'picker', 'height', 'size']"> + <MkPreferenceContainer k="emojiPickerHeight"> + <MkRadios v-model="emojiPickerHeight"> + <template #label><SearchLabel>{{ i18n.ts.height }}</SearchLabel></template> + <option :value="1">{{ i18n.ts.small }}</option> + <option :value="2">{{ i18n.ts.medium }}</option> + <option :value="3">{{ i18n.ts.large }}</option> + <option :value="4">{{ i18n.ts.large }}+</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['emoji', 'picker', 'style']"> + <MkPreferenceContainer k="emojiPickerStyle"> + <MkSelect v-model="emojiPickerStyle"> + <template #label><SearchLabel>{{ i18n.ts.style }}</SearchLabel></template> + <template #caption>{{ i18n.ts.needReloadToApply }}</template> + <option value="auto">{{ i18n.ts.auto }}</option> + <option value="popup">{{ i18n.ts.popup }}</option> + <option value="drawer">{{ i18n.ts.drawer }}</option> + </MkSelect> + </MkPreferenceContainer> + </SearchMarker> + + <MkButton @click="previewPicker"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton> + </div> + </FormSection> + </SearchMarker> + </div> +</SearchMarker> +</template> + +<script lang="ts" setup> +import { computed, ref, watch } from 'vue'; +import { v4 as uuid } from 'uuid'; +import XPalette from './emoji-palette.palette.vue'; +import MkRadios from '@/components/MkRadios.vue'; +import MkButton from '@/components/MkButton.vue'; +import FormSection from '@/components/form/section.vue'; +import MkSelect from '@/components/MkSelect.vue'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { definePage } from '@/page.js'; +import MkFolder from '@/components/MkFolder.vue'; +import { prefer } from '@/preferences.js'; +import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import { emojiPicker } from '@/utility/emoji-picker.js'; +import { store } from '@/store.js'; + +const emojiPaletteForReaction = prefer.model('emojiPaletteForReaction'); +const emojiPaletteForMain = prefer.model('emojiPaletteForMain'); +const emojiPickerScale = prefer.model('emojiPickerScale'); +const emojiPickerWidth = prefer.model('emojiPickerWidth'); +const emojiPickerHeight = prefer.model('emojiPickerHeight'); +const emojiPickerStyle = prefer.model('emojiPickerStyle'); + +const palettesSyncEnabled = ref(prefer.isSyncEnabled('emojiPalettes')); + +const like = computed(store.makeGetterSetter('like')); + +function chooseNewLike(ev: MouseEvent) { + os.pickEmoji(getHTMLElement(ev), { + showPinned: false, + }).then(async emoji => { + store.set('like', emoji as string); + }); +} + +async function resetLike() { + store.set('like', null); +} + +function changePalettesSyncEnabled(value: boolean) { + if (value) { + prefer.enableSync('emojiPalettes').then((res) => { + if (res == null) return; + if (res.enabled) palettesSyncEnabled.value = true; + }); + } else { + prefer.disableSync('emojiPalettes'); + palettesSyncEnabled.value = false; + } +} + +function addPalette() { + prefer.commit('emojiPalettes', [ + ...prefer.s.emojiPalettes, + { + id: uuid(), + name: '', + emojis: [], + }, + ]); +} + +function updatePaletteEmojis(id: string, emojis: string[]) { + prefer.commit('emojiPalettes', prefer.s.emojiPalettes.map(palette => { + if (palette.id === id) { + return { + ...palette, + emojis, + }; + } else { + return palette; + } + })); +} + +function updatePaletteName(id: string, name: string) { + prefer.commit('emojiPalettes', prefer.s.emojiPalettes.map(palette => { + if (palette.id === id) { + return { + ...palette, + name, + }; + } else { + return palette; + } + })); +} + +function delPalette(id: string) { + if (prefer.s.emojiPalettes.length === 1) { + addPalette(); + } + prefer.commit('emojiPalettes', prefer.s.emojiPalettes.filter(palette => palette.id !== id)); + if (prefer.s.emojiPaletteForMain === id) { + prefer.commit('emojiPaletteForMain', null); + } + if (prefer.s.emojiPaletteForReaction === id) { + prefer.commit('emojiPaletteForReaction', null); + } +} + +function getHTMLElement(ev: MouseEvent): HTMLElement { + const target = ev.currentTarget ?? ev.target; + return target as HTMLElement; +} + +function previewPicker(ev: MouseEvent) { + emojiPicker.show(getHTMLElement(ev)); +} + +definePage(() => ({ + title: i18n.ts.emojiPalette, + icon: 'ti ti-mood-happy', +})); +</script> + +<style lang="scss" module> +.tab { + margin: calc(var(--MI-margin) / 2) 0; + padding: calc(var(--MI-margin) / 2) 0; + background: var(--MI_THEME-bg); +} + +.emojis { + padding: 12px; + font-size: 1.1em; +} + +.emojisItem { + display: inline-block; + padding: 8px; + cursor: move; +} + +.emojisAdd { + display: inline-block; + padding: 8px; +} + +.editorCaption { + font-size: 0.85em; + padding: 8px 0 0 0; + color: var(--MI_THEME-fgTransparentWeak); +} +</style> diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue deleted file mode 100644 index 92cf3f0a95..0000000000 --- a/packages/frontend/src/pages/settings/emoji-picker.vue +++ /dev/null @@ -1,314 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<div class="_gaps_m"> - <MkFolder :defaultOpen="true"> - <template #icon><i class="ti ti-pin"></i></template> - <template #label>{{ i18n.ts.pinned }} ({{ i18n.ts.reaction }})</template> - <template #caption>{{ i18n.ts.pinnedEmojisForReactionSettingDescription }}</template> - - <div class="_gaps"> - <div> - <div v-panel style="border-radius: 6px;"> - <Sortable - v-model="pinnedEmojisForReaction" - :class="$style.emojis" - :itemKey="item => item" - :animation="150" - :delay="100" - :delayOnTouchOnly="true" - > - <template #item="{element}"> - <button class="_button" :class="$style.emojisItem" @click="removeReaction(element, $event)"> - <MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true" :fallbackToImage="true"/> - <MkEmoji v-else :emoji="element" :normal="true"/> - </button> - </template> - <template #footer> - <button class="_button" :class="$style.emojisAdd" @click="chooseReaction"> - <i class="ti ti-plus"></i> - </button> - </template> - </Sortable> - </div> - <div :class="$style.editorCaption">{{ i18n.ts.reactionSettingDescription2 }}</div> - </div> - - <div class="_buttons"> - <MkButton inline @click="previewReaction"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton> - <MkButton inline danger @click="setDefaultReaction"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton> - <MkButton inline danger @click="overwriteFromPinnedEmojis"><i class="ti ti-copy"></i> {{ i18n.ts.overwriteFromPinnedEmojis }}</MkButton> - </div> - </div> - </MkFolder> - - <MkFolder> - <template #icon><i class="ti ti-pin"></i></template> - <template #label>{{ i18n.ts.pinned }} ({{ i18n.ts.general }})</template> - <template #caption>{{ i18n.ts.pinnedEmojisSettingDescription }}</template> - - <div class="_gaps"> - <div> - <div v-panel style="border-radius: 6px;"> - <Sortable - v-model="pinnedEmojis" - :class="$style.emojis" - :itemKey="item => item" - :animation="150" - :delay="100" - :delayOnTouchOnly="true" - > - <template #item="{element}"> - <button class="_button" :class="$style.emojisItem" @click="removeEmoji(element, $event)"> - <MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true" :fallbackToImage="true"/> - <MkEmoji v-else :emoji="element" :normal="true"/> - </button> - </template> - <template #footer> - <button class="_button" :class="$style.emojisAdd" @click="chooseEmoji"> - <i class="ti ti-plus"></i> - </button> - </template> - </Sortable> - </div> - <div :class="$style.editorCaption">{{ i18n.ts.reactionSettingDescription2 }}</div> - </div> - - <div class="_buttons"> - <MkButton inline @click="previewEmoji"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton> - <MkButton inline danger @click="setDefaultEmoji"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton> - <MkButton inline danger @click="overwriteFromPinnedEmojisForReaction"><i class="ti ti-copy"></i> {{ i18n.ts.overwriteFromPinnedEmojisForReaction }}</MkButton> - </div> - </div> - </MkFolder> - - <FromSlot> - <template #label>{{ i18n.ts.defaultLike }}</template> - <MkCustomEmoji v-if="like && like.startsWith(':')" style="max-height: 3em; font-size: 1.1em;" :useOriginalSize="false" :name="like" :normal="true" :noStyle="true"/> - <MkEmoji v-else-if="like && !like.startsWith(':')" :emoji="like" style="max-height: 3em; font-size: 1.1em;" :normal="true" :noStyle="true"/> - <span v-else-if="!like">{{ i18n.ts.notSet }}</span> - <div class="_buttons" style="padding-top: 8px;"> - <MkButton rounded :small="true" inline @click="chooseNewLike"><i class="ti ti-plus"></i></MkButton> - <MkButton rounded :small="true" inline @click="resetLike"><i class="ti ti-refresh"></i></MkButton> - </div> - </FromSlot> - - <FormSection> - <template #label>{{ i18n.ts.emojiPickerDisplay }}</template> - - <div class="_gaps_m"> - <MkPreferenceContainer k="emojiPickerScale"> - <MkRadios v-model="emojiPickerScale"> - <template #label>{{ i18n.ts.size }}</template> - <option :value="1">{{ i18n.ts.small }}</option> - <option :value="2">{{ i18n.ts.medium }}</option> - <option :value="3">{{ i18n.ts.large }}</option> - </MkRadios> - </MkPreferenceContainer> - - <MkPreferenceContainer k="emojiPickerWidth"> - <MkRadios v-model="emojiPickerWidth"> - <template #label>{{ i18n.ts.numberOfColumn }}</template> - <option :value="1">5</option> - <option :value="2">6</option> - <option :value="3">7</option> - <option :value="4">8</option> - <option :value="5">9</option> - </MkRadios> - </MkPreferenceContainer> - - <MkPreferenceContainer k="emojiPickerHeight"> - <MkRadios v-model="emojiPickerHeight"> - <template #label>{{ i18n.ts.height }}</template> - <option :value="1">{{ i18n.ts.small }}</option> - <option :value="2">{{ i18n.ts.medium }}</option> - <option :value="3">{{ i18n.ts.large }}</option> - <option :value="4">{{ i18n.ts.large }}+</option> - </MkRadios> - </MkPreferenceContainer> - - <MkPreferenceContainer k="emojiPickerStyle"> - <MkSelect v-model="emojiPickerStyle"> - <template #label>{{ i18n.ts.style }}</template> - <template #caption>{{ i18n.ts.needReloadToApply }}</template> - <option value="auto">{{ i18n.ts.auto }}</option> - <option value="popup">{{ i18n.ts.popup }}</option> - <option value="drawer">{{ i18n.ts.drawer }}</option> - </MkSelect> - </MkPreferenceContainer> - </div> - </FormSection> -</div> -</template> - -<script lang="ts" setup> -import { computed, ref, watch } from 'vue'; -import Sortable from 'vuedraggable'; -import type { Ref } from 'vue'; -import MkRadios from '@/components/MkRadios.vue'; -import MkButton from '@/components/MkButton.vue'; -import FormSection from '@/components/form/section.vue'; -import FromSlot from '@/components/form/slot.vue'; -import MkSelect from '@/components/MkSelect.vue'; -import * as os from '@/os.js'; -import { store } from '@/store.js'; -import { i18n } from '@/i18n.js'; -import { definePage } from '@/page.js'; -import { deepClone } from '@/utility/clone.js'; -import { reactionPicker } from '@/utility/reaction-picker.js'; -import { emojiPicker } from '@/utility/emoji-picker.js'; -import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; -import MkEmoji from '@/components/global/MkEmoji.vue'; -import MkFolder from '@/components/MkFolder.vue'; -import { prefer } from '@/preferences.js'; -import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; - -const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(store.s.reactions)); -const pinnedEmojis: Ref<string[]> = ref(deepClone(store.s.pinnedEmojis)); - -const emojiPickerScale = prefer.model('emojiPickerScale'); -const emojiPickerWidth = prefer.model('emojiPickerWidth'); -const emojiPickerHeight = prefer.model('emojiPickerHeight'); -const emojiPickerStyle = prefer.model('emojiPickerStyle'); - -const removeReaction = (reaction: string, ev: MouseEvent) => remove(pinnedEmojisForReaction, reaction, ev); -const chooseReaction = (ev: MouseEvent) => pickEmoji(pinnedEmojisForReaction, ev); -const setDefaultReaction = () => setDefault(pinnedEmojisForReaction); - -const like = computed(store.makeGetterSetter('like')); - -const removeEmoji = (reaction: string, ev: MouseEvent) => remove(pinnedEmojis, reaction, ev); -const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev); -const setDefaultEmoji = () => setDefault(pinnedEmojis); - -function previewReaction(ev: MouseEvent) { - reactionPicker.show(getHTMLElement(ev), null); -} - -function previewEmoji(ev: MouseEvent) { - emojiPicker.show(getHTMLElement(ev)); -} - -async function overwriteFromPinnedEmojis() { - const { canceled } = await os.confirm({ - type: 'warning', - text: i18n.ts.overwriteContentConfirm, - }); - - if (canceled) { - return; - } - - pinnedEmojisForReaction.value = [...pinnedEmojis.value]; -} - -async function overwriteFromPinnedEmojisForReaction() { - const { canceled } = await os.confirm({ - type: 'warning', - text: i18n.ts.overwriteContentConfirm, - }); - - if (canceled) { - return; - } - - pinnedEmojis.value = [...pinnedEmojisForReaction.value]; -} - -function remove(itemsRef: Ref<string[]>, reaction: string, ev: MouseEvent) { - os.popupMenu([{ - text: i18n.ts.remove, - action: () => { - itemsRef.value = itemsRef.value.filter(x => x !== reaction); - }, - }], getHTMLElement(ev)); -} - -async function setDefault(itemsRef: Ref<string[]>) { - const { canceled } = await os.confirm({ - type: 'warning', - text: i18n.ts.resetAreYouSure, - }); - if (canceled) return; - - itemsRef.value = deepClone(store.def.reactions.default); -} - -async function pickEmoji(itemsRef: Ref<string[]>, ev: MouseEvent) { - os.pickEmoji(getHTMLElement(ev), { - showPinned: false, - }).then(it => { - const emoji = it; - if (!itemsRef.value.includes(emoji)) { - itemsRef.value.push(emoji); - } - }); -} - -function chooseNewLike(ev: MouseEvent) { - os.pickEmoji(getHTMLElement(ev), { - showPinned: false, - }).then(async emoji => { - store.set('like', emoji as string); - }); -} - -async function resetLike() { - store.set('like', null); -} - -function getHTMLElement(ev: MouseEvent): HTMLElement { - const target = ev.currentTarget ?? ev.target; - return target as HTMLElement; -} - -watch(pinnedEmojisForReaction, () => { - store.set('reactions', pinnedEmojisForReaction.value); -}, { - deep: true, -}); - -watch(pinnedEmojis, () => { - store.set('pinnedEmojis', pinnedEmojis.value); -}, { - deep: true, -}); - -definePage(() => ({ - title: i18n.ts.emojiPicker, - icon: 'ti ti-mood-happy', -})); -</script> - -<style lang="scss" module> -.tab { - margin: calc(var(--MI-margin) / 2) 0; - padding: calc(var(--MI-margin) / 2) 0; - background: var(--MI_THEME-bg); -} - -.emojis { - padding: 12px; - font-size: 1.1em; -} - -.emojisItem { - display: inline-block; - padding: 8px; - cursor: move; -} - -.emojisAdd { - display: inline-block; - padding: 8px; -} - -.editorCaption { - font-size: 0.85em; - padding: 8px 0 0 0; - color: var(--MI_THEME-fgTransparentWeak); -} -</style> diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index 0f732d7f70..961e02caf4 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div ref="el" class="vvcocwet" :class="{ wide: !narrow }"> <div class="body"> <div v-if="!narrow || currentPage?.route.name == null" class="nav"> - <div class="baaadecd"> + <div class="_gaps_s"> <MkInfo v-if="emailNotConfigured" warn class="info">{{ i18n.ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo> <MkInfo v-if="!store.r.enablePreferencesAutoCloudBackup.value && store.r.showPreferencesAutoCloudBackupSuggestion.value" class="info"> <div>{{ i18n.ts._preferencesBackup.autoPreferencesBackupIsNotEnabledForThisDevice }}</div> @@ -38,7 +38,7 @@ import type { SuperMenuDef } from '@/components/MkSuperMenu.vue'; import { i18n } from '@/i18n.js'; import MkInfo from '@/components/MkInfo.vue'; import MkSuperMenu from '@/components/MkSuperMenu.vue'; -import { signout, $i } from '@/account.js'; +import { $i } from '@/i.js'; import { clearCache } from '@/utility/clear-cache.js'; import { instance } from '@/instance.js'; import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js'; @@ -47,6 +47,7 @@ import { useRouter } from '@/router/supplier.js'; import { searchIndexes } from '@/utility/autogen/settings-search-index.js'; import { enableAutoBackup, getPreferencesProfileMenu } from '@/preferences/utility.js'; import { store } from '@/store.js'; +import { signout } from '@/signout.js'; const SETTING_INDEX = searchIndexes; // TODO: lazy load @@ -86,11 +87,6 @@ const menuDef = computed<SuperMenuDef[]>(() => [{ text: i18n.ts.privacy, to: '/settings/privacy', active: currentPage.value?.route.name === 'privacy', - }, { - icon: 'ti ti-mood-happy', - text: i18n.ts.emojiPicker, - to: '/settings/emoji-picker', - active: currentPage.value?.route.name === 'emojiPicker', }, { icon: 'ti ti-bell', text: i18n.ts.notifications, @@ -124,10 +120,10 @@ const menuDef = computed<SuperMenuDef[]>(() => [{ to: '/settings/pari', active: currentPage.value?.route.name === 'pari', }, { - icon: 'ti ti-device-desktop', - text: i18n.ts.appearance, - to: '/settings/appearance', - active: currentPage.value?.route.name === 'appearance', + icon: 'ti ti-mood-happy', + text: i18n.ts.emojiPalette, + to: '/settings/emoji-palette', + active: currentPage.value?.route.name === 'emoji-palette', }, { icon: 'ti ti-music', text: i18n.ts.sounds, @@ -150,11 +146,6 @@ const menuDef = computed<SuperMenuDef[]>(() => [{ text: i18n.ts.drive, to: '/settings/drive', active: currentPage.value?.route.name === 'drive', - }, { - icon: 'ti ti-badges', - text: i18n.ts.roles, - to: '/settings/roles', - active: currentPage.value?.route.name === 'roles', }, { icon: 'ti ti-ban', text: i18n.ts.muteAndBlock, @@ -259,30 +250,6 @@ definePage(() => INFO.value); <style lang="scss" scoped> .vvcocwet { - > .body { - > .nav { - .baaadecd { - > .info { - margin: 16px 0; - } - - > .accounts { - > .avatar { - display: block; - width: 50px; - height: 50px; - margin: 8px auto 16px auto; - } - } - } - } - - > .main { - .bkzroven { - } - } - } - &.wide { > .body { display: flex; diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue index 60386da545..260e390b51 100644 --- a/packages/frontend/src/pages/settings/migration.vue +++ b/packages/frontend/src/pages/settings/migration.vue @@ -68,7 +68,7 @@ import MkUserInfo from '@/components/MkUserInfo.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { unisonReload } from '@/utility/unison-reload.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/settings/mute-block.instance-mute.vue b/packages/frontend/src/pages/settings/mute-block.instance-mute.vue index 52e1937663..1c942e715a 100644 --- a/packages/frontend/src/pages/settings/mute-block.instance-mute.vue +++ b/packages/frontend/src/pages/settings/mute-block.instance-mute.vue @@ -19,7 +19,7 @@ import { ref, watch } from 'vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkButton from '@/components/MkButton.vue'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index a5ab7caf99..ce762af071 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -188,7 +188,7 @@ import { definePage } from '@/page.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; import * as os from '@/os.js'; import { instance, infoImageUrl } from '@/instance.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import MkInfo from '@/components/MkInfo.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue index 49910cdf4a..785fcdfbce 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -75,7 +75,7 @@ import FormSection from '@/components/form/section.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import * as os from '@/os.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index 835739a6c6..27fb743cb2 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -16,92 +16,102 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="reportError">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></MkSwitch> --> - <FormSection first> - <div class="_gaps_s"> - <SearchMarker :keywords="['account', 'info']"> - <MkFolder> - <template #icon><i class="ti ti-info-circle"></i></template> - <template #label><SearchLabel>{{ i18n.ts.accountInfo }}</SearchLabel></template> + <div class="_gaps_s"> + <SearchMarker :keywords="['account', 'info']"> + <MkFolder> + <template #icon><i class="ti ti-info-circle"></i></template> + <template #label><SearchLabel>{{ i18n.ts.accountInfo }}</SearchLabel></template> - <div class="_gaps_m"> - <MkKeyValue> - <template #key>ID</template> - <template #value><span class="_monospace">{{ $i.id }}</span></template> - </MkKeyValue> + <div class="_gaps_m"> + <MkKeyValue> + <template #key>ID</template> + <template #value><span class="_monospace">{{ $i.id }}</span></template> + </MkKeyValue> - <MkKeyValue> - <template #key>{{ i18n.ts.registeredDate }}</template> - <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> - </MkKeyValue> - </div> - </MkFolder> - </SearchMarker> + <MkKeyValue> + <template #key>{{ i18n.ts.registeredDate }}</template> + <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> + </MkKeyValue> - <SearchMarker :keywords="['account', 'move', 'migration']"> - <MkFolder> - <template #icon><i class="ti ti-plane"></i></template> - <template #label><SearchLabel>{{ i18n.ts.accountMigration }}</SearchLabel></template> + <MkFolder> + <template #icon><i class="ti ti-badges"></i></template> + <template #label><SearchLabel>{{ i18n.ts._role.policies }}</SearchLabel></template> - <XMigration/> - </MkFolder> - </SearchMarker> + <div class="_gaps_s"> + <div v-for="policy in Object.keys($i.policies)" :key="policy"> + {{ policy }} ... {{ $i.policies[policy] }} + </div> + </div> + </MkFolder> + </div> + </MkFolder> + </SearchMarker> - <SearchMarker :keywords="['account', 'close', 'delete']"> - <MkFolder> - <template #icon><i class="ti ti-alert-triangle"></i></template> - <template #label><SearchLabel>{{ i18n.ts.closeAccount }}</SearchLabel></template> + <SearchMarker :keywords="['roles']"> + <MkFolder> + <template #icon><i class="ti ti-badges"></i></template> + <template #label><SearchLabel>{{ i18n.ts.rolesAssignedToMe }}</SearchLabel></template> - <div class="_gaps_m"> - <FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo> - <FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo> - <MkButton v-if="!$i.isDeleted" danger @click="deleteAccount"><SearchKeyword>{{ i18n.ts._accountDelete.requestAccountDelete }}</SearchKeyword></MkButton> - <MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton> - </div> - </MkFolder> - </SearchMarker> + <MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/> + </MkFolder> + </SearchMarker> - <SearchMarker :keywords="['experimental', 'feature', 'flags']"> - <MkFolder> - <template #icon><i class="ti ti-flask"></i></template> - <template #label><SearchLabel>{{ i18n.ts.experimentalFeatures }}</SearchLabel></template> + <SearchMarker :keywords="['account', 'move', 'migration']"> + <MkFolder> + <template #icon><i class="ti ti-plane"></i></template> + <template #label><SearchLabel>{{ i18n.ts.accountMigration }}</SearchLabel></template> - <div class="_gaps_m"> - <MkSwitch v-model="enableCondensedLine"> - <template #label>Enable condensed line</template> - </MkSwitch> - <MkSwitch v-model="skipNoteRender"> - <template #label>Enable note render skipping</template> - </MkSwitch> - </div> - </MkFolder> - </SearchMarker> + <XMigration/> + </MkFolder> + </SearchMarker> - <SearchMarker :keywords="['developer', 'mode', 'debug']"> - <MkFolder> - <template #icon><i class="ti ti-code"></i></template> - <template #label><SearchLabel>{{ i18n.ts.developer }}</SearchLabel></template> + <SearchMarker :keywords="['account', 'close', 'delete']"> + <MkFolder> + <template #icon><i class="ti ti-alert-triangle"></i></template> + <template #label><SearchLabel>{{ i18n.ts.closeAccount }}</SearchLabel></template> - <div class="_gaps_m"> - <MkSwitch v-model="devMode"> - <template #label>{{ i18n.ts.devMode }}</template> - </MkSwitch> - </div> - </MkFolder> - </SearchMarker> - </div> - </FormSection> + <div class="_gaps_m"> + <FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo> + <FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo> + <MkButton v-if="!$i.isDeleted" danger @click="deleteAccount"><SearchKeyword>{{ i18n.ts._accountDelete.requestAccountDelete }}</SearchKeyword></MkButton> + <MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton> + </div> + </MkFolder> + </SearchMarker> - <FormSection> - <FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink> - </FormSection> + <SearchMarker :keywords="['experimental', 'feature', 'flags']"> + <MkFolder> + <template #icon><i class="ti ti-flask"></i></template> + <template #label><SearchLabel>{{ i18n.ts.experimentalFeatures }}</SearchLabel></template> - <FormSection> - <div class="_gaps_s"> - <MkSwitch v-model="defaultWithReplies">{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</MkSwitch> - <MkButton danger @click="updateRepliesAll(true)"><i class="ti ti-messages"></i> {{ i18n.ts.showRepliesToOthersInTimelineAll }}</MkButton> - <MkButton danger @click="updateRepliesAll(false)"><i class="ti ti-messages-off"></i> {{ i18n.ts.hideRepliesToOthersInTimelineAll }}</MkButton> - </div> - </FormSection> + <div class="_gaps_m"> + <MkSwitch v-model="enableCondensedLine"> + <template #label>Enable condensed line</template> + </MkSwitch> + <MkSwitch v-model="skipNoteRender"> + <template #label>Enable note render skipping</template> + </MkSwitch> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['developer', 'mode', 'debug']"> + <MkFolder> + <template #icon><i class="ti ti-code"></i></template> + <template #label><SearchLabel>{{ i18n.ts.developer }}</SearchLabel></template> + + <div class="_gaps_m"> + <MkSwitch v-model="devMode"> + <template #label>{{ i18n.ts.devMode }}</template> + </MkSwitch> + </div> + </MkFolder> + </SearchMarker> + </div> + + <hr> + + <FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink> </div> </SearchMarker> </template> @@ -117,13 +127,14 @@ import MkKeyValue from '@/components/MkKeyValue.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { store } from '@/store.js'; -import { signout, signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { reloadAsk } from '@/utility/reload-ask.js'; import FormSection from '@/components/form/section.vue'; import { prefer } from '@/preferences.js'; +import MkRolePreview from '@/components/MkRolePreview.vue'; +import { signout } from '@/signout.js'; const $i = signinRequired(); @@ -131,7 +142,6 @@ const reportError = prefer.model('reportError'); const enableCondensedLine = prefer.model('enableCondensedLine'); const skipNoteRender = prefer.model('skipNoteRender'); const devMode = prefer.model('devMode'); -const defaultWithReplies = computed(store.makeGetterSetter('defaultWithReplies')); watch(skipNoteRender, async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); @@ -161,16 +171,6 @@ async function deleteAccount() { await signout(); } -async function updateRepliesAll(withReplies: boolean) { - const { canceled } = await os.confirm({ - type: 'warning', - text: withReplies ? i18n.ts.confirmShowRepliesAll : i18n.ts.confirmHideRepliesAll, - }); - if (canceled) return; - - misskeyApi('following/update-all', { withReplies }); -} - const headerActions = computed(() => []); const headerTabs = computed(() => []); diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 1bc5a762bc..b0f5b588ff 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -10,112 +10,133 @@ SPDX-License-Identifier: AGPL-3.0-only <SearchKeyword>{{ i18n.ts._settings.preferencesBanner }}</SearchKeyword> </MkFeatureBanner> - <SearchMarker :keywords="['language']"> - <MkSelect v-model="lang"> - <template #label><SearchLabel>{{ i18n.ts.uiLanguage }}</SearchLabel></template> - <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> - <template #caption> - <I18n :src="i18n.ts.i18nInfo" tag="span"> - <template #link> - <MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink> - </template> - </I18n> - </template> - </MkSelect> - </SearchMarker> + <div class="_gaps_s"> + <SearchMarker :keywords="['general']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.general }}</SearchLabel></template> - <SearchMarker :keywords="['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop']"> - <MkRadios v-model="overridedDeviceKind"> - <template #label><SearchLabel>{{ i18n.ts.overridedDeviceKind }}</SearchLabel></template> - <option :value="null">{{ i18n.ts.auto }}</option> - <option value="smartphone"><i class="ti ti-device-mobile"/> {{ i18n.ts.smartphone }}</option> - <option value="tablet"><i class="ti ti-device-tablet"/> {{ i18n.ts.tablet }}</option> - <option value="desktop"><i class="ti ti-device-desktop"/> {{ i18n.ts.desktop }}</option> - </MkRadios> - </SearchMarker> + <div class="_gaps_m"> + <SearchMarker :keywords="['language']"> + <MkSelect v-model="lang"> + <template #label><SearchLabel>{{ i18n.ts.uiLanguage }}</SearchLabel></template> + <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> + <template #caption> + <I18n :src="i18n.ts.i18nInfo" tag="span"> + <template #link> + <MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink> + </template> + </I18n> + </template> + </MkSelect> + </SearchMarker> - <FormSection> - <div class="_gaps_s"> - <SearchMarker :keywords="['post', 'form', 'timeline']"> - <MkPreferenceContainer k="showFixedPostForm"> - <MkSwitch v-model="showFixedPostForm"> - <template #label><SearchLabel>{{ i18n.ts.showFixedPostForm }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> + <SearchMarker :keywords="['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop']"> + <MkRadios v-model="overridedDeviceKind"> + <template #label><SearchLabel>{{ i18n.ts.overridedDeviceKind }}</SearchLabel></template> + <option :value="null">{{ i18n.ts.auto }}</option> + <option value="smartphone"><i class="ti ti-device-mobile"/> {{ i18n.ts.smartphone }}</option> + <option value="tablet"><i class="ti ti-device-tablet"/> {{ i18n.ts.tablet }}</option> + <option value="desktop"><i class="ti ti-device-desktop"/> {{ i18n.ts.desktop }}</option> + </MkRadios> + </SearchMarker> - <SearchMarker :keywords="['post', 'form', 'timeline', 'channel']"> - <MkPreferenceContainer k="showFixedPostFormInChannel"> - <MkSwitch v-model="showFixedPostFormInChannel"> - <template #label><SearchLabel>{{ i18n.ts.showFixedPostFormInChannel }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['pinned', 'list']"> - <MkFolder> - <template #label><SearchLabel>{{ i18n.ts.pinnedList }}</SearchLabel></template> - <!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ --> - <MkButton v-if="prefer.r.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton> - <MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton> - </MkFolder> - </SearchMarker> - - <SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn']"> - <MkPreferenceContainer k="enableQuickAddMfmFunction"> - <MkSwitch v-model="enableQuickAddMfmFunction"> - <template #label><SearchLabel>{{ i18n.ts.enableQuickAddMfmFunction }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - </div> - </FormSection> - - <FormSection> - <div class="_gaps_m"> - <SearchMarker :keywords="['remember', 'keep', 'note', 'visibility']"> - <MkPreferenceContainer k="rememberNoteVisibility"> - <MkSwitch v-model="rememberNoteVisibility"> - <template #label><SearchLabel>{{ i18n.ts.rememberNoteVisibility }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['default', 'note', 'visibility']"> - <MkDisableSection :disabled="rememberNoteVisibility"> - <MkFolder> - <template #label><SearchLabel>{{ i18n.ts.defaultNoteVisibility }}</SearchLabel></template> - <template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template> - <template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template> - <template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template> - <template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template> - - <div class="_gaps_m"> - <MkPreferenceContainer k="defaultNoteVisibility"> - <MkSelect v-model="defaultNoteVisibility"> - <option value="public">{{ i18n.ts._visibility.public }}</option> - <option value="home">{{ i18n.ts._visibility.home }}</option> - <option value="followers">{{ i18n.ts._visibility.followers }}</option> - <option value="specified">{{ i18n.ts._visibility.specified }}</option> - </MkSelect> + <div class="_gaps_s"> + <SearchMarker :keywords="['blur']"> + <MkPreferenceContainer k="useBlurEffect"> + <MkSwitch v-model="useBlurEffect"> + <template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template> + </MkSwitch> </MkPreferenceContainer> + </SearchMarker> - <MkPreferenceContainer k="defaultNoteLocalOnly"> - <MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch> + <SearchMarker :keywords="['blur', 'modal']"> + <MkPreferenceContainer k="useBlurEffectForModal"> + <MkSwitch v-model="useBlurEffectForModal"> + <template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template> + </MkSwitch> </MkPreferenceContainer> - </div> - </MkFolder> - </MkDisableSection> - </SearchMarker> - </div> - </FormSection> + </SearchMarker> - <SearchMarker :keywords="['note']"> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.note }}</SearchLabel></template> + <SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']"> + <MkPreferenceContainer k="showAvatarDecorations"> + <MkSwitch v-model="showAvatarDecorations"> + <template #label><SearchLabel>{{ i18n.ts.showAvatarDecorations }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['follow', 'confirm', 'always']"> + <MkPreferenceContainer k="alwaysConfirmFollow"> + <MkSwitch v-model="alwaysConfirmFollow"> + <template #label><SearchLabel>{{ i18n.ts.alwaysConfirmFollow }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail']"> + <MkPreferenceContainer k="highlightSensitiveMedia"> + <MkSwitch v-model="highlightSensitiveMedia"> + <template #label><SearchLabel>{{ i18n.ts.highlightSensitiveMedia }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm']"> + <MkPreferenceContainer k="confirmWhenRevealingSensitiveMedia"> + <MkSwitch v-model="confirmWhenRevealingSensitiveMedia"> + <template #label><SearchLabel>{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + </div> + + <SearchMarker :keywords="['emoji', 'style', 'native', 'system', 'fluent', 'twemoji']"> + <MkPreferenceContainer k="emojiStyle"> + <div> + <MkRadios v-model="emojiStyle"> + <template #label><SearchLabel>{{ i18n.ts.emojiStyle }}</SearchLabel></template> + <option value="native">{{ i18n.ts.native }}</option> + <option value="fluentEmoji">Fluent Emoji</option> + <option value="twemoji">Twemoji</option> + </MkRadios> + <div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div> + </div> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['pinned', 'list']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.pinnedList }}</SearchLabel></template> + <!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ --> + <MkButton v-if="prefer.r.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton> + <MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton> + </MkFolder> + </SearchMarker> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['timeline']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.timeline }}</SearchLabel></template> - <div class="_gaps_m"> <div class="_gaps_s"> + <SearchMarker :keywords="['post', 'form', 'timeline']"> + <MkPreferenceContainer k="showFixedPostForm"> + <MkSwitch v-model="showFixedPostForm"> + <template #label><SearchLabel>{{ i18n.ts.showFixedPostForm }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['post', 'form', 'timeline', 'channel']"> + <MkPreferenceContainer k="showFixedPostFormInChannel"> + <MkSwitch v-model="showFixedPostFormInChannel"> + <template #label><SearchLabel>{{ i18n.ts.showFixedPostFormInChannel }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + <SearchMarker :keywords="['renote']"> <MkPreferenceContainer k="collapseRenotes"> <MkSwitch v-model="collapseRenotes"> @@ -125,84 +146,10 @@ SPDX-License-Identifier: AGPL-3.0-only </MkPreferenceContainer> </SearchMarker> - <SearchMarker :keywords="['hover', 'show', 'footer', 'action']"> - <MkPreferenceContainer k="showNoteActionsOnlyHover"> - <MkSwitch v-model="showNoteActionsOnlyHover"> - <template #label><SearchLabel>{{ i18n.ts.showNoteActionsOnlyHover }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['footer', 'action', 'clip', 'show']"> - <MkPreferenceContainer k="showClipButtonInNoteFooter"> - <MkSwitch v-model="showClipButtonInNoteFooter"> - <template #label><SearchLabel>{{ i18n.ts.showClipButtonInNoteFooter }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced']"> - <MkPreferenceContainer k="advancedMfm"> - <MkSwitch v-model="advancedMfm"> - <template #label><SearchLabel>{{ i18n.ts.enableAdvancedMfm }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['reaction', 'count', 'show']"> - <MkPreferenceContainer k="showReactionsCount"> - <MkSwitch v-model="showReactionsCount"> - <template #label><SearchLabel>{{ i18n.ts.showReactionsCount }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment']"> - <MkPreferenceContainer k="loadRawImages"> - <MkSwitch v-model="loadRawImages"> - <template #label><SearchLabel>{{ i18n.ts.loadRawImages }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - </div> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['notification']"> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.notifications }}</SearchLabel></template> - - <div class="_gaps_m"> - <SearchMarker :keywords="['group']"> - <MkPreferenceContainer k="useGroupedNotifications"> - <MkSwitch v-model="useGroupedNotifications"> - <template #label><SearchLabel>{{ i18n.ts.useGroupedNotifications }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['behavior']"> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.behavior }}</SearchLabel></template> - - <div class="_gaps_m"> - <div class="_gaps_s"> - <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab']"> - <MkPreferenceContainer k="imageNewTab"> - <MkSwitch v-model="imageNewTab"> - <template #label><SearchLabel>{{ i18n.ts.openImageInNewTab }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['reaction', 'picker', 'contextmenu', 'open']"> - <MkPreferenceContainer k="useReactionPickerForContextMenu"> - <MkSwitch v-model="useReactionPickerForContextMenu"> - <template #label><SearchLabel>{{ i18n.ts.useReactionPickerForContextMenu }}</SearchLabel></template> + <SearchMarker :keywords="['note', 'timeline', 'gap']"> + <MkPreferenceContainer k="showGapBetweenNotesInTimeline"> + <MkSwitch v-model="showGapBetweenNotesInTimeline"> + <template #label><SearchLabel>{{ i18n.ts.showGapBetweenNotesInTimeline }}</SearchLabel></template> </MkSwitch> </MkPreferenceContainer> </SearchMarker> @@ -222,144 +169,366 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> </MkPreferenceContainer> </SearchMarker> + </div> + </MkFolder> + </SearchMarker> - <SearchMarker :keywords="['follow', 'confirm', 'always']"> - <MkPreferenceContainer k="alwaysConfirmFollow"> - <MkSwitch v-model="alwaysConfirmFollow"> - <template #label><SearchLabel>{{ i18n.ts.alwaysConfirmFollow }}</SearchLabel></template> + <SearchMarker :keywords="['note']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.note }}</SearchLabel></template> + + <div class="_gaps_m"> + <div class="_gaps_s"> + <SearchMarker :keywords="['hover', 'show', 'footer', 'action']"> + <MkPreferenceContainer k="showNoteActionsOnlyHover"> + <MkSwitch v-model="showNoteActionsOnlyHover"> + <template #label><SearchLabel>{{ i18n.ts.showNoteActionsOnlyHover }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['footer', 'action', 'clip', 'show']"> + <MkPreferenceContainer k="showClipButtonInNoteFooter"> + <MkSwitch v-model="showClipButtonInNoteFooter"> + <template #label><SearchLabel>{{ i18n.ts.showClipButtonInNoteFooter }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced']"> + <MkPreferenceContainer k="advancedMfm"> + <MkSwitch v-model="advancedMfm"> + <template #label><SearchLabel>{{ i18n.ts.enableAdvancedMfm }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['reaction', 'count', 'show']"> + <MkPreferenceContainer k="showReactionsCount"> + <MkSwitch v-model="showReactionsCount"> + <template #label><SearchLabel>{{ i18n.ts.showReactionsCount }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['reaction', 'confirm']"> + <MkPreferenceContainer k="confirmOnReact"> + <MkSwitch v-model="confirmOnReact"> + <template #label><SearchLabel>{{ i18n.ts.confirmOnReact }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment']"> + <MkPreferenceContainer k="loadRawImages"> + <MkSwitch v-model="loadRawImages"> + <template #label><SearchLabel>{{ i18n.ts.loadRawImages }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['reaction', 'picker', 'contextmenu', 'open']"> + <MkPreferenceContainer k="useReactionPickerForContextMenu"> + <MkSwitch v-model="useReactionPickerForContextMenu"> + <template #label><SearchLabel>{{ i18n.ts.useReactionPickerForContextMenu }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + </div> + + <SearchMarker :keywords="['reaction', 'size', 'scale', 'display']"> + <MkPreferenceContainer k="reactionsDisplaySize"> + <MkRadios v-model="reactionsDisplaySize"> + <template #label><SearchLabel>{{ i18n.ts.reactionsDisplaySize }}</SearchLabel></template> + <option value="small">{{ i18n.ts.small }}</option> + <option value="medium">{{ i18n.ts.medium }}</option> + <option value="large">{{ i18n.ts.large }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['reaction', 'size', 'scale', 'display', 'width', 'limit']"> + <MkPreferenceContainer k="limitWidthOfReaction"> + <MkSwitch v-model="limitWidthOfReaction"> + <template #label><SearchLabel>{{ i18n.ts.limitWidthOfReaction }}</SearchLabel></template> </MkSwitch> </MkPreferenceContainer> </SearchMarker> - <SearchMarker :keywords="['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm']"> - <MkPreferenceContainer k="confirmWhenRevealingSensitiveMedia"> - <MkSwitch v-model="confirmWhenRevealingSensitiveMedia"> - <template #label><SearchLabel>{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</SearchLabel></template> - </MkSwitch> + <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height']"> + <MkPreferenceContainer k="mediaListWithOneImageAppearance"> + <MkRadios v-model="mediaListWithOneImageAppearance"> + <template #label><SearchLabel>{{ i18n.ts.mediaListWithOneImageAppearance }}</SearchLabel></template> + <option value="expand">{{ i18n.ts.default }}</option> + <option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option> + <option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option> + <option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option> + </MkRadios> </MkPreferenceContainer> </SearchMarker> - <SearchMarker :keywords="['reaction', 'confirm']"> - <MkPreferenceContainer k="confirmOnReact"> - <MkSwitch v-model="confirmOnReact"> - <template #label><SearchLabel>{{ i18n.ts.confirmOnReact }}</SearchLabel></template> - </MkSwitch> + <SearchMarker :keywords="['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation']"> + <MkPreferenceContainer k="instanceTicker"> + <MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker"> + <template #label><SearchLabel>{{ i18n.ts.instanceTicker }}</SearchLabel></template> + <option value="none">{{ i18n.ts._instanceTicker.none }}</option> + <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> + <option value="always">{{ i18n.ts._instanceTicker.always }}</option> + </MkSelect> </MkPreferenceContainer> </SearchMarker> - <SearchMarker :keywords="['remember', 'keep', 'note', 'cw']"> - <MkPreferenceContainer k="keepCw"> - <MkSwitch v-model="keepCw"> - <template #label><SearchLabel>{{ i18n.ts.keepCw }}</SearchLabel></template> - </MkSwitch> + <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility']"> + <MkPreferenceContainer k="nsfw"> + <MkSelect v-model="nsfw"> + <template #label><SearchLabel>{{ i18n.ts.displayOfSensitiveMedia }}</SearchLabel></template> + <option value="respect">{{ i18n.ts._displayOfSensitiveMedia.respect }}</option> + <option value="ignore">{{ i18n.ts._displayOfSensitiveMedia.ignore }}</option> + <option value="force">{{ i18n.ts._displayOfSensitiveMedia.force }}</option> + </MkSelect> </MkPreferenceContainer> </SearchMarker> </div> + </MkFolder> + </SearchMarker> - <SearchMarker :keywords="['server', 'disconnect', 'reconnect', 'reload', 'streaming']"> - <MkPreferenceContainer k="serverDisconnectedBehavior"> - <MkSelect v-model="serverDisconnectedBehavior"> - <template #label><SearchLabel>{{ i18n.ts.whenServerDisconnected }}</SearchLabel></template> - <option value="doNothing">{{ i18n.ts.doNothing }}</option> - <option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option> - <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option> - <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> - </MkSelect> - </MkPreferenceContainer> - </SearchMarker> + <SearchMarker :keywords="['post', 'form']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.postForm }}</SearchLabel></template> - <SearchMarker :keywords="['cache', 'page']"> - <MkPreferenceContainer k="numberOfPageCache"> - <MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing> - <template #label><SearchLabel>{{ i18n.ts.numberOfPageCache }}</SearchLabel></template> - <template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template> - </MkRange> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :label="i18n.ts.dataSaver" :keywords="['datasaver']"> - <MkFolder> - <template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template> - - <div class="_gaps_m"> - <MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo> - - <div class="_buttons"> - <MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton> - <MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton> - </div> - <div class="_gaps_m"> - <MkSwitch v-model="dataSaver.media"> - {{ i18n.ts._dataSaver._media.title }} - <template #caption>{{ i18n.ts._dataSaver._media.description }}</template> + <div class="_gaps_m"> + <div class="_gaps_s"> + <SearchMarker :keywords="['remember', 'keep', 'note', 'cw']"> + <MkPreferenceContainer k="keepCw"> + <MkSwitch v-model="keepCw"> + <template #label><SearchLabel>{{ i18n.ts.keepCw }}</SearchLabel></template> </MkSwitch> - <MkSwitch v-model="dataSaver.avatar"> - {{ i18n.ts._dataSaver._avatar.title }} - <template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template> - </MkSwitch> - <MkSwitch v-model="dataSaver.urlPreview"> - {{ i18n.ts._dataSaver._urlPreview.title }} - <template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template> - </MkSwitch> - <MkSwitch v-model="dataSaver.code"> - {{ i18n.ts._dataSaver._code.title }} - <template #caption>{{ i18n.ts._dataSaver._code.description }}</template> - </MkSwitch> - </div> - </div> - </MkFolder> - </SearchMarker> - </div> - </FormSection> - </SearchMarker> + </MkPreferenceContainer> + </SearchMarker> - <SearchMarker> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template> + <SearchMarker :keywords="['remember', 'keep', 'note', 'visibility']"> + <MkPreferenceContainer k="rememberNoteVisibility"> + <MkSwitch v-model="rememberNoteVisibility"> + <template #label><SearchLabel>{{ i18n.ts.rememberNoteVisibility }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> - <div class="_gaps"> - <SearchMarker :keywords="['ad', 'show']"> - <MkPreferenceContainer k="forceShowAds"> - <MkSwitch v-model="forceShowAds"> - <template #label><SearchLabel>{{ i18n.ts.forceShowAds }}</SearchLabel></template> + <SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn']"> + <MkPreferenceContainer k="enableQuickAddMfmFunction"> + <MkSwitch v-model="enableQuickAddMfmFunction"> + <template #label><SearchLabel>{{ i18n.ts.enableQuickAddMfmFunction }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + </div> + + <SearchMarker :keywords="['default', 'note', 'visibility']"> + <MkDisableSection :disabled="rememberNoteVisibility"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.defaultNoteVisibility }}</SearchLabel></template> + <template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template> + <template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template> + <template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template> + <template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template> + + <div class="_gaps_m"> + <MkPreferenceContainer k="defaultNoteVisibility"> + <MkSelect v-model="defaultNoteVisibility"> + <option value="public">{{ i18n.ts._visibility.public }}</option> + <option value="home">{{ i18n.ts._visibility.home }}</option> + <option value="followers">{{ i18n.ts._visibility.followers }}</option> + <option value="specified">{{ i18n.ts._visibility.specified }}</option> + </MkSelect> + </MkPreferenceContainer> + + <MkPreferenceContainer k="defaultNoteLocalOnly"> + <MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch> + </MkPreferenceContainer> + </div> + </MkFolder> + </MkDisableSection> + </SearchMarker> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['notification']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.notifications }}</SearchLabel></template> + + <div class="_gaps_m"> + <SearchMarker :keywords="['group']"> + <MkPreferenceContainer k="useGroupedNotifications"> + <MkSwitch v-model="useGroupedNotifications"> + <template #label><SearchLabel>{{ i18n.ts.useGroupedNotifications }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['position']"> + <MkPreferenceContainer k="notificationPosition"> + <MkRadios v-model="notificationPosition"> + <template #label><SearchLabel>{{ i18n.ts.position }}</SearchLabel></template> + <option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option> + <option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option> + <option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option> + <option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['stack', 'axis', 'direction']"> + <MkPreferenceContainer k="notificationStackAxis"> + <MkRadios v-model="notificationStackAxis"> + <template #label><SearchLabel>{{ i18n.ts.stackAxis }}</SearchLabel></template> + <option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option> + <option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['datasaver']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template> + + <div class="_gaps_m"> + <MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo> + + <div class="_buttons"> + <MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton> + <MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton> + </div> + <div class="_gaps_m"> + <MkSwitch v-model="dataSaver.media"> + {{ i18n.ts._dataSaver._media.title }} + <template #caption>{{ i18n.ts._dataSaver._media.description }}</template> </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> + <MkSwitch v-model="dataSaver.avatar"> + {{ i18n.ts._dataSaver._avatar.title }} + <template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template> + </MkSwitch> + <MkSwitch v-model="dataSaver.urlPreview"> + {{ i18n.ts._dataSaver._urlPreview.title }} + <template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template> + </MkSwitch> + <MkSwitch v-model="dataSaver.code"> + {{ i18n.ts._dataSaver._code.title }} + <template #caption>{{ i18n.ts._dataSaver._code.description }}</template> + </MkSwitch> + </div> + </div> + </MkFolder> + </SearchMarker> - <SearchMarker> - <MkPreferenceContainer k="hemisphere"> - <MkRadios v-model="hemisphere"> - <template #label><SearchLabel>{{ i18n.ts.hemisphere }}</SearchLabel></template> - <option value="N">{{ i18n.ts._hemisphere.N }}</option> - <option value="S">{{ i18n.ts._hemisphere.S }}</option> - <template #caption>{{ i18n.ts._hemisphere.caption }}</template> - </MkRadios> - </MkPreferenceContainer> - </SearchMarker> + <SearchMarker :keywords="['other']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template> - <SearchMarker :keywords="['emoji', 'dictionary', 'additional', 'extra']"> - <MkFolder> - <template #label><SearchLabel>{{ i18n.ts.additionalEmojiDictionary }}</SearchLabel></template> - <div class="_buttons"> - <template v-for="lang in emojiIndexLangs" :key="lang"> - <MkButton v-if="store.r.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton> - <MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ store.r.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton> - </template> - </div> - </MkFolder> - </SearchMarker> + <div class="_gaps_m"> + <div class="_gaps_s"> + <SearchMarker :keywords="['avatar', 'icon', 'square']"> + <MkPreferenceContainer k="squareAvatars"> + <MkSwitch v-model="squareAvatars"> + <template #label><SearchLabel>{{ i18n.ts.squareAvatars }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> - <FormLink to="/settings/navbar">{{ i18n.ts.navbar }}</FormLink> - <FormLink to="/settings/statusbar">{{ i18n.ts.statusbar }}</FormLink> - </div> - </FormSection> - </SearchMarker> + <SearchMarker :keywords="['effect', 'show']"> + <MkPreferenceContainer k="enableSeasonalScreenEffect"> + <MkSwitch v-model="enableSeasonalScreenEffect"> + <template #label><SearchLabel>{{ i18n.ts.seasonalScreenEffect }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> - <FormSection> - <div class="_gaps"> - <FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink> - </div> - </FormSection> + <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab']"> + <MkPreferenceContainer k="imageNewTab"> + <MkSwitch v-model="imageNewTab"> + <template #label><SearchLabel>{{ i18n.ts.openImageInNewTab }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['follow', 'replies']"> + <MkPreferenceContainer k="defaultFollowWithReplies"> + <MkSwitch v-model="defaultFollowWithReplies"> + <template #label><SearchLabel>{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + </div> + + <SearchMarker :keywords="['server', 'disconnect', 'reconnect', 'reload', 'streaming']"> + <MkPreferenceContainer k="serverDisconnectedBehavior"> + <MkSelect v-model="serverDisconnectedBehavior"> + <template #label><SearchLabel>{{ i18n.ts.whenServerDisconnected }}</SearchLabel></template> + <option value="doNothing">{{ i18n.ts.doNothing }}</option> + <option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option> + <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option> + <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> + </MkSelect> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['cache', 'page']"> + <MkPreferenceContainer k="numberOfPageCache"> + <MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing> + <template #label><SearchLabel>{{ i18n.ts.numberOfPageCache }}</SearchLabel></template> + <template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template> + </MkRange> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['ad', 'show']"> + <MkPreferenceContainer k="forceShowAds"> + <MkSwitch v-model="forceShowAds"> + <template #label><SearchLabel>{{ i18n.ts.forceShowAds }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker> + <MkPreferenceContainer k="hemisphere"> + <MkRadios v-model="hemisphere"> + <template #label><SearchLabel>{{ i18n.ts.hemisphere }}</SearchLabel></template> + <option value="N">{{ i18n.ts._hemisphere.N }}</option> + <option value="S">{{ i18n.ts._hemisphere.S }}</option> + <template #caption>{{ i18n.ts._hemisphere.caption }}</template> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['emoji', 'dictionary', 'additional', 'extra']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.additionalEmojiDictionary }}</SearchLabel></template> + <div class="_buttons"> + <template v-for="lang in emojiIndexLangs" :key="lang"> + <MkButton v-if="store.r.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton> + <MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ store.r.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton> + </template> + </div> + </MkFolder> + </SearchMarker> + </div> + </MkFolder> + </SearchMarker> + </div> + + <hr> + + <div class="_gaps_s"> + <FormLink to="/settings/navbar"><template #icon><i class="ti ti-list"></i></template>{{ i18n.ts.navbar }}</FormLink> + <FormLink to="/settings/statusbar"><template #icon><i class="ti ti-list"></i></template>{{ i18n.ts.statusbar }}</FormLink> + <FormLink to="/settings/deck"><template #icon><i class="ti ti-columns"></i></template>{{ i18n.ts.deck }}</FormLink> + <FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink> + </div> </div> </SearchMarker> </template> @@ -367,6 +536,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, ref, watch } from 'vue'; import { langs } from '@@/js/config.js'; +import * as Misskey from 'misskey-js'; import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkRadios from '@/components/MkRadios.vue'; @@ -387,6 +557,9 @@ import { miLocalStorage } from '@/local-storage.js'; import { prefer } from '@/preferences.js'; import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; +import { globalEvents } from '@/events.js'; +import { claimAchievement } from '@/utility/achievements.js'; +import { instance } from '@/instance.js'; const lang = ref(miLocalStorage.getItem('lang')); const dataSaver = ref(prefer.s.dataSaver); @@ -414,10 +587,25 @@ const useGroupedNotifications = prefer.model('useGroupedNotifications'); const alwaysConfirmFollow = prefer.model('alwaysConfirmFollow'); const confirmWhenRevealingSensitiveMedia = prefer.model('confirmWhenRevealingSensitiveMedia'); const confirmOnReact = prefer.model('confirmOnReact'); -const contextMenu = prefer.model('contextMenu'); const defaultNoteVisibility = prefer.model('defaultNoteVisibility'); const defaultNoteLocalOnly = prefer.model('defaultNoteLocalOnly'); const rememberNoteVisibility = prefer.model('rememberNoteVisibility'); +const showGapBetweenNotesInTimeline = prefer.model('showGapBetweenNotesInTimeline'); +const notificationPosition = prefer.model('notificationPosition'); +const notificationStackAxis = prefer.model('notificationStackAxis'); +const instanceTicker = prefer.model('instanceTicker'); +const highlightSensitiveMedia = prefer.model('highlightSensitiveMedia'); +const mediaListWithOneImageAppearance = prefer.model('mediaListWithOneImageAppearance'); +const reactionsDisplaySize = prefer.model('reactionsDisplaySize'); +const limitWidthOfReaction = prefer.model('limitWidthOfReaction'); +const squareAvatars = prefer.model('squareAvatars'); +const enableSeasonalScreenEffect = prefer.model('enableSeasonalScreenEffect'); +const showAvatarDecorations = prefer.model('showAvatarDecorations'); +const nsfw = prefer.model('nsfw'); +const emojiStyle = prefer.model('emojiStyle'); +const useBlurEffectForModal = prefer.model('useBlurEffectForModal'); +const useBlurEffect = prefer.model('useBlurEffect'); +const defaultFollowWithReplies = prefer.model('defaultFollowWithReplies'); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); @@ -434,7 +622,17 @@ watch([ disableStreamingTimeline, alwaysConfirmFollow, confirmWhenRevealingSensitiveMedia, - contextMenu, + showGapBetweenNotesInTimeline, + mediaListWithOneImageAppearance, + reactionsDisplaySize, + limitWidthOfReaction, + mediaListWithOneImageAppearance, + reactionsDisplaySize, + limitWidthOfReaction, + instanceTicker, + squareAvatars, + highlightSensitiveMedia, + enableSeasonalScreenEffect, ], async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); @@ -523,6 +721,33 @@ watch(dataSaver, (to) => { deep: true, }); +let smashCount = 0; +let smashTimer: number | null = null; + +function testNotification(): void { + const notification: Misskey.entities.Notification = { + id: Math.random().toString(), + createdAt: new Date().toUTCString(), + isRead: false, + type: 'test', + }; + + globalEvents.emit('clientNotification', notification); + + // セルフ通知破壊 実績関連 + smashCount++; + if (smashCount >= 10) { + claimAchievement('smashTestNotificationButton'); + smashCount = 0; + } + if (smashTimer) { + clearTimeout(smashTimer); + } + smashTimer = window.setTimeout(() => { + smashCount = 0; + }, 300); +} + const headerActions = computed(() => []); const headerTabs = computed(() => []); diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue index edc750c295..b73f781b9c 100644 --- a/packages/frontend/src/pages/settings/privacy.vue +++ b/packages/frontend/src/pages/settings/privacy.vue @@ -185,7 +185,7 @@ import MkFolder from '@/components/MkFolder.vue'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { definePage } from '@/page.js'; import FormSlot from '@/components/form/slot.vue'; import { formatDateTimeString } from '@/utility/format-time-string.js'; diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index 1d85ba7834..b12ba9fe93 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -164,7 +164,7 @@ import FormSlot from '@/components/form/slot.vue'; import { selectFile } from '@/utility/select-file.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; -import { signinRequired } from '@/account.js'; +import { signinRequired } from '@/i.js'; import { langmap } from '@/utility/langmap.js'; import { definePage } from '@/page.js'; import { claimAchievement } from '@/utility/achievements.js'; diff --git a/packages/frontend/src/pages/settings/roles.vue b/packages/frontend/src/pages/settings/roles.vue deleted file mode 100644 index c1cabad2c3..0000000000 --- a/packages/frontend/src/pages/settings/roles.vue +++ /dev/null @@ -1,48 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<div class="_gaps_m"> - <FormSection first> - <template #label>{{ i18n.ts.rolesAssignedToMe }}</template> - <div class="_gaps_s"> - <MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/> - </div> - </FormSection> - <FormSection> - <template #label>{{ i18n.ts._role.policies }}</template> - <div class="_gaps_s"> - <div v-for="policy in Object.keys($i.policies)" :key="policy"> - {{ policy }} ... {{ $i.policies[policy] }} - </div> - </div> - </FormSection> -</div> -</template> - -<script lang="ts" setup> -import { computed } from 'vue'; -import FormSection from '@/components/form/section.vue'; -import * as os from '@/os.js'; -import { i18n } from '@/i18n.js'; -import { signinRequired } from '@/account.js'; -import { definePage } from '@/page.js'; -import MkRolePreview from '@/components/MkRolePreview.vue'; - -const $i = signinRequired(); - -const headerActions = computed(() => []); - -const headerTabs = computed(() => []); - -definePage(() => ({ - title: i18n.ts.roles, - icon: 'ti ti-badges', -})); -</script> - -<style lang="scss" module> - -</style> diff --git a/packages/frontend/src/pages/signup-complete.vue b/packages/frontend/src/pages/signup-complete.vue index 5fe4de3c91..919a64c397 100644 --- a/packages/frontend/src/pages/signup-complete.vue +++ b/packages/frontend/src/pages/signup-complete.vue @@ -28,10 +28,10 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref } from 'vue'; import MkButton from '@/components/MkButton.vue'; import MkAnimBg from '@/components/MkAnimBg.vue'; -import { login } from '@/account.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; +import { login } from '@/accounts.js'; const submitting = ref(false); diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue index 1af69d82db..868c64d06d 100644 --- a/packages/frontend/src/pages/tag.vue +++ b/packages/frontend/src/pages/tag.vue @@ -25,7 +25,7 @@ import MkNotes from '@/components/MkNotes.vue'; import MkButton from '@/components/MkButton.vue'; import { definePage } from '@/page.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { store } from '@/store.js'; import * as os from '@/os.js'; import { genEmbedCode } from '@/utility/get-embed-code.js'; diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue index 11971756f4..45d8c96ab7 100644 --- a/packages/frontend/src/pages/theme-editor.vue +++ b/packages/frontend/src/pages/theme-editor.vue @@ -86,13 +86,13 @@ import MkButton from '@/components/MkButton.vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkFolder from '@/components/MkFolder.vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { applyTheme } from '@/theme.js'; import * as os from '@/os.js'; import { store } from '@/store.js'; import { addTheme } from '@/theme-store.js'; import { i18n } from '@/i18n.js'; -import { useLeaveGuard } from '@/utility/use-leave-guard.js'; +import { useLeaveGuard } from '@/use/use-leave-guard.js'; import { definePage } from '@/page.js'; import { prefer } from '@/preferences.js'; diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 0cf5d8bbe9..4d725ca747 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -48,7 +48,7 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { definePage } from '@/page.js'; import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js'; import { deviceKind } from '@/utility/device-kind.js'; diff --git a/packages/frontend/src/pages/user/achievements.vue b/packages/frontend/src/pages/user/achievements.vue index b78ac2dc17..8f13e959e1 100644 --- a/packages/frontend/src/pages/user/achievements.vue +++ b/packages/frontend/src/pages/user/achievements.vue @@ -14,7 +14,7 @@ import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue'; import * as Misskey from 'misskey-js'; import MkAchievements from '@/components/MkAchievements.vue'; import { claimAchievement } from '@/utility/achievements.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; const props = defineProps<{ user: Misskey.entities.User; diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue index a86387571a..3b5e9617d5 100644 --- a/packages/frontend/src/pages/user/activity.following.vue +++ b/packages/frontend/src/pages/user/activity.following.vue @@ -21,7 +21,7 @@ import * as Misskey from 'misskey-js'; import gradient from 'chartjs-plugin-gradient'; import { misskeyApi } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { chartVLine } from '@/utility/chart-vline.js'; import { initChart } from '@/utility/init-chart.js'; import { chartLegend } from '@/utility/chart-legend.js'; diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue index d083fdebed..af8e4d43a6 100644 --- a/packages/frontend/src/pages/user/activity.notes.vue +++ b/packages/frontend/src/pages/user/activity.notes.vue @@ -21,7 +21,7 @@ import * as Misskey from 'misskey-js'; import gradient from 'chartjs-plugin-gradient'; import { misskeyApi } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { chartVLine } from '@/utility/chart-vline.js'; import { initChart } from '@/utility/init-chart.js'; import { chartLegend } from '@/utility/chart-legend.js'; diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue index d5e8f45608..32e4e78d21 100644 --- a/packages/frontend/src/pages/user/activity.pv.vue +++ b/packages/frontend/src/pages/user/activity.pv.vue @@ -21,7 +21,7 @@ import * as Misskey from 'misskey-js'; import gradient from 'chartjs-plugin-gradient'; import { misskeyApi } from '@/utility/misskey-api.js'; import { store } from '@/store.js'; -import { useChartTooltip } from '@/utility/use-chart-tooltip.js'; +import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { chartVLine } from '@/utility/chart-vline.js'; import { initChart } from '@/utility/init-chart.js'; import { chartLegend } from '@/utility/chart-legend.js'; diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 6450f1e077..149481f99b 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -176,7 +176,7 @@ import number from '@/filters/number.js'; import { userPage } from '@/filters/user.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; -import { $i, iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/i.js'; import { dateString } from '@/filters/date.js'; import { confetti } from '@/utility/confetti.js'; import { misskeyApi } from '@/utility/misskey-api.js'; diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue index b5127de390..16413a55cc 100644 --- a/packages/frontend/src/pages/user/index.vue +++ b/packages/frontend/src/pages/user/index.vue @@ -38,7 +38,7 @@ import { acct as getAcct } from '@/filters/user.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { definePage } from '@/page.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { serverContext, assertServerContext } from '@/server-context.js'; diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue index 939ca934e8..d9e3ca9966 100644 --- a/packages/frontend/src/pages/welcome.setup.vue +++ b/packages/frontend/src/pages/welcome.setup.vue @@ -45,9 +45,9 @@ import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { login } from '@/account.js'; import { i18n } from '@/i18n.js'; import MkAnimBg from '@/components/MkAnimBg.vue'; +import { login } from '@/accounts.js'; const username = ref(''); const password = ref(''); diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts index f55b1e93cf..3ebf2ab4e4 100644 --- a/packages/frontend/src/pizzax.ts +++ b/packages/frontend/src/pizzax.ts @@ -8,7 +8,7 @@ import { onUnmounted, ref, watch } from 'vue'; import { BroadcastChannel } from 'broadcast-channel'; import type { Ref } from 'vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { get, set } from '@/utility/idb-proxy.js'; import { store } from '@/store.js'; diff --git a/packages/frontend/src/preferences.ts b/packages/frontend/src/preferences.ts index 7d1821b72b..f9e6ab2a75 100644 --- a/packages/frontend/src/preferences.ts +++ b/packages/frontend/src/preferences.ts @@ -7,25 +7,25 @@ import { v4 as uuid } from 'uuid'; import type { PreferencesProfile, StorageProvider } from '@/preferences/manager.js'; import { cloudBackup } from '@/preferences/utility.js'; import { miLocalStorage } from '@/local-storage.js'; -import { isSameCond, ProfileManager } from '@/preferences/manager.js'; +import { isSameScope, PreferencesManager } from '@/preferences/manager.js'; import { store } from '@/store.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { misskeyApi } from '@/utility/misskey-api.js'; const TAB_ID = uuid(); -function createProfileManager(storageProvider: StorageProvider) { +function createPrefManager(storageProvider: StorageProvider) { let profile: PreferencesProfile; const savedProfileRaw = miLocalStorage.getItem('preferences'); if (savedProfileRaw == null) { - profile = ProfileManager.newProfile(); + profile = PreferencesManager.newProfile(); miLocalStorage.setItem('preferences', JSON.stringify(profile)); } else { - profile = ProfileManager.normalizeProfile(JSON.parse(savedProfileRaw)); + profile = PreferencesManager.normalizeProfile(JSON.parse(savedProfileRaw)); } - return new ProfileManager(profile, storageProvider); + return new PreferencesManager(profile, storageProvider); } const syncGroup = 'default'; @@ -44,7 +44,7 @@ const storageProvider: StorageProvider = { scope: ['client', 'preferences', 'sync'], key: syncGroup + ':' + ctx.key, }) as [any, any][]; - const target = cloudData.find(([cond]) => isSameCond(cond, ctx.cond)); + const target = cloudData.find(([scope]) => isSameScope(scope, ctx.scope)); if (target == null) return null; return { value: target[1], @@ -73,12 +73,12 @@ const storageProvider: StorageProvider = { } } - const i = cloudData.findIndex(([cond]) => isSameCond(cond, ctx.cond)); + const i = cloudData.findIndex(([scope]) => isSameScope(scope, ctx.scope)); if (i === -1) { - cloudData.push([ctx.cond, ctx.value]); + cloudData.push([ctx.scope, ctx.value]); } else { - cloudData[i] = [ctx.cond, ctx.value]; + cloudData[i] = [ctx.scope, ctx.value]; } await misskeyApi('i/registry/set', { @@ -104,7 +104,7 @@ const storageProvider: StorageProvider = { }, }; -export const prefer = createProfileManager(storageProvider); +export const prefer = createPrefManager(storageProvider); let latestSyncedAt = Date.now(); @@ -118,7 +118,7 @@ function syncBetweenTabs() { if (latestTab === TAB_ID) return; if (latestAt <= latestSyncedAt) return; - prefer.rewriteProfile(ProfileManager.normalizeProfile(JSON.parse(miLocalStorage.getItem('preferences')!))); + prefer.rewriteProfile(PreferencesManager.normalizeProfile(JSON.parse(miLocalStorage.getItem('preferences')!))); latestSyncedAt = Date.now(); diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index 1b02ec9f16..7e53553107 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -29,7 +29,13 @@ export type SoundStore = { volume: number; }; +// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる) + export const PREF_DEF = { + accounts: { + default: [] as [host: string, user: Misskey.entities.User][], + }, + pinnedUserLists: { accountDependent: true, default: [] as Misskey.entities.UserList[], @@ -56,6 +62,27 @@ export const PREF_DEF = { default: [] as DeckProfile[], }, + emojiPalettes: { + serverDependent: true, + default: [{ + id: 'a', + name: '', + emojis: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'], + }] as { + id: string; + name: string; + emojis: string[]; + }[], + }, + emojiPaletteForReaction: { + serverDependent: true, + default: null as string | null, + }, + emojiPaletteForMain: { + serverDependent: true, + default: null as string | null, + }, + overridedDeviceKind: { default: null as DeviceKind | null, }, @@ -286,6 +313,9 @@ export const PREF_DEF = { confirmOnReact: { default: false, }, + defaultFollowWithReplies: { + default: false, + }, plugins: { default: [] as Plugin[], }, @@ -367,4 +397,7 @@ export const PREF_DEF = { clickToShowInstanceTickerWindow: { default: true, }, + defaultLike: { + default: '❤️', + }, } satisfies PreferencesDefinition; diff --git a/packages/frontend/src/preferences/manager.ts b/packages/frontend/src/preferences/manager.ts index 3f3eba6389..fad0226b6e 100644 --- a/packages/frontend/src/preferences/manager.ts +++ b/packages/frontend/src/preferences/manager.ts @@ -9,7 +9,7 @@ import { host, version } from '@@/js/config.js'; import { PREF_DEF } from './def.js'; import type { Ref, WritableComputedRef } from 'vue'; import type { MenuItem } from '@/types/menu.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; @@ -25,7 +25,7 @@ type PREF = typeof PREF_DEF; type ValueOf<K extends keyof PREF> = PREF[K]['default']; type Account = string; // <host>/<userId> -type Cond = Partial<{ +type Scope = Partial<{ server: string | null; // 将来のため account: Account | null; device: string | null; // 将来のため @@ -35,33 +35,33 @@ type ValueMeta = Partial<{ sync: boolean; }>; -type PrefRecord<K extends keyof PREF> = [cond: Cond, value: ValueOf<K>, meta: ValueMeta]; +type PrefRecord<K extends keyof PREF> = [scope: Scope, value: ValueOf<K>, meta: ValueMeta]; -function parseCond(cond: Cond): { +function parseScope(scope: Scope): { server: string | null; account: Account | null; device: string | null; } { return { - server: cond.server ?? null, - account: cond.account ?? null, - device: cond.device ?? null, + server: scope.server ?? null, + account: scope.account ?? null, + device: scope.device ?? null, }; } -function makeCond(cond: Partial<{ +function makeScope(scope: Partial<{ server: string | null; account: Account | null; device: string | null; -}>): Cond { - const c = {} as Cond; - if (cond.server != null) c.server = cond.server; - if (cond.account != null) c.account = cond.account; - if (cond.device != null) c.device = cond.device; +}>): Scope { + const c = {} as Scope; + if (scope.server != null) c.server = scope.server; + if (scope.account != null) c.account = scope.account; + if (scope.device != null) c.device = scope.device; return c; } -export function isSameCond(a: Cond, b: Cond): boolean { +export function isSameScope(a: Scope, b: Scope): boolean { // null と undefined (キー無し) は区別したくないので == で比較 // eslint-disable-next-line eqeqeq return a.server == b.server && a.account == b.account && a.device == b.device; @@ -80,9 +80,9 @@ export type PreferencesProfile = { export type StorageProvider = { save: (ctx: { profile: PreferencesProfile; }) => void; - cloudGets: <K extends keyof PREF>(ctx: { needs: { key: K; cond: Cond; }[] }) => Promise<Partial<Record<K, ValueOf<K>>>>; - cloudGet: <K extends keyof PREF>(ctx: { key: K; cond: Cond; }) => Promise<{ value: ValueOf<K>; } | null>; - cloudSet: <K extends keyof PREF>(ctx: { key: K; cond: Cond; value: ValueOf<K>; }) => Promise<void>; + cloudGets: <K extends keyof PREF>(ctx: { needs: { key: K; scope: Scope; }[] }) => Promise<Partial<Record<K, ValueOf<K>>>>; + cloudGet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; }) => Promise<{ value: ValueOf<K>; } | null>; + cloudSet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; value: ValueOf<K>; }) => Promise<void>; }; export type PreferencesDefinition = Record<string, { @@ -91,9 +91,10 @@ export type PreferencesDefinition = Record<string, { serverDependent?: boolean; }>; -export class ProfileManager { +export class PreferencesManager { private storageProvider: StorageProvider; public profile: PreferencesProfile; + public cloudReady: Promise<void>; /** * static / state の略 (static が予約語のため) @@ -120,7 +121,7 @@ export class ProfileManager { this.r[key] = ref(this.s[key]); } - this.fetchCloudValues(); + this.cloudReady = this.fetchCloudValues(); // TODO: 定期的にクラウドの値をフェッチ } @@ -140,8 +141,8 @@ export class ProfileManager { this.rewriteRawState(key, value); const record = this.getMatchedRecordOf(key); - if (parseCond(record[0]).account == null && this.isAccountDependentKey(key)) { - this.profile.preferences[key].push([makeCond({ + if (parseScope(record[0]).account == null && this.isAccountDependentKey(key)) { + this.profile.preferences[key].push([makeScope({ account: `${host}/${$i!.id}`, }), value, {}]); this.save(); @@ -154,7 +155,7 @@ export class ProfileManager { if (record[2].sync) { // awaitの必要なし // TODO: リクエストを間引く - this.storageProvider.cloudSet({ key, cond: record[0], value: record[1] }); + this.storageProvider.cloudSet({ key, scope: record[0], value: record[1] }); } } @@ -207,14 +208,14 @@ export class ProfileManager { } private async fetchCloudValues() { - const needs = [] as { key: keyof PREF; cond: Cond; }[]; + const needs = [] as { key: keyof PREF; scope: Scope; }[]; for (const _key in PREF_DEF) { const key = _key as keyof PREF; const record = this.getMatchedRecordOf(key); if (record[2].sync) { needs.push({ key, - cond: record[0], + scope: record[0], }); } } @@ -226,7 +227,7 @@ export class ProfileManager { const record = this.getMatchedRecordOf(key); if (record[2].sync && Object.hasOwn(cloudValues, key) && cloudValues[key] !== undefined) { const cloudValue = cloudValues[key]; - if (cloudValue !== this.s[key]) { + if (!deepEqual(cloudValue, record[1])) { this.rewriteRawState(key, cloudValue); record[1] = cloudValue; console.log('cloud fetched', key, cloudValue); @@ -241,7 +242,7 @@ export class ProfileManager { public static newProfile(): PreferencesProfile { const data = {} as PreferencesProfile['preferences']; for (const key in PREF_DEF) { - data[key] = [[makeCond({}), PREF_DEF[key].default, {}]]; + data[key] = [[makeScope({}), PREF_DEF[key].default, {}]]; } return { id: uuid(), @@ -258,7 +259,7 @@ export class ProfileManager { for (const key in PREF_DEF) { const records = profileLike.preferences[key]; if (records == null || records.length === 0) { - data[key] = [[makeCond({}), PREF_DEF[key].default, {}]]; + data[key] = [[makeScope({}), PREF_DEF[key].default, {}]]; continue; } else { data[key] = records; @@ -288,18 +289,18 @@ export class ProfileManager { public getMatchedRecordOf<K extends keyof PREF>(key: K): PrefRecord<K> { const records = this.profile.preferences[key]; - if ($i == null) return records.find(([cond, v]) => parseCond(cond).account == null)!; + if ($i == null) return records.find(([scope, v]) => parseScope(scope).account == null)!; - const accountOverrideRecord = records.find(([cond, v]) => parseCond(cond).account === `${host}/${$i!.id}`); + const accountOverrideRecord = records.find(([scope, v]) => parseScope(scope).account === `${host}/${$i!.id}`); if (accountOverrideRecord) return accountOverrideRecord; - const record = records.find(([cond, v]) => parseCond(cond).account == null); + const record = records.find(([scope, v]) => parseScope(scope).account == null); return record!; } public isAccountOverrided<K extends keyof PREF>(key: K): boolean { if ($i == null) return false; - return this.profile.preferences[key].some(([cond, v]) => parseCond(cond).account === `${host}/${$i!.id}`) ?? false; + return this.profile.preferences[key].some(([scope, v]) => parseScope(scope).account === `${host}/${$i!.id}`) ?? false; } public setAccountOverride<K extends keyof PREF>(key: K) { @@ -308,7 +309,7 @@ export class ProfileManager { if (this.isAccountOverrided(key)) return; const records = this.profile.preferences[key]; - records.push([makeCond({ + records.push([makeScope({ account: `${host}/${$i!.id}`, }), this.s[key], {}]); @@ -321,7 +322,7 @@ export class ProfileManager { const records = this.profile.preferences[key]; - const index = records.findIndex(([cond, v]) => parseCond(cond).account === `${host}/${$i!.id}`); + const index = records.findIndex(([scope, v]) => parseScope(scope).account === `${host}/${$i!.id}`); if (index === -1) return; records.splice(index, 1); @@ -340,7 +341,7 @@ export class ProfileManager { const record = this.getMatchedRecordOf(key); - const existing = await this.storageProvider.cloudGet({ key, cond: record[0] }); + const existing = await this.storageProvider.cloudGet({ key, scope: record[0] }); if (existing != null && !deepEqual(existing.value, record[1])) { const { canceled, result } = await os.select({ title: i18n.ts.preferenceSyncConflictTitle, @@ -370,7 +371,7 @@ export class ProfileManager { this.save(); // awaitの必要性は無い - this.storageProvider.cloudSet({ key, cond: record[0], value: this.s[key] }); + this.storageProvider.cloudSet({ key, scope: record[0], value: this.s[key] }); return { enabled: true }; } diff --git a/packages/frontend/src/preferences/utility.ts b/packages/frontend/src/preferences/utility.ts index c37dbcf96b..bf3dfa157f 100644 --- a/packages/frontend/src/preferences/utility.ts +++ b/packages/frontend/src/preferences/utility.ts @@ -12,7 +12,7 @@ import { miLocalStorage } from '@/local-storage.js'; import { prefer } from '@/preferences.js'; import * as os from '@/os.js'; import { store } from '@/store.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { unisonReload } from '@/utility/unison-reload.js'; diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index d67fc35cf4..5a8509717d 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -7,7 +7,7 @@ import { defineAsyncComponent } from 'vue'; import type { AsyncComponentLoader } from 'vue'; import type { IRouter, RouteDef } from '@/nirax.js'; import { Router } from '@/nirax.js'; -import { $i, iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/i.js'; import MkLoading from '@/pages/_loading_.vue'; import MkError from '@/pages/_error_.vue'; @@ -57,18 +57,14 @@ const routes: RouteDef[] = [{ path: '/avatar-decoration', name: 'avatarDecoration', component: page(() => import('@/pages/settings/avatar-decoration.vue')), - }, { - path: '/roles', - name: 'roles', - component: page(() => import('@/pages/settings/roles.vue')), }, { path: '/privacy', name: 'privacy', component: page(() => import('@/pages/settings/privacy.vue')), }, { - path: '/emoji-picker', - name: 'emojiPicker', - component: page(() => import('@/pages/settings/emoji-picker.vue')), + path: '/emoji-palette', + name: 'emoji-palette', + component: page(() => import('@/pages/settings/emoji-palette.vue')), }, { path: '/drive', name: 'drive', @@ -105,10 +101,6 @@ const routes: RouteDef[] = [{ path: '/theme', name: 'theme', component: page(() => import('@/pages/settings/theme.vue')), - }, { - path: '/appearance', - name: 'appearance', - component: page(() => import('@/pages/settings/appearance.vue')), }, { path: '/navbar', name: 'navbar', diff --git a/packages/frontend/src/signout.ts b/packages/frontend/src/signout.ts new file mode 100644 index 0000000000..8e90552546 --- /dev/null +++ b/packages/frontend/src/signout.ts @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { defineAsyncComponent, ref } from 'vue'; +import { apiUrl, host } from '@@/js/config.js'; +import { defaultMemoryStorage } from '@/memory-storage'; +import { i18n } from '@/i18n.js'; +import { miLocalStorage } from '@/local-storage.js'; +import { waiting, popup, popupMenu, success, alert } from '@/os.js'; +import { unisonReload, reloadChannel } from '@/utility/unison-reload.js'; +import { prefer } from '@/preferences.js'; +import { store } from '@/store.js'; +import { $i } from '@/i.js'; + +export async function signout() { + if (!$i) return; + + defaultMemoryStorage.clear(); + + waiting(); + miLocalStorage.removeItem('account'); + + // TODO: preferencesも削除 + + //#region Remove service worker registration + try { + if (navigator.serviceWorker.controller) { + const registration = await navigator.serviceWorker.ready; + const push = await registration.pushManager.getSubscription(); + if (push) { + await window.fetch(`${apiUrl}/sw/unregister`, { + method: 'POST', + body: JSON.stringify({ + i: $i.token, + endpoint: push.endpoint, + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + } + } + + await navigator.serviceWorker.getRegistrations() + .then(registrations => { + return Promise.all(registrations.map(registration => registration.unregister())); + }); + } catch (err) {} + //#endregion + + unisonReload('/'); +} diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index e1ef471a5a..763e468bbe 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -39,14 +39,6 @@ export const store = markRaw(new Storage('base', { where: 'account', default: null, }, - reactions: { - where: 'account', - default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮', '😋', '🥰'], - }, - pinnedEmojis: { - where: 'account', - default: [], - }, reactionAcceptance: { where: 'account', default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null, @@ -112,14 +104,14 @@ export const store = markRaw(new Storage('base', { where: 'device', default: {} as Record<string, Record<string, string[]>>, }, - defaultWithReplies: { - where: 'account', - default: true, - }, pluginTokens: { where: 'deviceAccount', default: {} as Record<string, string>, // plugin id, token }, + accountTokens: { + where: 'device', + default: {} as Record<string, string>, // host/userId, token + }, enablePreferencesAutoCloudBackup: { where: 'device', @@ -131,6 +123,18 @@ export const store = markRaw(new Storage('base', { }, //#region TODO: そのうち消す (preferに移行済み) + defaultWithReplies: { + where: 'account', + default: false, + }, + reactions: { + where: 'account', + default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'], + }, + pinnedEmojis: { + where: 'account', + default: [], + }, widgets: { where: 'account', default: [] as { diff --git a/packages/frontend/src/stream.ts b/packages/frontend/src/stream.ts index e7367e45d8..69ad22de3c 100644 --- a/packages/frontend/src/stream.ts +++ b/packages/frontend/src/stream.ts @@ -5,7 +5,7 @@ import * as Misskey from 'misskey-js'; import { markRaw } from 'vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { wsOrigin } from '@@/js/config.js'; // TODO: No WebsocketモードでStreamMockが使えそう //import { StreamMock } from '@/utility/stream-mock.js'; diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index aa7e8443ca..ae5c34cb85 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -158,7 +158,7 @@ optgroup, option { } hr { - margin: var(--MI-margin) 0 var(--MI-margin) 0; + margin: 0; border: none; height: 1px; background: var(--MI_THEME-divider); diff --git a/packages/frontend/src/theme-store.ts b/packages/frontend/src/theme-store.ts index 5d09ec27f9..2ae5d8730e 100644 --- a/packages/frontend/src/theme-store.ts +++ b/packages/frontend/src/theme-store.ts @@ -5,7 +5,7 @@ import type { Theme } from '@/theme.js'; import { getBuiltinThemes } from '@/theme.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { prefer } from '@/preferences.js'; export function getThemes(): Theme[] { diff --git a/packages/frontend/src/timelines.ts b/packages/frontend/src/timelines.ts index 94eda3545e..a39ccd481d 100644 --- a/packages/frontend/src/timelines.ts +++ b/packages/frontend/src/timelines.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { instance } from '@/instance.js'; export const basicTimelineTypes = [ diff --git a/packages/frontend/src/ui/_common_/PreferenceRestore.vue b/packages/frontend/src/ui/_common_/PreferenceRestore.vue index c70b82cd0e..5fd9f5e44b 100644 --- a/packages/frontend/src/ui/_common_/PreferenceRestore.vue +++ b/packages/frontend/src/ui/_common_/PreferenceRestore.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; import { hideRestoreBackupSuggestion, restoreFromCloudBackup } from '@/preferences/utility.js'; diff --git a/packages/frontend/src/ui/_common_/announcements.vue b/packages/frontend/src/ui/_common_/announcements.vue index d153dc8726..f9af8e1ee7 100644 --- a/packages/frontend/src/ui/_common_/announcements.vue +++ b/packages/frontend/src/ui/_common_/announcements.vue @@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; </script> <style lang="scss" module> diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index 8e5ba8927a..819e1fa42f 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -9,7 +9,7 @@ import * as os from '@/os.js'; import { instance } from '@/instance.js'; import { host } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; function toolsMenuItems(): MenuItem[] { return [{ diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index ef7474c8a9..5810672021 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -58,7 +58,7 @@ import { popups } from '@/os.js'; import { pendingApiRequestsCount } from '@/utility/misskey-api.js'; import { uploads } from '@/utility/upload.js'; import * as sound from '@/utility/sound.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue index 698e9d8d47..2fbc9ab4b3 100644 --- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue +++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue @@ -53,10 +53,11 @@ import { computed, defineAsyncComponent, toRef } from 'vue'; import { openInstanceMenu } from './common.js'; import * as os from '@/os.js'; import { navbarItemDef } from '@/navbar.js'; -import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js'; import { prefer } from '@/preferences.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; +import { openAccountMenu as openAccountMenu_ } from '@/accounts.js'; +import { $i } from '@/i.js'; const menu = toRef(prefer.s, 'menu'); const otherMenuItemIndicated = computed(() => { diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index 234972e76d..1810ec1743 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -93,13 +93,14 @@ import { computed, defineAsyncComponent, ref, watch } from 'vue'; import { openInstanceMenu } from './common.js'; import * as os from '@/os.js'; import { navbarItemDef } from '@/navbar.js'; -import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js'; import { store } from '@/store.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js'; import { useRouter } from '@/router/supplier.js'; import { prefer } from '@/preferences.js'; +import { openAccountMenu as openAccountMenu_ } from '@/accounts.js'; +import { $i } from '@/i.js'; const router = useRouter(); diff --git a/packages/frontend/src/ui/_common_/sw-inject.ts b/packages/frontend/src/ui/_common_/sw-inject.ts index df392c6532..ae61e497b5 100644 --- a/packages/frontend/src/ui/_common_/sw-inject.ts +++ b/packages/frontend/src/ui/_common_/sw-inject.ts @@ -5,10 +5,11 @@ import { post } from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { $i, login } from '@/account.js'; +import { $i } from '@/i.js'; import { getAccountFromId } from '@/utility/get-account-from-id.js'; import { deepClone } from '@/utility/clone.js'; import { mainRouter } from '@/router/main.js'; +import { login } from '@/accounts.js'; export function swInject() { navigator.serviceWorker.addEventListener('message', async ev => { diff --git a/packages/frontend/src/ui/classic.header.vue b/packages/frontend/src/ui/classic.header.vue index 39b40754ff..1e8a342977 100644 --- a/packages/frontend/src/ui/classic.header.vue +++ b/packages/frontend/src/ui/classic.header.vue @@ -51,12 +51,12 @@ import { computed, defineAsyncComponent, onMounted, ref } from 'vue'; import { openInstanceMenu } from './_common_/common.js'; import * as os from '@/os.js'; import { navbarItemDef } from '@/navbar.js'; -import { openAccountMenu as openAccountMenu_, $i } from '@/account.js'; import MkButton from '@/components/MkButton.vue'; -import { store } from '@/store.js'; import { instance } from '@/instance.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; +import { openAccountMenu as openAccountMenu_ } from '@/accounts.js'; +import { $i } from '@/i.js'; const WINDOW_THRESHOLD = 1400; diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue index 259aad7401..096ea0d4cf 100644 --- a/packages/frontend/src/ui/classic.sidebar.vue +++ b/packages/frontend/src/ui/classic.sidebar.vue @@ -54,7 +54,6 @@ import { openInstanceMenu } from './_common_/common.js'; // import { host } from '@@/js/config.js'; import * as os from '@/os.js'; import { navbarItemDef } from '@/navbar.js'; -import { openAccountMenu as openAccountMenu_, $i } from '@/account.js'; import MkButton from '@/components/MkButton.vue'; // import { StickySidebar } from '@/utility/sticky-sidebar.js'; // import { mainRouter } from '@/router.js'; @@ -63,6 +62,8 @@ import { store } from '@/store.js'; import { instance } from '@/instance.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; +import { openAccountMenu as openAccountMenu_ } from '@/accounts.js'; +import { $i } from '@/i.js'; const WINDOW_THRESHOLD = 1400; diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index 994f0e8c56..e02ca97206 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -105,7 +105,7 @@ import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { navbarItemDef } from '@/navbar.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; import { deviceKind } from '@/utility/device-kind.js'; import { prefer } from '@/preferences.js'; diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index 38bd07b7f8..c429c2ccd1 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -108,7 +108,7 @@ import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import * as os from '@/os.js'; import { navbarItemDef } from '@/navbar.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js'; import { deviceKind } from '@/utility/device-kind.js'; import { miLocalStorage } from '@/local-storage.js'; diff --git a/packages/frontend/src/utility/use-chart-tooltip.ts b/packages/frontend/src/use/use-chart-tooltip.ts similarity index 100% rename from packages/frontend/src/utility/use-chart-tooltip.ts rename to packages/frontend/src/use/use-chart-tooltip.ts diff --git a/packages/frontend/src/utility/use-form.ts b/packages/frontend/src/use/use-form.ts similarity index 100% rename from packages/frontend/src/utility/use-form.ts rename to packages/frontend/src/use/use-form.ts diff --git a/packages/frontend/src/utility/use-leave-guard.ts b/packages/frontend/src/use/use-leave-guard.ts similarity index 100% rename from packages/frontend/src/utility/use-leave-guard.ts rename to packages/frontend/src/use/use-leave-guard.ts diff --git a/packages/frontend/src/utility/use-note-capture.ts b/packages/frontend/src/use/use-note-capture.ts similarity index 95% rename from packages/frontend/src/utility/use-note-capture.ts rename to packages/frontend/src/use/use-note-capture.ts index b2c387a90c..5ff5ba8950 100644 --- a/packages/frontend/src/utility/use-note-capture.ts +++ b/packages/frontend/src/use/use-note-capture.ts @@ -4,12 +4,11 @@ */ import { onUnmounted } from 'vue'; -import type { Ref, ShallowRef } from 'vue'; import * as Misskey from 'misskey-js'; -import { misskeyApi } from './misskey-api.js'; +import type { Ref, ShallowRef } from 'vue'; +import { misskeyApi } from '@/utility/misskey-api.js'; import { useStream } from '@/stream.js'; -import { $i } from '@/account.js'; -import * as os from '@/os.js'; +import { $i } from '@/i.js'; export function useNoteCapture(props: { rootEl: ShallowRef<HTMLElement | undefined>; @@ -20,7 +19,7 @@ export function useNoteCapture(props: { onDeleteCallback?: (id: Misskey.entities.Note['id']) => void | Promise<void>; }) { const note = props.note; - const pureNote = props.pureNote !== undefined ? props.pureNote : props.note; + const pureNote = props.pureNote ?? props.note; const connection = $i ? useStream() : null; async function onStreamNoteUpdated(noteData): Promise<void> { diff --git a/packages/frontend/src/utility/use-tooltip.ts b/packages/frontend/src/use/use-tooltip.ts similarity index 100% rename from packages/frontend/src/utility/use-tooltip.ts rename to packages/frontend/src/use/use-tooltip.ts diff --git a/packages/frontend/src/utility/achievements.ts b/packages/frontend/src/utility/achievements.ts index 3025a985ba..f6ab587ae1 100644 --- a/packages/frontend/src/utility/achievements.ts +++ b/packages/frontend/src/utility/achievements.ts @@ -4,7 +4,7 @@ */ import { misskeyApi } from '@/utility/misskey-api.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; export const ACHIEVEMENT_TYPES = [ 'notes1', diff --git a/packages/frontend/src/utility/autogen/settings-search-index.ts b/packages/frontend/src/utility/autogen/settings-search-index.ts index d0bce9a522..e768d8a5ae 100644 --- a/packages/frontend/src/utility/autogen/settings-search-index.ts +++ b/packages/frontend/src/utility/autogen/settings-search-index.ts @@ -271,177 +271,271 @@ export const searchIndexes: SearchIndexItem[] = [ id: '3yCAv0IsZ', children: [ { - id: 'kMJ5laK3n', - label: i18n.ts.uiLanguage, - keywords: ['language'], - }, - { - id: 'dlKebHH6k', - label: i18n.ts.overridedDeviceKind, - keywords: ['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop'], - }, - { - id: 'nxvMUir3T', - label: i18n.ts.showFixedPostForm, - keywords: ['post', 'form', 'timeline'], - }, - { - id: '84MdeDWL1', - label: i18n.ts.showFixedPostFormInChannel, - keywords: ['post', 'form', 'timeline', 'channel'], - }, - { - id: 'dOig3ye4Z', - label: i18n.ts.pinnedList, - keywords: ['pinned', 'list'], - }, - { - id: '4huRldNp5', - label: i18n.ts.enableQuickAddMfmFunction, - keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'], - }, - { - id: '1x3JNXj8N', - label: i18n.ts.rememberNoteVisibility, - keywords: ['remember', 'keep', 'note', 'visibility'], - }, - { - id: 'CfAg0Qekq', - label: i18n.ts.defaultNoteVisibility, - keywords: ['default', 'note', 'visibility'], - }, - { - id: 'tMm9kH9gy', + id: 'AKvDrxSj5', children: [ { - id: 'hDdVkBFJP', + id: 'cAszhShB0', + label: i18n.ts.uiLanguage, + keywords: ['language'], + }, + { + id: 'apz9AutPm', + label: i18n.ts.overridedDeviceKind, + keywords: ['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop'], + }, + { + id: 'nqRVtw1xw', + label: i18n.ts.useBlurEffect, + keywords: ['blur'], + }, + { + id: 'EO5WHBeG8', + label: i18n.ts.useBlurEffectForModal, + keywords: ['blur', 'modal'], + }, + { + id: 'CWpyT9vLK', + label: i18n.ts.showAvatarDecorations, + keywords: ['avatar', 'icon', 'decoration', 'show'], + }, + { + id: '1wwACqQz1', + label: i18n.ts.alwaysConfirmFollow, + keywords: ['follow', 'confirm', 'always'], + }, + { + id: '1x3JNXj8N', + label: i18n.ts.highlightSensitiveMedia, + keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'], + }, + { + id: 'CfAg0Qekq', + label: i18n.ts.confirmWhenRevealingSensitiveMedia, + keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'], + }, + { + id: '4LxdiOMNh', + label: i18n.ts.emojiStyle, + keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'], + }, + { + id: '67knC3FWp', + label: i18n.ts.pinnedList, + keywords: ['pinned', 'list'], + }, + ], + label: i18n.ts.general, + keywords: ['general'], + }, + { + id: 'hDdVkBFJP', + children: [ + { + id: 'igFN7RIUa', + label: i18n.ts.showFixedPostForm, + keywords: ['post', 'form', 'timeline'], + }, + { + id: '9uxocbLO0', + label: i18n.ts.showFixedPostFormInChannel, + keywords: ['post', 'form', 'timeline', 'channel'], + }, + { + id: 'eaT1O1Fao', label: i18n.ts.collapseRenotes, keywords: ['renote', i18n.ts.collapseRenotesDescription], }, { - id: 'uJJyDABGu', + id: 'jC7LtTnmc', + label: i18n.ts.showGapBetweenNotesInTimeline, + keywords: ['note', 'timeline', 'gap'], + }, + { + id: 'p2wlrnwLo', + label: i18n.ts.enableInfiniteScroll, + keywords: ['load', 'auto', 'more'], + }, + { + id: 'eqMBMY6LU', + label: i18n.ts.disableStreamingTimeline, + keywords: ['disable', 'streaming', 'timeline'], + }, + ], + label: i18n.ts.timeline, + keywords: ['timeline'], + }, + { + id: '2LNjwv1cr', + children: [ + { + id: '6ylW3eIcD', label: i18n.ts.showNoteActionsOnlyHover, keywords: ['hover', 'show', 'footer', 'action'], }, { - id: 'ufc2X9voy', + id: 'lBbtAg0Hm', label: i18n.ts.showClipButtonInNoteFooter, keywords: ['footer', 'action', 'clip', 'show'], }, { - id: '7Jwvu8bK6', + id: 'E9whefUtX', label: i18n.ts.enableAdvancedMfm, keywords: ['mfm', 'enable', 'show', 'advanced'], }, { - id: 'yb11lSY1G', + id: 'iQaBbJBva', label: i18n.ts.showReactionsCount, keywords: ['reaction', 'count', 'show'], }, { - id: 'fL49Zxe9i', + id: 'hgEVGgJa1', + label: i18n.ts.confirmOnReact, + keywords: ['reaction', 'confirm'], + }, + { + id: 'yxehrHZ6x', label: i18n.ts.loadRawImages, keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment'], }, + { + id: 'DdoFLaSG8', + label: i18n.ts.useReactionPickerForContextMenu, + keywords: ['reaction', 'picker', 'contextmenu', 'open'], + }, + { + id: 'fyod6U3QX', + label: i18n.ts.reactionsDisplaySize, + keywords: ['reaction', 'size', 'scale', 'display'], + }, + { + id: 'kmdsnVIQX', + label: i18n.ts.limitWidthOfReaction, + keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'], + }, + { + id: 'hacQ9br20', + label: i18n.ts.mediaListWithOneImageAppearance, + keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'], + }, + { + id: 'vE7KeV4U4', + label: i18n.ts.instanceTicker, + keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'], + }, + { + id: '3reoOxO26', + label: i18n.ts.displayOfSensitiveMedia, + keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'], + }, ], label: i18n.ts.note, keywords: ['note'], }, { - id: 'bUOs2UKY4', + id: 'eROFRMtXv', children: [ { - id: 'c8gA9Xj2a', + id: 'bezWaWd6M', + label: i18n.ts.keepCw, + keywords: ['remember', 'keep', 'note', 'cw'], + }, + { + id: '90ngq28Nx', + label: i18n.ts.rememberNoteVisibility, + keywords: ['remember', 'keep', 'note', 'visibility'], + }, + { + id: 'ERGQVw6ml', + label: i18n.ts.enableQuickAddMfmFunction, + keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'], + }, + { + id: 'g0otcvWv3', + label: i18n.ts.defaultNoteVisibility, + keywords: ['default', 'note', 'visibility'], + }, + ], + label: i18n.ts.postForm, + keywords: ['post', 'form'], + }, + { + id: 'AWLIP02IT', + children: [ + { + id: 'rDLJRu99', label: i18n.ts.useGroupedNotifications, keywords: ['group'], }, + { + id: '70WDijfPH', + label: i18n.ts.position, + keywords: ['position'], + }, + { + id: 'xKUzsSrKy', + label: i18n.ts.stackAxis, + keywords: ['stack', 'axis', 'direction'], + }, ], label: i18n.ts.notifications, keywords: ['notification'], }, { - id: 'tjGzqy3qa', + id: '2E7vdIUQd', + label: i18n.ts.dataSaver, + keywords: ['datasaver'], + }, + { + id: '6ZbRRIhA6', children: [ { - id: '3OeHscv45', + id: 'soNZaKfiW', + label: i18n.ts.squareAvatars, + keywords: ['avatar', 'icon', 'square'], + }, + { + id: 'nhwHJJ2tl', + label: i18n.ts.seasonalScreenEffect, + keywords: ['effect', 'show'], + }, + { + id: 'oMAVUuxTm', label: i18n.ts.openImageInNewTab, keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'], }, { - id: 'bFsNusspF', - label: i18n.ts.useReactionPickerForContextMenu, - keywords: ['reaction', 'picker', 'contextmenu', 'open'], + id: 'hSqX5JKM7', + label: i18n.ts.withRepliesByDefaultForNewlyFollowed, + keywords: ['follow', 'replies'], }, { - id: '2h3rY1izt', - label: i18n.ts.enableInfiniteScroll, - keywords: ['load', 'auto', 'more'], - }, - { - id: 'pkK3eeFKm', - label: i18n.ts.disableStreamingTimeline, - keywords: ['disable', 'streaming', 'timeline'], - }, - { - id: 'y2v7CV9zs', - label: i18n.ts.alwaysConfirmFollow, - keywords: ['follow', 'confirm', 'always'], - }, - { - id: 'A8a5hcLce', - label: i18n.ts.confirmWhenRevealingSensitiveMedia, - keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'], - }, - { - id: 'utFrfuW7X', - label: i18n.ts.confirmOnReact, - keywords: ['reaction', 'confirm'], - }, - { - id: 'kmdsnVIQX', - label: i18n.ts.keepCw, - keywords: ['remember', 'keep', 'note', 'cw'], - }, - { - id: 'mNRK0pt8L', + id: 'fm98eqzke', label: i18n.ts.whenServerDisconnected, keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'], }, { - id: 'vE7KeV4U4', + id: '1rWDVig8Y', label: i18n.ts.numberOfPageCache, keywords: ['cache', 'page'], }, { - id: 'eJ2jme16W', - label: i18n.ts.dataSaver, - keywords: ['datasaver'], - }, - ], - label: i18n.ts.behavior, - keywords: ['behavior'], - }, - { - id: 'F3kpUNvSQ', - children: [ - { - id: '4bfFRM0UD', + id: 'vXLtihtCp', label: i18n.ts.forceShowAds, keywords: ['ad', 'show'], }, { - id: '2pB0jWBHo', + id: '77YljFpiH', label: i18n.ts.hemisphere, keywords: [], }, { - id: 'eIvnR6Xxo', + id: 'CZgDNPP1h', label: i18n.ts.additionalEmojiDictionary, keywords: ['emoji', 'dictionary', 'additional', 'extra'], }, ], label: i18n.ts.other, - keywords: [], + keywords: ['other'], }, ], label: i18n.ts.preferences, @@ -460,27 +554,32 @@ export const searchIndexes: SearchIndexItem[] = [ id: 'F1uK9ssiY', children: [ { - id: 'msAcN6u3S', - label: i18n.ts.accountInfo, + id: 'E0ndmaP6Q', + label: i18n.ts._role.policies, keywords: ['account', 'info'], }, { - id: 'ts8DgdnZV', + id: 'r5SjfwZJc', + label: i18n.ts.rolesAssignedToMe, + keywords: ['roles'], + }, + { + id: 'cm7LrjgaW', label: i18n.ts.accountMigration, keywords: ['account', 'move', 'migration'], }, { - id: '4BG7nBECm', + id: 'ozfqNviP3', label: i18n.ts.closeAccount, keywords: ['account', 'close', 'delete', i18n.ts._accountDelete.requestAccountDelete], }, { - id: '2qI6ruPgi', + id: 'tpywgkpxy', label: i18n.ts.experimentalFeatures, keywords: ['experimental', 'feature', 'flags'], }, { - id: 'cIeaax47o', + id: '54wETGawJ', label: i18n.ts.developer, keywords: ['developer', 'mode', 'debug'], }, @@ -536,6 +635,57 @@ export const searchIndexes: SearchIndexItem[] = [ path: '/settings/mute-block', icon: 'ti ti-ban', }, + { + id: 'yR1OSyLiT', + children: [ + { + id: 'yMJzyzOUk', + label: i18n.ts._emojiPalette.enableSyncBetweenDevicesForPalettes, + keywords: ['sync', 'palettes', 'devices'], + }, + { + id: 'wCE09vgZr', + label: i18n.ts._emojiPalette.paletteForMain, + keywords: ['main', 'palette'], + }, + { + id: 'uCzRPrSNx', + label: i18n.ts._emojiPalette.paletteForReaction, + keywords: ['reaction', 'palette'], + }, + { + id: 'hgQr28WUk', + children: [ + { + id: 'fY04NIHSQ', + label: i18n.ts.size, + keywords: ['emoji', 'picker', 'scale', 'size'], + }, + { + id: '3j7vlaL7t', + label: i18n.ts.numberOfColumn, + keywords: ['emoji', 'picker', 'width', 'column', 'size'], + }, + { + id: 'zPX8z1Bcy', + label: i18n.ts.height, + keywords: ['emoji', 'picker', 'height', 'size'], + }, + { + id: '2CSkZa4tl', + label: i18n.ts.style, + keywords: ['emoji', 'picker', 'style'], + }, + ], + label: i18n.ts.emojiPickerDisplay, + keywords: ['emoji', 'picker', 'display'], + }, + ], + label: i18n.ts.emojiPalette, + keywords: ['emoji', 'palette'], + path: '/settings/emoji-palette', + icon: 'ti ti-mood-happy', + }, { id: '3Tcxw4Fwl', children: [ @@ -606,6 +756,33 @@ export const searchIndexes: SearchIndexItem[] = [ }, { id: 'FfZdOs8y', + children: [ + { + id: 'B1ZU6Ur54', + label: i18n.ts._deck.enableSyncBetweenDevicesForProfiles, + keywords: ['sync', 'profiles', 'devices'], + }, + { + id: 'iEF0gqNAo', + label: i18n.ts._deck.useSimpleUiForNonRootPages, + keywords: ['ui', 'root', 'page'], + }, + { + id: 'BNdSeWxZn', + label: i18n.ts.defaultNavigationBehaviour, + keywords: ['default', 'navigation', 'behaviour', 'window'], + }, + { + id: 'zT9pGm8DF', + label: i18n.ts._deck.alwaysShowMainColumn, + keywords: ['always', 'show', 'main', 'column'], + }, + { + id: '5dk2xv1vc', + label: i18n.ts._deck.columnAlign, + keywords: ['column', 'align'], + }, + ], label: i18n.ts.deck, keywords: ['deck', 'ui'], path: '/settings/deck', @@ -638,117 +815,11 @@ export const searchIndexes: SearchIndexItem[] = [ icon: 'ti ti-sparkles', }, { - id: 'AqPvMgn3A', - children: [ - { - id: '1wtOIwAdm', - label: i18n.ts.useBlurEffect, - keywords: ['blur'], - }, - { - id: '6fLNMTwNt', - label: i18n.ts.useBlurEffectForModal, - keywords: ['blur', 'modal'], - }, - { - id: 'E0WXhhRB1', - label: i18n.ts.highlightSensitiveMedia, - keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'], - }, - { - id: '7iZsGkplG', - label: i18n.ts.squareAvatars, - keywords: ['avatar', 'icon', 'square'], - }, - { - id: 'AfRMcC6IM', - label: i18n.ts.showAvatarDecorations, - keywords: ['avatar', 'icon', 'decoration', 'show'], - }, - { - id: 'i7aSaEWaT', - label: i18n.ts.showGapBetweenNotesInTimeline, - keywords: ['note', 'timeline', 'gap'], - }, - { - id: 'knj98Mx84', - label: i18n.ts.seasonalScreenEffect, - keywords: ['effect', 'show'], - }, - { - id: 'Bzg77rYNd', - label: i18n.ts.menuStyle, - keywords: ['menu', 'style', 'popup', 'drawer'], - }, - { - id: '7AOZ1ZgDv', - label: i18n.ts.emojiStyle, - keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'], - }, - { - id: 'fDelHUrBi', - label: i18n.ts.fontSize, - keywords: ['font', 'size'], - }, - { - id: 'siOW5aSwp', - label: i18n.ts.useSystemFont, - keywords: ['font', 'system', 'native'], - }, - { - id: 's05dHQ1dW', - children: [ - { - id: 'zoMbYCvP0', - label: i18n.ts.reactionsDisplaySize, - keywords: ['reaction', 'size', 'scale', 'display'], - }, - { - id: 'lGFzLnWfB', - label: i18n.ts.limitWidthOfReaction, - keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'], - }, - { - id: '9E0v8VKIY', - label: i18n.ts.mediaListWithOneImageAppearance, - keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'], - }, - { - id: 'xB7MPEF4Q', - label: i18n.ts.instanceTicker, - keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'], - }, - { - id: '7siYCSodm', - label: i18n.ts.displayOfSensitiveMedia, - keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'], - }, - ], - label: i18n.ts.displayOfNote, - keywords: ['note', 'display'], - }, - { - id: 'uQfyiHMSs', - children: [ - { - id: 'y3uTXsSQ6', - label: i18n.ts.position, - keywords: ['position'], - }, - { - id: 'PILAdkVM', - label: i18n.ts.stackAxis, - keywords: ['stack', 'axis', 'direction'], - }, - ], - label: i18n.ts.notificationDisplay, - keywords: ['notification', 'display'], - }, - ], - label: i18n.ts.appearance, - keywords: ['appearance', i18n.ts._settings.appearanceBanner], - path: '/settings/appearance', - icon: 'ti ti-device-desktop', + id: 'zK6posor9', + label: i18n.ts.accounts, + keywords: ['accounts'], + path: '/settings/accounts', + icon: 'ti ti-users', }, { id: '330Q4mf8E', @@ -795,7 +866,7 @@ export const searchIndexes: SearchIndexItem[] = [ }, ], label: i18n.ts._settings.accountData, - keywords: ['import', 'export', 'data', i18n.ts._settings.accountDataBanner], + keywords: ['import', 'export', 'data', 'archive', i18n.ts._settings.accountDataBanner], path: '/settings/account-data', icon: 'ti ti-package', }, @@ -834,9 +905,24 @@ export const searchIndexes: SearchIndexItem[] = [ }, { id: '1fV9WINCQ', + label: i18n.ts.menuStyle, + keywords: ['menu', 'style', 'popup', 'drawer'], + }, + { + id: 'mLQzlKUNu', label: i18n.ts._contextMenu.title, keywords: ['contextmenu', 'system', 'native'], }, + { + id: 'yP96aA3j9', + label: i18n.ts.fontSize, + keywords: ['font', 'size'], + }, + { + id: 'jQeiMopFE', + label: i18n.ts.useSystemFont, + keywords: ['font', 'system', 'native'], + }, ], label: i18n.ts.accessibility, keywords: ['accessibility', i18n.ts._settings.accessibilityBanner], diff --git a/packages/frontend/src/utility/check-permissions.ts b/packages/frontend/src/utility/check-permissions.ts index ed86529d5b..2de8fd2cd1 100644 --- a/packages/frontend/src/utility/check-permissions.ts +++ b/packages/frontend/src/utility/check-permissions.ts @@ -4,7 +4,7 @@ */ import { instance } from '@/instance.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; export const notesSearchAvailable = ( // FIXME: instance.policies would be null in Vitest diff --git a/packages/frontend/src/utility/emoji-picker.ts b/packages/frontend/src/utility/emoji-picker.ts index e7275b86f2..6279786b2d 100644 --- a/packages/frontend/src/utility/emoji-picker.ts +++ b/packages/frontend/src/utility/emoji-picker.ts @@ -3,10 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineAsyncComponent, ref } from 'vue'; +import { defineAsyncComponent, ref, watch } from 'vue'; import type { Ref } from 'vue'; import { popup } from '@/os.js'; -import { store } from '@/store.js'; +import { prefer } from '@/preferences.js'; /** * 絵文字ピッカーを表示する。 @@ -25,7 +25,14 @@ class EmojiPicker { } public async init() { - const emojisRef = store.r.pinnedEmojis; + const emojisRef = ref<string[]>([]); + + watch([prefer.r.emojiPaletteForMain, prefer.r.emojiPalettes], () => { + emojisRef.value = prefer.s.emojiPaletteForMain == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForMain)?.emojis ?? []; + }, { + immediate: true, + }); + await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { src: this.src, pinnedEmojis: emojisRef, diff --git a/packages/frontend/src/utility/get-note-menu.ts b/packages/frontend/src/utility/get-note-menu.ts index 069f4c7e0b..ca903d3ed1 100644 --- a/packages/frontend/src/utility/get-note-menu.ts +++ b/packages/frontend/src/utility/get-note-menu.ts @@ -9,7 +9,7 @@ import { url } from '@@/js/config.js'; import { claimAchievement } from './achievements.js'; import type { Ref, ShallowRef } from 'vue'; import type { MenuItem } from '@/types/menu.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import * as os from '@/os.js'; diff --git a/packages/frontend/src/utility/get-user-menu.ts b/packages/frontend/src/utility/get-user-menu.ts index 1b9b0eac95..b89c7537e2 100644 --- a/packages/frontend/src/utility/get-user-menu.ts +++ b/packages/frontend/src/utility/get-user-menu.ts @@ -13,7 +13,7 @@ import { i18n } from '@/i18n.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { $i, iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/i.js'; import { notesSearchAvailable, canSearchNonLocalNotes } from '@/utility/check-permissions.js'; import { antennasCache, rolesCache, userListsCache } from '@/cache.js'; import { mainRouter } from '@/router/main.js'; diff --git a/packages/frontend/src/utility/isFfVisibleForMe.ts b/packages/frontend/src/utility/isFfVisibleForMe.ts index e28e5725bc..48ef1c4e49 100644 --- a/packages/frontend/src/utility/isFfVisibleForMe.ts +++ b/packages/frontend/src/utility/isFfVisibleForMe.ts @@ -4,7 +4,7 @@ */ import * as Misskey from 'misskey-js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; export function isFollowingVisibleForMe(user: Misskey.entities.UserDetailed): boolean { if ($i && ($i.id === user.id || $i.isAdmin || $i.isModerator)) return true; diff --git a/packages/frontend/src/utility/misskey-api.ts b/packages/frontend/src/utility/misskey-api.ts index dc07ad477b..72ba54ade3 100644 --- a/packages/frontend/src/utility/misskey-api.ts +++ b/packages/frontend/src/utility/misskey-api.ts @@ -6,7 +6,7 @@ import * as Misskey from 'misskey-js'; import { ref } from 'vue'; import { apiUrl } from '@@/js/config.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; export const pendingApiRequestsCount = ref(0); export type Endpoint = keyof Misskey.Endpoints; diff --git a/packages/frontend/src/utility/please-login.ts b/packages/frontend/src/utility/please-login.ts index a8a330eb6d..9253105f48 100644 --- a/packages/frontend/src/utility/please-login.ts +++ b/packages/frontend/src/utility/please-login.ts @@ -4,7 +4,7 @@ */ import { defineAsyncComponent } from 'vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { instance } from '@/instance.js'; import { i18n } from '@/i18n.js'; import { popup } from '@/os.js'; diff --git a/packages/frontend/src/utility/reaction-picker.ts b/packages/frontend/src/utility/reaction-picker.ts index 200fb0b686..7c159fa2da 100644 --- a/packages/frontend/src/utility/reaction-picker.ts +++ b/packages/frontend/src/utility/reaction-picker.ts @@ -4,10 +4,10 @@ */ import * as Misskey from 'misskey-js'; -import { defineAsyncComponent, ref } from 'vue'; +import { defineAsyncComponent, ref, watch } from 'vue'; import type { Ref } from 'vue'; import { popup } from '@/os.js'; -import { store } from '@/store.js'; +import { prefer } from '@/preferences.js'; class ReactionPicker { private src: Ref<HTMLElement | null> = ref(null); @@ -21,7 +21,14 @@ class ReactionPicker { } public async init() { - const reactionsRef = store.r.reactions; + const reactionsRef = ref<string[]>([]); + + watch([prefer.r.emojiPaletteForReaction, prefer.r.emojiPalettes], () => { + reactionsRef.value = prefer.s.emojiPaletteForReaction == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForReaction)?.emojis ?? []; + }, { + immediate: true, + }); + await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { src: this.src, pinnedEmojis: reactionsRef, diff --git a/packages/frontend/src/utility/show-moved-dialog.ts b/packages/frontend/src/utility/show-moved-dialog.ts index 35b3ef79d8..db21b028cd 100644 --- a/packages/frontend/src/utility/show-moved-dialog.ts +++ b/packages/frontend/src/utility/show-moved-dialog.ts @@ -4,7 +4,7 @@ */ import * as os from '@/os.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; export function showMovedDialog() { diff --git a/packages/frontend/src/utility/upload.ts b/packages/frontend/src/utility/upload.ts index d105a318a7..eb3cbd3dfa 100644 --- a/packages/frontend/src/utility/upload.ts +++ b/packages/frontend/src/utility/upload.ts @@ -9,7 +9,7 @@ import { v4 as uuid } from 'uuid'; import { readAndCompressImage } from '@misskey-dev/browser-image-resizer'; import { apiUrl } from '@@/js/config.js'; import { getCompressionConfig } from './upload/compress-config.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { alert } from '@/os.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; diff --git a/packages/frontend/src/widgets/WidgetActivity.vue b/packages/frontend/src/widgets/WidgetActivity.vue index d911e71ab2..db03d1406c 100644 --- a/packages/frontend/src/widgets/WidgetActivity.vue +++ b/packages/frontend/src/widgets/WidgetActivity.vue @@ -28,7 +28,7 @@ import XChart from './WidgetActivity.chart.vue'; import type { GetFormResultType } from '@/utility/form.js'; import { misskeyApiGet } from '@/utility/misskey-api.js'; import MkContainer from '@/components/MkContainer.vue'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; const name = 'activity'; diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue index b49041158f..c46fd81466 100644 --- a/packages/frontend/src/widgets/WidgetAiscript.vue +++ b/packages/frontend/src/widgets/WidgetAiscript.vue @@ -27,7 +27,7 @@ import type { GetFormResultType } from '@/utility/form.js'; import * as os from '@/os.js'; import MkContainer from '@/components/MkContainer.vue'; import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; const name = 'aiscript'; diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue index fb9dea1847..429b0e0ffb 100644 --- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue +++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue @@ -21,7 +21,7 @@ import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps import type { GetFormResultType } from '@/utility/form.js'; import * as os from '@/os.js'; import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import MkAsUi from '@/components/MkAsUi.vue'; import MkContainer from '@/components/MkContainer.vue'; import { registerAsUiLib } from '@/aiscript/ui.js'; diff --git a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue index 8c7507ef44..be11a26917 100644 --- a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue +++ b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue @@ -33,7 +33,7 @@ import MkContainer from '@/components/MkContainer.vue'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { infoImageUrl } from '@/instance.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; const name = i18n.ts._widgets.birthdayFollowings; diff --git a/packages/frontend/src/widgets/WidgetButton.vue b/packages/frontend/src/widgets/WidgetButton.vue index 3f0f9eb9fd..4afe735a22 100644 --- a/packages/frontend/src/widgets/WidgetButton.vue +++ b/packages/frontend/src/widgets/WidgetButton.vue @@ -18,7 +18,7 @@ import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps import type { GetFormResultType } from '@/utility/form.js'; import * as os from '@/os.js'; import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import MkButton from '@/components/MkButton.vue'; const name = 'button'; diff --git a/packages/frontend/src/widgets/WidgetProfile.vue b/packages/frontend/src/widgets/WidgetProfile.vue index c86d1c9653..3fe8378a39 100644 --- a/packages/frontend/src/widgets/WidgetProfile.vue +++ b/packages/frontend/src/widgets/WidgetProfile.vue @@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { useWidgetPropsManager } from './widget.js'; import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import type { GetFormResultType } from '@/utility/form.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; import { userPage } from '@/filters/user.js'; const name = 'profile'; diff --git a/packages/frontend/test/aiscript/api.test.ts b/packages/frontend/test/aiscript/api.test.ts index a569c0fa51..ad24625b96 100644 --- a/packages/frontend/test/aiscript/api.test.ts +++ b/packages/frontend/test/aiscript/api.test.ts @@ -33,11 +33,11 @@ async function exe(script: string): Promise<values.Value[]> { return outputs; } -let $iMock = vi.hoisted<Partial<typeof import('@/account.js').$i> | null >( +let $iMock = vi.hoisted<Partial<typeof import('@/i.js').$i> | null >( () => null ); -vi.mock('@/account.js', () => { +vi.mock('@/i.js', () => { return { get $i() { return $iMock; diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 945ea588b4..5b7a4ca610 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2025.3.2-alpha.9", + "version": "2025.3.2-alpha.10", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js",