diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd333372a..44cec25a46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,13 @@ - Enhance: ドライブのファイル一覧で自動でもっと見るを利用可能に - Enhance: ウィジェットの表示設定をプレビューを見ながら行えるように - Enhance: ウィジェットの設定項目のラベルの多言語対応 +- Enhance: アカウント管理ページで、全てのアカウントから一括でログアウトできるように - Enhance: パフォーマンスの向上 - Fix: ドライブクリーナーでファイルを削除しても画面に反映されない問題を修正 #16061 - Fix: 非ログイン時にログインを求めるダイアログが表示された後にダイアログのぼかしが解除されず操作不能になることがある問題を修正 - Fix: ドライブのソートが「登録日(昇順)」の場合に正しく動作しない問題を修正 +- Fix: ログアウトボタンを押下するとすべてのアカウントからログアウトする問題を修正 +- Fix: アカウント管理ページで、アカウントの追加・削除を行ってもリストに反映されない問題を修正 - Fix: 高度なMFMのピッカーを使用する際の挙動を改善 - Fix: 管理画面でアーカイブ済のお知らせを表示した際にアクティブなお知らせが多い旨の警告が出る問題を修正 - Fix: ファイルタブのセンシティブメディアを開く際に確認ダイアログを出す設定が適用されない問題を修正 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e1464151e7..d39ff4ac82 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -34,6 +34,7 @@ noAccountDescription: "自己紹介はありません" login: "ログイン" loggingIn: "ログイン中" logout: "ログアウト" +logoutFromAll: "すべてのアカウントからログアウト" signup: "新規登録" uploading: "アップロード中" save: "保存" @@ -991,6 +992,7 @@ numberOfPageCache: "ページキャッシュ数" numberOfPageCacheDescription: "多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。" logoutConfirm: "ログアウトしますか?" logoutWillClearClientData: "ログアウトするとクライアントの設定情報がブラウザから消去されます。再ログイン時に設定情報を復元できるようにするためには、設定の自動バックアップを有効にしてください。" +logoutFromOtherAccountConfirm: "{username}からログアウトしますか?" lastActiveDate: "最終利用日時" statusbar: "ステータスバー" pleaseSelect: "選択してください" diff --git a/packages/frontend/src/accounts.ts b/packages/frontend/src/accounts.ts index 862ef4e113..f1dd5f622c 100644 --- a/packages/frontend/src/accounts.ts +++ b/packages/frontend/src/accounts.ts @@ -19,13 +19,15 @@ import { signout } from '@/signout.js'; type AccountWithToken = Misskey.entities.MeDetailed & { token: string }; -export async function getAccounts(): Promise<{ +export type AccountData = { host: string; id: Misskey.entities.User['id']; username: Misskey.entities.User['username']; user?: Misskey.entities.MeDetailed | null; token: string | null; -}[]> { +}; + +export async function getAccounts(): Promise { const tokens = store.s.accountTokens; const accountInfos = store.s.accountInfos; const accounts = prefer.s.accounts; @@ -33,8 +35,8 @@ export async function getAccounts(): Promise<{ host, id: user.id, username: user.username, - user: accountInfos[host + '/' + user.id], - token: tokens[host + '/' + user.id] ?? null, + user: accountInfos[`${host}/${user.id}`], + token: tokens[`${host}/${user.id}`] ?? null, })); } @@ -53,10 +55,15 @@ export async function removeAccount(host: string, id: AccountWithToken['id']) { const accountInfos = JSON.parse(JSON.stringify(store.s.accountInfos)); delete accountInfos[host + '/' + id]; store.set('accountInfos', accountInfos); - prefer.commit('accounts', prefer.s.accounts.filter(x => x[0] !== host || x[1].id !== id)); } +export async function removeAccountAssociatedData(host: string, id: AccountWithToken['id']) { + // 設定・状態を削除 + prefer.clearAccountSettingsFromDevice(host, id); + await store.clearAccountDataFromDevice(id); +} + const isAccountDeleted = Symbol('isAccountDeleted'); function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Promise { @@ -162,14 +169,36 @@ export async function refreshCurrentAccount() { }); } -export async function login(token: AccountWithToken['token'], redirect?: string) { +export async function refreshAccounts() { + const accounts = await getAccounts(); + for (const account of accounts) { + if (account.host === host && account.id === $i?.id) { + await refreshCurrentAccount(); + } else if (account.token) { + try { + const user = await fetchAccount(account.token, account.id); + store.set('accountInfos', { ...store.s.accountInfos, [account.host + '/' + account.id]: user }); + } catch (e) { + if (e === isAccountDeleted) { + await removeAccount(account.host, account.id); + await removeAccountAssociatedData(account.host, account.id); + } + } + } + } +} + +export async function login(token: AccountWithToken['token'], redirect?: string, showWaiting = true) { const showing = ref(true); - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkWaitingDialog.vue')), { - success: false, - showing: showing, - }, { - closed: () => dispose(), - }); + + if (showWaiting) { + 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; @@ -195,7 +224,7 @@ export async function login(token: AccountWithToken['token'], redirect?: string) } export async function switchAccount(host: string, id: string) { - const token = store.s.accountTokens[host + '/' + id]; + const token = store.s.accountTokens[`${host}/${id}`]; if (token) { login(token); } else { diff --git a/packages/frontend/src/components/MkUserCardMini.vue b/packages/frontend/src/components/MkUserCardMini.vue index 1fd43bd6e4..c1200d8232 100644 --- a/packages/frontend/src/components/MkUserCardMini.vue +++ b/packages/frontend/src/components/MkUserCardMini.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + @{{ acct(user) }}
diff --git a/packages/frontend/src/lib/pizzax.ts b/packages/frontend/src/lib/pizzax.ts index 0dd8a82957..51daa83a9e 100644 --- a/packages/frontend/src/lib/pizzax.ts +++ b/packages/frontend/src/lib/pizzax.ts @@ -12,7 +12,7 @@ import { BroadcastChannel } from 'broadcast-channel'; import type { Ref } from 'vue'; import { $i } from '@/i.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { get, set } from '@/utility/idb-proxy.js'; +import { get, set, delMany } from '@/utility/idb-proxy.js'; import { store } from '@/store.js'; import { deepClone } from '@/utility/clone.js'; import { deepMerge } from '@/utility/merge.js'; @@ -222,6 +222,18 @@ export class Pizzax { return this.def[key].default; } + /** 現在のアカウントに紐づくデータをデバイスから削除します */ + public async clearAccountDataFromDevice(id = $i?.id) { + if (id == null) return; + + const deviceAccountStateKey = `pizzax::${this.key}::${id}` satisfies typeof this.deviceAccountStateKeyName; + const registryCacheKey = `pizzax::${this.key}::cache::${id}` satisfies typeof this.registryCacheKeyName; + + await this.addIdbSetJob(async () => { + await delMany([deviceAccountStateKey, registryCacheKey]); + }); + } + /** * 特定のキーの、簡易的なcomputed refを作ります * 主にvue上で設定コントロールのmodelとして使う用 diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue index 55a81bbf38..0ef69f7540 100644 --- a/packages/frontend/src/pages/settings/accounts.vue +++ b/packages/frontend/src/pages/settings/accounts.vue @@ -8,11 +8,23 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.addAccount }} - + {{ i18n.ts.reloadAccountsList }} + {{ i18n.ts.logoutFromAll }}
@@ -20,36 +32,73 @@ SPDX-License-Identifier: AGPL-3.0-only