From 41b11562da18d40cc64d6657619766995e1cc1db Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 15 Jan 2025 19:10:28 +0900 Subject: [PATCH 01/18] =?UTF-8?q?enhance(frontend):=20=E6=8A=95=E7=A8=BF?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0=E3=81=AE=E7=B5=B5=E6=96=87?= =?UTF-8?q?=E5=AD=97=E3=83=94=E3=83=83=E3=82=AB=E3=83=BC=E3=81=AB=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E3=81=97=E3=81=9F=E3=82=A6=E3=82=A3=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=82=A6=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 8 +- locales/ja-JP.yml | 2 +- .../MkEmojiPickerWindow.stories.impl.ts | 7 ++ .../src/components/MkEmojiPickerWindow.vue | 72 +++++++++++++++++ .../frontend/src/components/MkPostForm.vue | 17 ++-- .../src/components/MkPostFormDialog.vue | 9 ++- packages/frontend/src/events.ts | 1 + .../src/pages/settings/emoji-picker.vue | 11 ++- packages/frontend/src/scripts/emoji-picker.ts | 81 +++++++++++++------ packages/frontend/src/store.ts | 4 +- 10 files changed, 171 insertions(+), 41 deletions(-) create mode 100644 packages/frontend/src/components/MkEmojiPickerWindow.stories.impl.ts create mode 100644 packages/frontend/src/components/MkEmojiPickerWindow.vue diff --git a/locales/index.d.ts b/locales/index.d.ts index 8bd0e647b1..904485d8cf 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -126,6 +126,10 @@ export interface Locale extends ILocale { * ウィンドウで開く */ "openInWindow": string; + /** + * ウィンドウ + */ + "window": string; /** * プロフィール */ @@ -3182,10 +3186,6 @@ export interface Locale extends ILocale { * 設定はページリロード後に反映されます。 */ "reloadToApplySetting": string; - /** - * 反映には再起動が必要です。 - */ - "needReloadToApply": string; /** * タイトルバーを表示する */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 2a8fd94522..bad5f831fc 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -27,6 +27,7 @@ notificationSettings: "通知の設定" basicSettings: "基本設定" otherSettings: "その他の設定" openInWindow: "ウィンドウで開く" +window: "ウィンドウ" profile: "プロフィール" timeline: "タイムライン" noAccountDescription: "自己紹介はありません" @@ -791,7 +792,6 @@ center: "中央" wide: "広い" narrow: "狭い" reloadToApplySetting: "設定はページリロード後に反映されます。" -needReloadToApply: "反映には再起動が必要です。" showTitlebar: "タイトルバーを表示する" clearCache: "キャッシュをクリア" onlineUsersCount: "{n}人がオンライン" diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.stories.impl.ts b/packages/frontend/src/components/MkEmojiPickerWindow.stories.impl.ts new file mode 100644 index 0000000000..67dd0f8d6f --- /dev/null +++ b/packages/frontend/src/components/MkEmojiPickerWindow.stories.impl.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import MkEmojiPickerWindow from './MkEmojiPickerWindow.vue'; +void MkEmojiPickerWindow; diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue new file mode 100644 index 0000000000..cd9b3cbd9d --- /dev/null +++ b/packages/frontend/src/components/MkEmojiPickerWindow.vue @@ -0,0 +1,72 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 5d0716ef37..71ca3a8830 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -100,7 +100,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index e2bf6dbff8..8d39c9fab0 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -1104,9 +1104,7 @@ onMounted(() => { }); onBeforeUnmount(() => { - // MkPostFormDialogでも発火しているが、Dialogではない場合は呼ばれないためこちらでも呼ぶ必要がある - // なのでDialogの場合は2回発火されるが、ウィンドウを閉じる指示のため悪影響はない - globalEvents.emit('requestCloseEmojiPickerWindow'); + emoijPicker.closeWindow(); }); defineExpose({ diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue index 9e85d9b548..970af83440 100644 --- a/packages/frontend/src/components/MkPostFormDialog.vue +++ b/packages/frontend/src/components/MkPostFormDialog.vue @@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { useTemplateRef } from 'vue'; import MkModal from '@/components/MkModal.vue'; import MkPostForm from '@/components/MkPostForm.vue'; -import { globalEvents } from '@/events.js'; +import { emojiPicker } from '@/utility/emoji-picker.js'; import type { PostFormProps } from '@/types/post-form.js'; const props = withDefaults(defineProps @@ -58,7 +58,7 @@ import MkLoading from '@/components/global/MkLoading.vue'; const emit = defineEmits<{ (ev: 'done', value: Misskey.entities.Role[]), (ev: 'close'), - (ev: 'dispose'), + (ev: 'closed'), }>(); const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/events.ts b/packages/frontend/src/events.ts index 6a57ecce58..dfd3d4120c 100644 --- a/packages/frontend/src/events.ts +++ b/packages/frontend/src/events.ts @@ -10,5 +10,4 @@ export const globalEvents = new EventEmitter<{ themeChanging: () => void; themeChanged: () => void; clientNotification: (notification: Misskey.entities.Notification) => void; - requestCloseEmojiPickerWindow: () => void; }>(); diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 8e4c97e59f..bd0a77087c 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -5,10 +5,10 @@ // TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する -import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue'; +import { markRaw, ref, shallowRef, defineAsyncComponent, nextTick } from 'vue'; import { EventEmitter } from 'eventemitter3'; import * as Misskey from 'misskey-js'; -import type { Component, Ref } from 'vue'; +import type { Component, Ref, ShallowRef } from 'vue'; import type { ComponentProps as CP } from 'vue-component-type-helpers'; import type { Form, GetFormResultType } from '@/utility/form.js'; import type { MenuItem } from '@/types/menu.js'; @@ -141,6 +141,7 @@ let popupIdCount = 0; export const popups = ref<{ id: number; component: Component; + componentRef: ShallowRef; props: Record; events: Record; }[]>([]); @@ -178,13 +179,21 @@ type EmitsExtractor = { [K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize : K extends string ? never : K]: T[K]; }; -export function popup( +export function popup< + T extends Component, + TI extends T extends new (...args: unknown[]) => infer I ? I : T, +>( component: T, props: ComponentProps, events: ComponentEmit = {} as ComponentEmit, -): { dispose: () => void } { +): { + dispose: () => void; + componentRef: ShallowRef; +} { markRaw(component); + const componentRef = shallowRef(null); + const id = ++popupIdCount; const dispose = () => { // このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ? @@ -194,6 +203,7 @@ export function popup( }; const state = { component, + componentRef, props, events, id, @@ -203,6 +213,7 @@ export function popup( return { dispose, + componentRef, }; } @@ -627,14 +638,17 @@ export async function selectRole(params: { { canceled: false; result: Misskey.entities.Role[] } > { return new Promise((resolve) => { - popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, { + const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, { done: roles => { resolve({ canceled: false, result: roles }); }, close: () => { resolve({ canceled: true, result: undefined }); }, - }, 'dispose'); + closed: () => { + dispose(); + }, + }); }); } diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index a39a4ee86b..a51504b274 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only :is="popup.component" v-for="popup in popups" :key="popup.id" + :ref="(el: Component | null) => popup.componentRef = el" v-bind="popup.props" v-on="popup.events" /> diff --git a/packages/frontend/src/utility/emoji-picker.ts b/packages/frontend/src/utility/emoji-picker.ts index c7d8858a8b..7bbc5bd0a5 100644 --- a/packages/frontend/src/utility/emoji-picker.ts +++ b/packages/frontend/src/utility/emoji-picker.ts @@ -3,8 +3,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineAsyncComponent, ref, watch } from 'vue'; -import type { Ref } from 'vue'; +import { defineAsyncComponent, ref, shallowRef, watch } from 'vue'; +import type { Ref, ShallowRef } from 'vue'; +import type MkEmojiPickerWindow_TypeOnly from '@/components/MkEmojiPickerWindow.vue'; import { popup } from '@/os.js'; import { prefer } from '@/preferences.js'; @@ -18,6 +19,7 @@ class EmojiPicker { private src: Ref = ref(null); private isWindow: boolean = false; + private windowComponentEl: ShallowRef | null> = shallowRef(null); private windowShowing: boolean = false; private dialogShowing = ref(false); @@ -73,7 +75,7 @@ class EmojiPicker { if (this.isWindow) { if (this.windowShowing) return; this.windowShowing = true; - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerWindow.vue')), { + const { dispose, componentRef } = popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerWindow.vue')), { src: opts.src, pinnedEmojis: this.emojisRef, asReactionPicker: false, @@ -87,6 +89,7 @@ class EmojiPicker { dispose(); }, }); + this.windowComponentEl.value = componentRef.value; } else { this.src.value = opts.src; this.dialogShowing.value = true; @@ -94,6 +97,12 @@ class EmojiPicker { this.onClosed = opts.onClosed; } } + + public closeWindow() { + if (this.isWindow && this.windowComponentEl.value) { + this.windowComponentEl.value.close(); + } + } } export const emojiPicker = new EmojiPicker(); From 3207fb283136583f60bd03a4800b7e41082845e2 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 20 Mar 2025 16:01:20 +0900 Subject: [PATCH 14/18] fix --- packages/frontend/src/pages/settings/emoji-palette.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/settings/emoji-palette.vue b/packages/frontend/src/pages/settings/emoji-palette.vue index 29177f12ff..34edc9282f 100644 --- a/packages/frontend/src/pages/settings/emoji-palette.vue +++ b/packages/frontend/src/pages/settings/emoji-palette.vue @@ -144,7 +144,7 @@ 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 { reloadAsk } from '@/scripts/reload-ask.js'; +import { reloadAsk } from '@/utility/reload-ask.js'; const emojiPaletteForReaction = prefer.model('emojiPaletteForReaction'); const emojiPaletteForMain = prefer.model('emojiPaletteForMain'); const emojiPickerScale = prefer.model('emojiPickerScale'); From 39edab772ad7a7c11b1433c83c6066972b4b49f5 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: Tue, 1 Apr 2025 16:08:09 +0900 Subject: [PATCH 15/18] fix typo --- packages/frontend/src/components/MkPostForm.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index a703462ddc..43b8c5b9d9 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -1104,7 +1104,7 @@ onMounted(() => { }); onBeforeUnmount(() => { - emoijPicker.closeWindow(); + emojiPicker.closeWindow(); }); defineExpose({ From 039ac0be655adc600274a0e70f8ffd24e84e6b59 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 23 May 2025 17:22:40 +0900 Subject: [PATCH 16/18] migrate --- packages/frontend/src/components/MkPostForm.vue | 2 +- packages/frontend/src/pages/chat/room.form.vue | 10 +++++----- packages/frontend/src/pages/settings/emoji-palette.vue | 4 +++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index ce98078412..d4cd4c18aa 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -980,7 +980,7 @@ async function insertEmoji(ev: MouseEvent) { let pos = textareaEl.value?.selectionStart ?? 0; let posEnd = textareaEl.value?.selectionEnd ?? text.value.length; emojiPicker.show({ - src: target as HTMLElement, + anchorElement: target as HTMLElement, onChosen: emoji => { const textBefore = text.value.substring(0, pos); const textAfter = text.value.substring(posEnd); diff --git a/packages/frontend/src/pages/chat/room.form.vue b/packages/frontend/src/pages/chat/room.form.vue index 7e3be67230..d33a90233c 100644 --- a/packages/frontend/src/pages/chat/room.form.vue +++ b/packages/frontend/src/pages/chat/room.form.vue @@ -256,20 +256,20 @@ async function insertEmoji(ev: MouseEvent) { let pos = textareaEl.value?.selectionStart ?? 0; let posEnd = textareaEl.value?.selectionEnd ?? text.value.length; - emojiPicker.show( - target as HTMLElement, - emoji => { + emojiPicker.show({ + anchorElement: target as HTMLElement, + onChosen: (emoji) => { const textBefore = text.value.substring(0, pos); const textAfter = text.value.substring(posEnd); text.value = textBefore + emoji + textAfter; pos += emoji.length; posEnd += emoji.length; }, - () => { + onClosed: () => { textareaReadOnly.value = false; nextTick(() => focus()); }, - ); + }); } onMounted(() => { diff --git a/packages/frontend/src/pages/settings/emoji-palette.vue b/packages/frontend/src/pages/settings/emoji-palette.vue index 91057c5731..6e7ade85f5 100644 --- a/packages/frontend/src/pages/settings/emoji-palette.vue +++ b/packages/frontend/src/pages/settings/emoji-palette.vue @@ -223,7 +223,9 @@ function getHTMLElement(ev: MouseEvent): HTMLElement { } function previewPicker(ev: MouseEvent) { - emojiPicker.show(getHTMLElement(ev)); + emojiPicker.show({ + anchorElement: getHTMLElement(ev), + }); } watch([ From d88d03ae6875c649c9a30506eae9f112ac1efb74 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 23 May 2025 17:24:10 +0900 Subject: [PATCH 17/18] migrate --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89bacb0ab3..dbb354697e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ - Enhance: シンタックスハイライトのエンジンをJavaScriptベースのものに変更 - フロントエンドの読み込みサイズを軽量化しました - ほとんどの言語のハイライトは問題なく行えますが、互換性の問題により一部の言語が正常にハイライトできなくなる可能性があります。詳しくは https://shiki.style/references/engine-js-compat をご覧ください。 +- Enhance: 投稿フォームの絵文字ピッカーに独立したウィンドウを使用できるように + - リアクションピッカーと絵文字ピッカーで表示スタイルの設定が分離しました。絵文字ピッカーでのみウィンドウスタイルを使用可能です。 - Fix: "時計"ウィジェット(Clock)において、Transparent設定が有効でも、その背景が透過されない問題を修正 - Fix: 一定時間操作がなかったら動画プレイヤーのコントロールを隠すように @@ -113,8 +115,6 @@ - Enhance: テーマでページヘッダーの色を変更できるように - Enhance: スワイプでのタブ切り替えを強化 - Enhance: デザインのブラッシュアップ -- Enhance: 投稿フォームの絵文字ピッカーに独立したウィンドウを使用できるように - - リアクションピッカーと絵文字ピッカーで表示スタイルの設定が分離しました。絵文字ピッカーでのみウィンドウスタイルを使用可能です。 - Fix: ログアウトした際に処理が終了しない問題を修正 - Fix: 自動バックアップが設定されている環境でログアウト直前に設定をバックアップするように - Fix: フォルダを開いた状態でメニューからアップロードしてもルートフォルダにアップロードされる問題を修正 #15836 From 4c8b3d3ea853c0bb5558c03d580307877080461e Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sat, 9 Aug 2025 11:41:05 +0900 Subject: [PATCH 18/18] fix --- packages/frontend/src/pages/settings/emoji-palette.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/pages/settings/emoji-palette.vue b/packages/frontend/src/pages/settings/emoji-palette.vue index 80506b6466..199cfa8461 100644 --- a/packages/frontend/src/pages/settings/emoji-palette.vue +++ b/packages/frontend/src/pages/settings/emoji-palette.vue @@ -144,7 +144,8 @@ 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 { reloadAsk } from '@/utility/reload-ask.js'; +import { suggestReload } from '@/utility/reload-suggest.js'; + const emojiPaletteForReaction = prefer.model('emojiPaletteForReaction'); const emojiPaletteForMain = prefer.model('emojiPaletteForMain'); const emojiPickerScale = prefer.model('emojiPickerScale'); @@ -231,8 +232,8 @@ function previewPicker(ev: MouseEvent) { watch([ emojiPickerStyle, reactionPickerStyle, -], async () => { - await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); +], () => { + suggestReload(); }); definePage(() => ({