diff --git a/CHANGELOG.md b/CHANGELOG.md index f73f972cc52..2fcd976c282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Feat: ジョブキュー管理画面からキューの一時停止/再開ができるように ### Client +- Enhance: 絵文字のメニューから直接絵文字パレットに絵文字を追加できるように - Fix: 一部の実績が正しく表示されない問題を修正 - Fix: アクセストークン発行時のダイアログのタイトルが「確認コード」となっているのを修正 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 7a845734b5c..45acb7fb839 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1413,6 +1413,9 @@ viewRenotedChannel: "リノート先のチャンネルを見る" previewingTheme: "テーマのプレビュー中" previewingThemeRestore: "元に戻す" accessToken: "アクセストークン" +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..2ffd40efefa --- /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 9f113ff4558..9f09271e183 100644 --- a/packages/i18n/src/autogen/locale.ts +++ b/packages/i18n/src/autogen/locale.ts @@ -5667,6 +5667,18 @@ export interface Locale extends ILocale { * アクセストークン */ "accessToken": string; + /** + * 絵文字パレットを選択 + */ + "chooseEmojiPalette": string; + /** + * 絵文字パレットに追加 + */ + "addToEmojiPalette": string; + /** + * この絵文字はすでにこの絵文字パレットに含まれています。削除しますか? + */ + "emojiPaletteAlreadyAddedConfirm": string; "_imageEditing": { "_vars": { /**