From 9a3cb69815ed991a545e63dd1df972b8975ead2b Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 17 May 2026 23:31:03 +0900 Subject: [PATCH 1/4] =?UTF-8?q?enhance(frontend):=20=E7=B5=B5=E6=96=87?= =?UTF-8?q?=E5=AD=97=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC=E3=81=8B=E3=82=89?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E7=B5=B5=E6=96=87=E5=AD=97=E3=83=91=E3=83=AC?= =?UTF-8?q?=E3=83=83=E3=83=88=E3=81=AB=E8=BF=BD=E5=8A=A0=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=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/ja-JP.yml | 3 + .../components/MkReactionsViewer.reaction.vue | 11 +++ packages/frontend/src/components/MkSelect.vue | 3 + .../src/components/global/MkCustomEmoji.vue | 13 ++++ .../src/components/global/MkEmoji.vue | 35 ++++++--- .../frontend/src/utility/emoji-palette.ts | 77 +++++++++++++++++++ packages/frontend/src/utility/emoji-picker.ts | 4 +- .../frontend/src/utility/reaction-picker.ts | 4 +- packages/i18n/src/autogen/locale.ts | 12 +++ 9 files changed, 148 insertions(+), 14 deletions(-) create mode 100644 packages/frontend/src/utility/emoji-palette.ts diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 26ef054b9f1..7726c7c2b8b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1412,6 +1412,9 @@ nothingToConfigure: "設定項目はありません" viewRenotedChannel: "リノート先のチャンネルを見る" previewingTheme: "テーマのプレビュー中" previewingThemeRestore: "元に戻す" +chooseEmojiPalette: "絵文字パレットを選択" +addToEmojiPalette: "絵文字パレットに追加" +emojiPaletteAlreadyAddedConfirm: "この絵文字はすでにこの絵文字パレットに含まれています。削除しますか?" _imageEditing: _vars: diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index e9ed1cf4ac8..db99080de55 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -38,6 +38,7 @@ import { prefer } from '@/preferences.js'; import { DI } from '@/di.js'; import { noteEvents } from '@/composables/use-note-capture.js'; import { mute as muteEmoji, unmute as unmuteEmoji, checkMuted as isEmojiMuted } from '@/utility/emoji-mute.js'; +import { addToEmojiPalette } from '@/utility/emoji-palette.js'; import { haptic } from '@/utility/haptic.js'; const props = defineProps<{ @@ -206,6 +207,16 @@ async function menu(ev: PointerEvent) { }); } + if (canToggle.value) { + menuItems.push({ + text: i18n.ts.addToEmojiPalette, + icon: 'ti ti-palette', + action: () => { + addToEmojiPalette(isLocalCustomEmoji ? `:${emojiName.value}:` : props.reaction); + }, + }); + } + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue index 6f6957d5044..f1ad91c381b 100644 --- a/packages/frontend/src/components/MkSelect.vue +++ b/packages/frontend/src/components/MkSelect.vue @@ -46,6 +46,7 @@ export type ItemOption = { type?: 'option'; value: T; label: string; + caption?: string; }; export type ItemGroup = { @@ -177,6 +178,7 @@ function show() { for (const option of item.items) { menu.push({ text: option.label, + caption: option.caption, active: computed(() => model.value === option.value), action: () => { model.value = option.value as ModelTChecked; @@ -186,6 +188,7 @@ function show() { } else { menu.push({ text: item.label, + caption: item.caption, active: computed(() => model.value === item.value), action: () => { model.value = item.value as ModelTChecked; diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index 9a171876a03..39662fb7d45 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -50,6 +50,7 @@ import { $i } from '@/i.js'; import { prefer } from '@/preferences.js'; import { DI } from '@/di.js'; import { makeEmojiMuteKey, mute as muteEmoji, unmute as unmuteEmoji, checkMuted as checkEmojiMuted } from '@/utility/emoji-mute'; +import { addToEmojiPalette } from '@/utility/emoji-palette.js'; const props = defineProps<{ name: string; @@ -167,8 +168,20 @@ function onClick(ev: PointerEvent) { }); } + if (isLocal.value) { + menuItems.push({ + text: i18n.ts.addToEmojiPalette, + icon: 'ti ti-palette', + action: () => { + addToEmojiPalette(`:${props.name}:`); + }, + }); + } + if (($i?.isModerator ?? $i?.isAdmin) && isLocal.value) { menuItems.push({ + type: 'divider', + }, { text: i18n.ts.edit, icon: 'ti ti-pencil', action: async () => { diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index 686720cec2d..53218830642 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -20,6 +20,7 @@ import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; import { DI } from '@/di.js'; import { mute as muteEmoji, unmute as unmuteEmoji, checkMuted as checkMutedEmoji } from '@/utility/emoji-mute.js'; +import { addToEmojiPalette } from '@/utility/emoji-palette.js'; const props = defineProps<{ emoji: string; @@ -94,17 +95,31 @@ function onClick(ev: PointerEvent) { menuItems.push({ type: 'divider', - }, isMuted.value ? { - text: i18n.ts.emojiUnmute, - icon: 'ti ti-mood-smile', - action: () => { - unmute(); - }, - } : { - text: i18n.ts.emojiMute, - icon: 'ti ti-mood-off', + }); + + if (isMuted.value) { + menuItems.push({ + text: i18n.ts.emojiUnmute, + icon: 'ti ti-mood-smile', + action: () => { + unmute(); + }, + }); + } else { + menuItems.push({ + text: i18n.ts.emojiMute, + icon: 'ti ti-mood-off', + action: () => { + mute(); + }, + }); + } + + menuItems.push({ + text: i18n.ts.addToEmojiPalette, + icon: 'ti ti-palette', action: () => { - mute(); + addToEmojiPalette(props.emoji); }, }); diff --git a/packages/frontend/src/utility/emoji-palette.ts b/packages/frontend/src/utility/emoji-palette.ts new file mode 100644 index 00000000000..43c6f796a72 --- /dev/null +++ b/packages/frontend/src/utility/emoji-palette.ts @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { prefer } from '@/preferences.js'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import type { MkSelectItem } from '@/components/MkSelect.vue'; + +export function chooseEmojiPalette() { + return os.select({ + title: i18n.ts.chooseEmojiPalette, + default: prefer.s.emojiPaletteForMain ?? prefer.s.emojiPaletteForReaction ?? prefer.s.emojiPalettes[0]?.id, + items: prefer.s.emojiPalettes.map>((palette) => { + let caption: string | undefined = undefined; + + if (prefer.s.emojiPaletteForMain === palette.id) { + caption = i18n.ts._emojiPalette.paletteForMain; + } else if (prefer.s.emojiPaletteForReaction === palette.id) { + caption = i18n.ts._emojiPalette.paletteForReaction; + } + + return { + label: palette.name || `(${i18n.ts.noName})`, + caption, + value: palette.id, + }; + }), + }); +} + +export async function addToEmojiPalette(emoji: string) { + const res = await chooseEmojiPalette(); + + if (res.canceled || res.result == null) return; + + const palette = prefer.s.emojiPalettes.find((p) => p.id === res.result); + if (!palette) return; + let emojis = [...palette.emojis]; + + if (!emojis.includes(emoji)) { + emojis.push(emoji); + prefer.commit('emojiPalettes', prefer.s.emojiPalettes.map((p) => { + if (p.id === palette.id) { + return { + ...p, + emojis, + } + } else { + return p; + } + })); + os.success(); + } else { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.emojiPaletteAlreadyAddedConfirm, + }); + + if (canceled) return; + + emojis = emojis.filter((e) => e !== emoji); + + prefer.commit('emojiPalettes', prefer.s.emojiPalettes.map((p) => { + if (p.id === palette.id) { + return { + ...p, + emojis, + } + } else { + return p; + } + })); + os.success(); + } +} diff --git a/packages/frontend/src/utility/emoji-picker.ts b/packages/frontend/src/utility/emoji-picker.ts index c3d5ca9b35f..e74fb62be1b 100644 --- a/packages/frontend/src/utility/emoji-picker.ts +++ b/packages/frontend/src/utility/emoji-picker.ts @@ -22,8 +22,8 @@ class EmojiPicker { } public init() { - 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 ?? []; + watch([prefer.r.emojiPaletteForMain, prefer.r.emojiPalettes], ([newId, newPalettes]) => { + this.emojisRef.value = newId == null ? newPalettes[0].emojis : newPalettes.find(palette => palette.id === newId)?.emojis ?? []; }, { immediate: true, }); diff --git a/packages/frontend/src/utility/reaction-picker.ts b/packages/frontend/src/utility/reaction-picker.ts index c1bdf19758a..b0b97f4bb51 100644 --- a/packages/frontend/src/utility/reaction-picker.ts +++ b/packages/frontend/src/utility/reaction-picker.ts @@ -17,8 +17,8 @@ class ReactionPicker { } public init() { - watch([prefer.r.emojiPaletteForReaction, prefer.r.emojiPalettes], () => { - this.reactionsRef.value = prefer.s.emojiPaletteForReaction == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForReaction)?.emojis ?? []; + watch([prefer.r.emojiPaletteForReaction, prefer.r.emojiPalettes], ([newId, newPalettes]) => { + this.reactionsRef.value = newId == null ? newPalettes[0].emojis : newPalettes.find(palette => palette.id === newId)?.emojis ?? []; }, { immediate: true, }); diff --git a/packages/i18n/src/autogen/locale.ts b/packages/i18n/src/autogen/locale.ts index e13458c22d3..2537e97efde 100644 --- a/packages/i18n/src/autogen/locale.ts +++ b/packages/i18n/src/autogen/locale.ts @@ -5663,6 +5663,18 @@ export interface Locale extends ILocale { * 元に戻す */ "previewingThemeRestore": string; + /** + * 絵文字パレットを選択 + */ + "chooseEmojiPalette": string; + /** + * 絵文字パレットに追加 + */ + "addToEmojiPalette": string; + /** + * この絵文字はすでにこの絵文字パレットに含まれています。削除しますか? + */ + "emojiPaletteAlreadyAddedConfirm": string; "_imageEditing": { "_vars": { /** From 886c1a68185b56f44db40198fe15137c6416e8ad Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 17 May 2026 23:32:13 +0900 Subject: [PATCH 2/4] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f026e3f689d..fbeb8716b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Client - Enhance: テーマのプレビュー時、リロードせずにもとのテーマに戻せるように - Enhance: Fluent Emojiを更新し、Unicode 15+相当の絵文字の表示に対応 +- Enhance: 絵文字のメニューから直接絵文字パレットに絵文字を追加できるように - Fix: テーマエディター使用時に、最初の変更のみ適用される問題を修正 - Fix: テーマのプレビュー時、既存のテーマとIDが被っている場合にプレビューできない問題を修正 - Fix: テーマのインストールエラーの表示を改善 From 6926b6bd58512086a073565e57dc87cd73cd5932 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 17 May 2026 23:37:02 +0900 Subject: [PATCH 3/4] fix lint --- packages/frontend/src/utility/emoji-palette.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/utility/emoji-palette.ts b/packages/frontend/src/utility/emoji-palette.ts index 43c6f796a72..2ffd40efefa 100644 --- a/packages/frontend/src/utility/emoji-palette.ts +++ b/packages/frontend/src/utility/emoji-palette.ts @@ -46,7 +46,7 @@ export async function addToEmojiPalette(emoji: string) { return { ...p, emojis, - } + }; } else { return p; } @@ -67,7 +67,7 @@ export async function addToEmojiPalette(emoji: string) { return { ...p, emojis, - } + }; } else { return p; } From 96e7564a426042330fe0144fa338900c242b7ecb Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 21 May 2026 18:58:52 +0900 Subject: [PATCH 4/4] Update Changelog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30a1b9a90c7..1b3f95ace15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - ### Client -- +- Enhance: 絵文字のメニューから直接絵文字パレットに絵文字を追加できるように ### Server - @@ -41,7 +41,6 @@ ### Client - Enhance: テーマのプレビュー時、リロードせずにもとのテーマに戻せるように - Enhance: Fluent Emojiを更新し、Unicode 15+相当の絵文字の表示に対応 -- Enhance: 絵文字のメニューから直接絵文字パレットに絵文字を追加できるように - Fix: テーマエディター使用時に、最初の変更のみ適用される問題を修正 - Fix: テーマのプレビュー時、既存のテーマとIDが被っている場合にプレビューできない問題を修正 - Fix: テーマのインストールエラーの表示を改善