From 076cc953e2bcd9f7335e2d9799cdf902829816cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:20:33 +0900 Subject: [PATCH 01/22] =?UTF-8?q?enhance(frontend):=20=E5=A4=96=E9=83=A8?= =?UTF-8?q?=E3=82=A2=E3=83=97=E3=83=AA=E8=AA=8D=E8=A8=BC=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=AE=E6=94=B9=E8=89=AF=20(#14828)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(frontend): 外部アプリ認証画面の改良 * :art: * lint * Update Changelog * indent * lint * enhance: miauthのリダイレクト先をUI内でも表示するように * :art: * fix * fix --- CHANGELOG.md | 3 + locales/index.d.ts | 16 + locales/ja-JP.yml | 4 + packages/frontend/src/_boot_.ts | 2 +- packages/frontend/src/account.ts | 70 ++- .../components/MkAuthConfirm.stories.impl.ts | 7 + .../frontend/src/components/MkAuthConfirm.vue | 450 ++++++++++++++++++ .../frontend/src/components/MkModalWindow.vue | 10 +- .../src/components/MkSignupDialog.vue | 10 +- packages/frontend/src/pages/miauth.vue | 145 +++--- packages/frontend/src/pages/oauth.vue | 111 +++-- .../frontend/src/pages/settings/accounts.vue | 20 +- 12 files changed, 697 insertions(+), 151 deletions(-) create mode 100644 packages/frontend/src/components/MkAuthConfirm.stories.impl.ts create mode 100644 packages/frontend/src/components/MkAuthConfirm.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index c85dc00011..6e5747f3d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ - Enhance: ドライブでソートができるように - Enhance: 「単なるラッキー」の取得条件を変更 - Enhance: 投稿フォームでEscキーを押したときIME入力中ならフォームを閉じないように( #10866 ) +- Enhance: MiAuth, OAuthの認可画面の改善 + - どのアカウントで認証しようとしているのかがわかるように + - 認証するアカウントを切り替えられるように - Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正 - Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正 (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/768) diff --git a/locales/index.d.ts b/locales/index.d.ts index 8350297a79..80adf69232 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5210,6 +5210,10 @@ export interface Locale extends ILocale { * ロックダウン */ "lockdown": string; + /** + * アカウントを選択してください + */ + "pleaseSelectAccount": string; "_accountSettings": { /** * コンテンツの表示にログインを必須にする @@ -8448,14 +8452,26 @@ export interface Locale extends ILocale { * アプリケーションに戻っています */ "callback": string; + /** + * アクセスを許可しました + */ + "accepted": string; /** * アクセスを拒否しました */ "denied": string; + /** + * 以下のユーザーとして操作しています + */ + "scopeUser": string; /** * アプリケーションにアクセス許可を与えるには、ログインが必要です。 */ "pleaseLogin": string; + /** + * アクセスを許可すると、自動で以下のURLに遷移します + */ + "byClickingYouWillBeRedirectedToThisUrl": string; }; "_antennaSources": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 93ed879a08..d545425cbd 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1298,6 +1298,7 @@ yourNameContainsProhibitedWords: "変更しようとした名前に禁止され yourNameContainsProhibitedWordsDescription: "名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。" thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者により、表示にはログインが必要と設定されています" lockdown: "ロックダウン" +pleaseSelectAccount: "アカウントを選択してください" _accountSettings: requireSigninToViewContents: "コンテンツの表示にログインを必須にする" @@ -2217,8 +2218,11 @@ _auth: permissionAsk: "このアプリは次の権限を要求しています" pleaseGoBack: "アプリケーションに戻ってやっていってください" callback: "アプリケーションに戻っています" + accepted: "アクセスを許可しました" denied: "アクセスを拒否しました" + scopeUser: "以下のユーザーとして操作しています" pleaseLogin: "アプリケーションにアクセス許可を与えるには、ログインが必要です。" + byClickingYouWillBeRedirectedToThisUrl: "アクセスを許可すると、自動で以下のURLに遷移します" _antennaSources: all: "全てのノート" diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts index 13a97e433c..c90cc6bdd0 100644 --- a/packages/frontend/src/_boot_.ts +++ b/packages/frontend/src/_boot_.ts @@ -12,7 +12,7 @@ import '@/style.scss'; import { mainBoot } from '@/boot/main-boot.js'; import { subBoot } from '@/boot/sub-boot.js'; -const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete']; +const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete']; if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) { subBoot(); diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index f5a74a0581..36186ecac1 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -235,26 +235,6 @@ export async function openAccountMenu(opts: { }, ev: MouseEvent) { if (!$i) return; - function showSigninDialog() { - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, { - done: (res: Misskey.entities.SigninFlowResponse & { finished: true }) => { - addAccount(res.id, res.i); - success(); - }, - closed: () => dispose(), - }); - } - - function createAccount() { - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, { - done: (res: Misskey.entities.SignupResponse) => { - addAccount(res.id, res.token); - switchAccountWithToken(res.token); - }, - closed: () => dispose(), - }); - } - async function switchAccount(account: Misskey.entities.UserDetailed) { const storedAccounts = await getAccounts(); const found = storedAccounts.find(x => x.id === account.id); @@ -323,10 +303,22 @@ export async function openAccountMenu(opts: { text: i18n.ts.addAccount, children: [{ text: i18n.ts.existingAccount, - action: () => { showSigninDialog(); }, + action: () => { + getAccountWithSigninDialog().then(res => { + if (res != null) { + success(); + } + }); + }, }, { text: i18n.ts.createAccount, - action: () => { createAccount(); }, + action: () => { + getAccountWithSignupDialog().then(res => { + if (res != null) { + switchAccountWithToken(res.token); + } + }); + }, }], }, { type: 'link', @@ -347,6 +339,40 @@ export async function openAccountMenu(opts: { }); } +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/components/MkAuthConfirm.stories.impl.ts b/packages/frontend/src/components/MkAuthConfirm.stories.impl.ts new file mode 100644 index 0000000000..0adc44e204 --- /dev/null +++ b/packages/frontend/src/components/MkAuthConfirm.stories.impl.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import MkAuthConfirm from './MkAuthConfirm.vue'; +void MkAuthConfirm; diff --git a/packages/frontend/src/components/MkAuthConfirm.vue b/packages/frontend/src/components/MkAuthConfirm.vue new file mode 100644 index 0000000000..f5f6d7f6cc --- /dev/null +++ b/packages/frontend/src/components/MkAuthConfirm.vue @@ -0,0 +1,450 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue index fe9e1ce088..f06cfffee4 100644 --- a/packages/frontend/src/components/MkModalWindow.vue +++ b/packages/frontend/src/components/MkModalWindow.vue @@ -26,11 +26,11 @@ import { onMounted, onUnmounted, shallowRef, ref } from 'vue'; import MkModal from './MkModal.vue'; const props = withDefaults(defineProps<{ - withOkButton: boolean; - withCloseButton: boolean; - okButtonDisabled: boolean; - width: number; - height: number; + withOkButton?: boolean; + withCloseButton?: boolean; + okButtonDisabled?: boolean; + width?: number; + height?: number; }>(), { withOkButton: false, withCloseButton: true, diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue index f240e6dc46..4f75a36fbe 100644 --- a/packages/frontend/src/components/MkSignupDialog.vue +++ b/packages/frontend/src/components/MkSignupDialog.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="dialog" :width="500" :height="600" - @close="dialog?.close()" + @close="onClose" @closed="$emit('closed')" > @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only :leaveToClass="$style.transition_x_leaveTo" > diff --git a/packages/frontend/src/components/MkAuthConfirm.vue b/packages/frontend/src/components/MkAuthConfirm.vue index f5f6d7f6cc..f78d2d38f0 100644 --- a/packages/frontend/src/components/MkAuthConfirm.vue +++ b/packages/frontend/src/components/MkAuthConfirm.vue @@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.pleaseSelectAccount }}
-
+
{{ i18n.ts.selectAccount }}
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index 4777da2848..02c84df447 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -58,7 +58,7 @@ const windowRouter = routerFactory(props.initialPath); const contents = shallowRef(null); const pageMetadata = ref(null); const windowEl = shallowRef>(); -const history = ref<{ path: string; key: any; }[]>([{ +const history = ref<{ path: string; key: string; }[]>([{ path: windowRouter.getCurrentPath(), key: windowRouter.getCurrentKey(), }]); diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue index 26c251a8d2..df664e49f7 100644 --- a/packages/frontend/src/components/MkPopupMenu.vue +++ b/packages/frontend/src/components/MkPopupMenu.vue @@ -19,7 +19,7 @@ defineProps<{ items: MenuItem[]; align?: 'center' | string; width?: number; - src?: any; + src?: HTMLElement | null; returnFocusTo?: HTMLElement | null; }>(); diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index b6b80082d3..f2fe048449 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -129,25 +129,13 @@ import { miLocalStorage } from '@/local-storage.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { emojiPicker } from '@/scripts/emoji-picker.js'; import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js'; +import type { PostFormProps } from '@/types/post-form.js'; const $i = signinRequired(); const modal = inject('modal'); -const props = withDefaults(defineProps<{ - reply?: Misskey.entities.Note; - renote?: Misskey.entities.Note; - channel?: Misskey.entities.Channel; // TODO - mention?: Misskey.entities.User; - specified?: Misskey.entities.UserDetailed; - initialText?: string; - initialCw?: string; - initialVisibility?: (typeof Misskey.noteVisibilities)[number]; - initialFiles?: Misskey.entities.DriveFile[]; - initialLocalOnly?: boolean; - initialVisibleUsers?: Misskey.entities.UserDetailed[]; - initialNote?: Misskey.entities.Note; - instant?: boolean; +const props = withDefaults(defineProps { - if (typeof key !== 'string') return; + }, (key, value) => { + if (typeof key !== 'string' || typeof value !== 'string') return; if (key === 'text') { text.value = value; } if (key === 'cw') { useCw.value = value !== null; cw.value = value; } }); diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue index ee7038df64..56e026aa3c 100644 --- a/packages/frontend/src/components/MkPostFormAttaches.vue +++ b/packages/frontend/src/components/MkPostFormAttaches.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only