From 0227148c8969c549e17d5c56114ec66127932347 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: Mon, 27 Apr 2026 10:40:12 +0900 Subject: [PATCH 1/4] Update CHANGELOG for #17347 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26b0258121..6889f16b75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Client - Enhance: チャンネル指定リノートでリノート先のチャンネルに移動できるように +- Enhance: ベータ版でのアップデート時のダイアログの更新情報リンクをGitHubのReleasesページに遷移するようにし、正しく閲覧できるように - Fix: 一部のページ内リンクが正しく動作しない問題を修正 - Fix: ドライブへの画像アップロード時にファイル名の変更が無視される不具合を修正 - Fix: 連合が無効化されたサーバーで一部の設定項目が空欄で表示される問題を修正 From 985de915b3f0414e547753c4856478fea6ac542e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:17:25 +0900 Subject: [PATCH 2/4] fix(deps): update dependency sanitize-html to v2.17.3 [security] (#17319) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/frontend/package.json | 2 +- pnpm-lock.yaml | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index c7f3f3da05..6404b2c10a 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -61,7 +61,7 @@ "punycode.js": "2.3.1", "qr-code-styling": "1.9.2", "qr-scanner": "1.4.2", - "sanitize-html": "2.17.2", + "sanitize-html": "2.17.3", "shiki": "4.0.2", "textarea-caret": "3.1.0", "three": "0.183.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9aa148f6c5..2d53130c3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -744,8 +744,8 @@ importers: specifier: 1.4.2 version: 1.4.2 sanitize-html: - specifier: 2.17.2 - version: 2.17.2 + specifier: 2.17.3 + version: 2.17.3 shiki: specifier: 4.0.2 version: 4.0.2 @@ -8928,9 +8928,6 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sanitize-html@2.17.2: - resolution: {integrity: sha512-EnffJUl46VE9uvZ0XeWzObHLurClLlT12gsOk1cHyP2Ol1P0BnBnsXmShlBmWVJM+dKieQI68R0tsPY5m/B+Jg==} - sanitize-html@2.17.3: resolution: {integrity: sha512-Kn4srCAo2+wZyvCNKCSyB2g8RQ8IkX/gQs2uqoSRNu5t9I2qvUyAVvRDiFUVAiX3N3PNuwStY0eNr+ooBHVWEg==} @@ -10238,6 +10235,9 @@ packages: vue-component-type-helpers@3.2.6: resolution: {integrity: sha512-O02tnvIfOQVmnvoWwuSydwRoHjZVt8UEBR+2p4rT35p8GAy5VTlWP8o5qXfJR/GWCN0nVZoYWsVUvx2jwgdBmQ==} + vue-component-type-helpers@3.2.7: + resolution: {integrity: sha512-+gPp5YGmhfsj1IN+xUo7y0fb4clfnOiiUA39y07yW1VzCRjzVgwLbtmdWlghh7mXrPsEaYc7rrIir/HT6C8vYQ==} + vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} @@ -13614,7 +13614,7 @@ snapshots: storybook: 10.3.5(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(utf-8-validate@6.0.6) type-fest: 2.19.0 vue: 3.5.32(typescript@5.9.3) - vue-component-type-helpers: 3.2.6 + vue-component-type-helpers: 3.2.7 '@stylistic/eslint-plugin@5.5.0(eslint@9.39.4)': dependencies: @@ -19275,15 +19275,6 @@ snapshots: safer-buffer@2.1.2: {} - sanitize-html@2.17.2: - dependencies: - deepmerge: 4.3.1 - escape-string-regexp: 4.0.0 - htmlparser2: 10.1.0 - is-plain-object: 5.0.0 - parse-srcset: 1.0.2 - postcss: 8.5.9 - sanitize-html@2.17.3: dependencies: deepmerge: 4.3.1 @@ -20542,6 +20533,8 @@ snapshots: vue-component-type-helpers@3.2.6: {} + vue-component-type-helpers@3.2.7: {} + vue-demi@0.14.10(vue@3.5.32(typescript@5.9.3)): dependencies: vue: 3.5.32(typescript@5.9.3) From 973b5b50a950405b76696431c38418b40787ed16 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: Thu, 30 Apr 2026 11:29:23 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix(frontend):=20=E3=81=BE=E3=82=8C?= =?UTF-8?q?=E3=81=AB=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=83=BB=E7=B5=B5=E6=96=87=E5=AD=97=E3=83=94=E3=83=83=E3=82=AB?= =?UTF-8?q?=E3=83=BC=E3=81=8C=E5=8B=95=E4=BD=9C=E3=81=97=E3=81=AA=E3=81=8F?= =?UTF-8?q?=E3=81=AA=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=20(#17349)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "fix(frontend): popupのりアクティビティがチャンクをまたいで切れる事がある問題を修正" This reverts commit 0a93f526dde551c9846b7b0d3596c66bbcd4d5e5. * fix: iOS PWA でリアクション・絵文字ピッカーが動作しない問題を修正 Agent-Logs-Url: https://github.com/lqvp/misskey-tempura/sessions/44526368-0e6a-4a94-8991-fcdc094d2b96 Co-authored-by: lqvp <183242690+lqvp@users.noreply.github.com> * refactor * fix --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lqvp <183242690+lqvp@users.noreply.github.com> --- packages/frontend/src/os.ts | 48 ++------- packages/frontend/src/utility/emoji-picker.ts | 96 ++++++++++------- .../frontend/src/utility/reaction-picker.ts | 100 +++++++++++------- 3 files changed, 133 insertions(+), 111 deletions(-) diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index c56631fe3c..9d199738f6 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -5,9 +5,9 @@ // TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する -import { markRaw, ref, defineAsyncComponent, nextTick, effectScope, isRef, shallowReactive, watch } from 'vue'; +import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue'; import * as Misskey from 'misskey-js'; -import type { Component, MaybeRef, ShallowReactive } from 'vue'; +import type { Component, MaybeRef } from 'vue'; import type { ComponentEmit, ComponentProps as CP } from 'vue-component-type-helpers'; import type { Form, GetFormResultType } from '@/utility/form.js'; import type { MenuItem } from '@/types/menu.js'; @@ -145,7 +145,7 @@ let popupIdCount = 0; export const popups = ref<{ id: number; component: Component; - props: ShallowReactive>; + props: Record; events: Record; }[]>([]); @@ -183,32 +183,6 @@ type ComponentEmitsObject void; }>; -// ref をそのまま保持せず popup 側の reactive props に同期するようにして、スコープをまたいでリアクティビティが切れるのを防止する -function normalizePopupProps>(props: T): { - resolvedProps: ShallowReactive; - stopSync: () => void; -} { - const resolvedProps = shallowReactive({} as T) as T; // shallowReactiveの返り値はreadonlyだが、実際には書き換えるので元の型で扱う - const scope = effectScope(); - - scope.run(() => { - for (const [key, value] of Object.entries(props)) { - if (isRef(value)) { - watch(value, (resolvedValue) => { - resolvedProps[key as keyof T] = resolvedValue as T[keyof T]; - }, { immediate: true }); - } else { - resolvedProps[key as keyof T] = value; - } - } - }); - - return { - resolvedProps: resolvedProps as ShallowReactive, - stopSync: () => scope.stop(), - }; -} - // NOTE: ジェネリック型つきのコンポーネントでは、emitsの型推論がうまく働かない(型変数を取り出すことはできないため) // NOTE: emitsがOverloadToUnionで対応しているオーバーロードの数を超える場合は、OverloadToUnionの個数を増やせばOK export function popup( @@ -219,24 +193,20 @@ export function popup( markRaw(component); const id = ++popupIdCount; - const { resolvedProps, stopSync } = normalizePopupProps(props); - let disposed = false; const dispose = () => { - if (disposed) return; - disposed = true; - stopSync(); - + // このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ? nextTick(() => { popups.value = popups.value.filter(p => p.id !== id); }); }; - - popups.value.push({ + const state = { component, - props: resolvedProps, + props, events, id, - }); + }; + + popups.value.push(state); return { dispose, diff --git a/packages/frontend/src/utility/emoji-picker.ts b/packages/frontend/src/utility/emoji-picker.ts index c663776de3..2ec8bb9779 100644 --- a/packages/frontend/src/utility/emoji-picker.ts +++ b/packages/frontend/src/utility/emoji-picker.ts @@ -3,9 +3,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineAsyncComponent, ref, watch } from 'vue'; -import type { Ref } from 'vue'; -import { popup } from '@/os.js'; +import { markRaw, shallowRef, ref, watch } from 'vue'; +import type MkEmojiPickerDialog__TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue'; +import { popup, popupAsyncWithDialog } from '@/os.js'; import { prefer } from '@/preferences.js'; /** @@ -15,53 +15,77 @@ import { prefer } from '@/preferences.js'; * 一度表示したダイアログを連続で使用できることが望ましいシーンでの利用が想定される。 */ class EmojiPicker { - private anchorElement: Ref = ref(null); - private manualShowing = ref(false); - private onChosen?: (emoji: string) => void; - private onClosed?: () => void; + private loadedComponent: typeof MkEmojiPickerDialog__TypeReferenceOnly | null = null; + private emojisRef = ref([]); constructor() { // nop } - public async init() { - const emojisRef = ref([]); - - 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, + public init() { + // コンポーネントをプリロードしてキャッシュしておく。 + // iOS PWA では await を挟むとユーザーアクティベーションが失われfocusが効かなくなるため、 + // show() 呼び出し時には同期的に popup() できるよう事前にコンポーネントを解決しておく。 + import('@/components/MkEmojiPickerDialog.vue').then(m => { + this.loadedComponent = markRaw(m.default); + }).catch(err => { + console.error('[EmojiPicker] Failed to preload MkEmojiPickerDialog:', err); }); - await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { - anchorElement: this.anchorElement, - pinnedEmojis: emojisRef, - asReactionPicker: false, - manualShowing: this.manualShowing, - choseAndClose: false, + watch([prefer.r.emojiPaletteForMain, prefer.r.emojiPalettes], () => { + this.emojisRef.value = prefer.s.emojiPaletteForMain == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForMain)?.emojis ?? []; }, { - done: emoji => { - if (this.onChosen) this.onChosen(emoji); - }, - close: () => { - this.manualShowing.value = false; - }, - closed: () => { - this.anchorElement.value = null; - if (this.onClosed) this.onClosed(); - }, + immediate: true, }); } public show( anchorElement: HTMLElement, - onChosen?: EmojiPicker['onChosen'], - onClosed?: EmojiPicker['onClosed'], + onChosen?: (emoji: string) => void, + onClosed?: () => void, ) { - this.anchorElement.value = anchorElement; - this.manualShowing.value = true; - this.onChosen = onChosen; - this.onClosed = onClosed; + const anchorRef = shallowRef(anchorElement); + + if (this.loadedComponent) { + // コンポーネント解決済みのため同期的に popup() できる。 + // ユーザーアクティベーションコンテキストが維持されiOSでもfocusが機能する。 + const { dispose } = popup(this.loadedComponent, { + anchorElement: anchorRef, + pinnedEmojis: this.emojisRef, + asReactionPicker: false, + choseAndClose: false, + }, { + done: (emoji: string) => { + if (onChosen) onChosen(emoji); + }, + closed: () => { + if (onClosed) onClosed(); + dispose(); + }, + }); + } else { + // フォールバック: 初回タップがプリロード完了前 + popupAsyncWithDialog( + import('@/components/MkEmojiPickerDialog.vue').then(m => { + this.loadedComponent = markRaw(m.default); + return this.loadedComponent; + }), + { + anchorElement: anchorRef, + pinnedEmojis: this.emojisRef, + asReactionPicker: false, + choseAndClose: false, + }, + { + done: (emoji: string) => { + if (onChosen) onChosen(emoji); + }, + closed: () => { + if (onClosed) onClosed(); + }, + }, + ); + } } } diff --git a/packages/frontend/src/utility/reaction-picker.ts b/packages/frontend/src/utility/reaction-picker.ts index e33346101d..49114664cf 100644 --- a/packages/frontend/src/utility/reaction-picker.ts +++ b/packages/frontend/src/utility/reaction-picker.ts @@ -4,57 +4,85 @@ */ import * as Misskey from 'misskey-js'; -import { defineAsyncComponent, ref, watch } from 'vue'; -import type { Ref } from 'vue'; -import { popup } from '@/os.js'; +import { markRaw, shallowRef, ref, watch } from 'vue'; +import type MkEmojiPickerDialog__TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue'; +import { popup, popupAsyncWithDialog } from '@/os.js'; import { prefer } from '@/preferences.js'; class ReactionPicker { - private anchorElement: Ref = ref(null); - private manualShowing = ref(false); - private targetNote: Ref = ref(null); - private onChosen?: (reaction: string) => void; - private onClosed?: () => void; + private loadedComponent: typeof MkEmojiPickerDialog__TypeReferenceOnly | null = null; + private reactionsRef = ref([]); constructor() { // nop } - public async init() { - const reactionsRef = ref([]); + public init() { + // コンポーネントをプリロードしてキャッシュしておく。 + // iOS PWA では await を挟むとユーザーアクティベーションが失われfocusが効かなくなるため、 + // show() 呼び出し時には同期的に popup() できるよう事前にコンポーネントを解決しておく。 + import('@/components/MkEmojiPickerDialog.vue').then(m => { + this.loadedComponent = markRaw(m.default); + }).catch(err => { + console.error('[ReactionPicker] Failed to preload MkEmojiPickerDialog:', err); + }); 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 ?? []; + this.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')), { - anchorElement: this.anchorElement, - pinnedEmojis: reactionsRef, - asReactionPicker: true, - targetNote: this.targetNote, - manualShowing: this.manualShowing, - }, { - done: reaction => { - if (this.onChosen) this.onChosen(reaction); - }, - close: () => { - this.manualShowing.value = false; - }, - closed: () => { - this.anchorElement.value = null; - if (this.onClosed) this.onClosed(); - }, - }); } - public show(anchorElement: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { - this.anchorElement.value = anchorElement; - this.targetNote.value = targetNote; - this.manualShowing.value = true; - this.onChosen = onChosen; - this.onClosed = onClosed; + public show( + anchorElement: HTMLElement | null, + targetNote: Misskey.entities.Note | null, + onChosen?: (reaction: string) => void, + onClosed?: () => void, + ) { + const anchorRef = shallowRef(anchorElement); + const targetNoteRef = ref(targetNote); + + if (this.loadedComponent) { + // 通常パス: コンポーネント解決済みのため同期的に popup() できる。 + // ユーザーアクティベーションコンテキストが維持されiOSでもfocusが機能する。 + const { dispose } = popup(this.loadedComponent, { + anchorElement: anchorRef, + pinnedEmojis: this.reactionsRef, + asReactionPicker: true, + targetNote: targetNoteRef, + }, { + done: (reaction: string) => { + if (onChosen) onChosen(reaction); + }, + closed: () => { + if (onClosed) onClosed(); + dispose(); + }, + }); + } else { + // フォールバック: 初回タップがプリロード完了前 + popupAsyncWithDialog( + import('@/components/MkEmojiPickerDialog.vue').then(m => { + this.loadedComponent = markRaw(m.default); + return this.loadedComponent; + }), + { + anchorElement: anchorRef, + pinnedEmojis: this.reactionsRef, + asReactionPicker: true, + targetNote: targetNoteRef, + }, + { + done: (reaction: string) => { + if (onChosen) onChosen(reaction); + }, + closed: () => { + if (onClosed) onClosed(); + }, + }, + ); + } } } From 665adfccb7b4d40f48f1c609c70d7ec559d1cf67 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Apr 2026 02:31:32 +0000 Subject: [PATCH 4/4] Bump version to 2026.4.0-beta.2 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4c304236c9..e723cca7e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2026.4.0-beta.1", + "version": "2026.4.0-beta.2", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 59027c72e8..e659cf422a 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2026.4.0-beta.1", + "version": "2026.4.0-beta.2", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js",