diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aeaa66f79..c91d602692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - ### Client +- Enhance: ノートのリアクション一覧で、押せるリアクションを優先して表示できるようにするオプションを追加 - Fix: ドライブファイルの選択が不安定な問題を修正 - Fix: コントロールパネルのファイル欄などのデザインが崩れている問題を修正 - Fix: ユーザーの検索結果を追加で読み込むことができない問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index 73bcb2f1c8..0119b4ae5e 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5821,6 +5821,10 @@ export interface Locale extends ILocale { * URLプレビューを表示する */ "showUrlPreview": string; + /** + * 利用できるリアクションを先頭に表示 + */ + "showAvailableReactionsFirstInNote": string; "_chat": { /** * 送信者の名前を表示 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index c7971507aa..b61bbf4970 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1455,6 +1455,7 @@ _settings: contentsUpdateFrequency_description: "高いほどリアルタイムにコンテンツが更新されますが、パフォーマンスが低下し、通信量とバッテリーの消費が多くなります。" contentsUpdateFrequency_description2: "リアルタイムモードがオンのときは、この設定に関わらずリアルタイムでコンテンツが更新されます。" showUrlPreview: "URLプレビューを表示する" + showAvailableReactionsFirstInNote: "利用できるリアクションを先頭に表示" _chat: showSenderName: "送信者の名前を表示" diff --git a/packages/frontend-shared/js/emojilist.ts b/packages/frontend-shared/js/emojilist.ts index f8bbf39177..09bea06719 100644 --- a/packages/frontend-shared/js/emojilist.ts +++ b/packages/frontend-shared/js/emojilist.ts @@ -48,6 +48,10 @@ export function getUnicodeEmoji(char: string): UnicodeEmojiDef | string { ?? char; } +export function isSupportedEmoji(char: string): boolean { + return unicodeEmojisMap.has(colorizeEmoji(char)) || unicodeEmojisMap.has(char); +} + export function getEmojiName(char: string): string { // Colorize it because emojilist.json assumes that const idx = _indexByChar.get(colorizeEmoji(char)) ?? _indexByChar.get(char); diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue index 725978179e..bd9ef50157 100644 --- a/packages/frontend/src/components/MkReactionsViewer.vue +++ b/packages/frontend/src/components/MkReactionsViewer.vue @@ -33,7 +33,10 @@ import * as Misskey from 'misskey-js'; import { inject, watch, ref } from 'vue'; import { TransitionGroup } from 'vue'; import XReaction from '@/components/MkReactionsViewer.reaction.vue'; +import { $i } from '@/i.js'; import { prefer } from '@/preferences.js'; +import { customEmojisMap } from '@/custom-emojis.js'; +import { isSupportedEmoji } from '@@/js/emojilist.js'; import { DI } from '@/di.js'; const props = withDefaults(defineProps<{ @@ -70,6 +73,12 @@ function onMockToggleReaction(emoji: string, count: number) { emit('mockUpdateMyReaction', emoji, (count - _reactions.value[i][1])); } +function canReact(reaction: string) { + if (!$i) return false; + // TODO: CheckPermissions + return !reaction.match(/@\w/) && (customEmojisMap.has(reaction) || isSupportedEmoji(reaction)); +} + watch([() => props.reactions, () => props.maxNumber], ([newSource, maxNumber]) => { let newReactions: [string, number][] = []; hasMoreReactions.value = Object.keys(newSource).length > maxNumber; @@ -86,7 +95,15 @@ watch([() => props.reactions, () => props.maxNumber], ([newSource, maxNumber]) = newReactions = [ ...newReactions, ...Object.entries(newSource) - .sort(([, a], [, b]) => b - a) + .sort(([emojiA, countA], [emojiB, countB]) => { + if (prefer.s.showAvailableReactionsFirstInNote) { + if (!canReact(emojiA) && canReact(emojiB)) return 1; + if (canReact(emojiA) && !canReact(emojiB)) return -1; + return countB - countA; + } else { + return countB - countA; + } + }) .filter(([y], i) => i < maxNumber && !newReactionsNames.includes(y)), ]; diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 678cfb93c0..00f810cc37 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -229,6 +229,14 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + + @@ -824,6 +832,7 @@ const showFixedPostFormInChannel = prefer.model('showFixedPostFormInChannel'); const numberOfPageCache = prefer.model('numberOfPageCache'); const enableInfiniteScroll = prefer.model('enableInfiniteScroll'); const useReactionPickerForContextMenu = prefer.model('useReactionPickerForContextMenu'); +const showAvailableReactionsFirstInNote = prefer.model('showAvailableReactionsFirstInNote'); const useGroupedNotifications = prefer.model('useGroupedNotifications'); const alwaysConfirmFollow = prefer.model('alwaysConfirmFollow'); const confirmWhenRevealingSensitiveMedia = prefer.model('confirmWhenRevealingSensitiveMedia'); @@ -917,6 +926,7 @@ watch([ enableHorizontalSwipe, enablePullToRefresh, reduceAnimation, + showAvailableReactionsFirstInNote, ], async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index 5aadf835f2..6eb9b2408a 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -377,6 +377,9 @@ export const PREF_DEF = definePreferences({ showTitlebar: { default: false, }, + showAvailableReactionsFirstInNote: { + default: false, + }, plugins: { default: [] as Plugin[], mergeStrategy: (a, b) => {