diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc2f7e0b08..cd601c7e29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
- Enhance: クライアントエラー画面の多言語対応
- Enhance: 開発者モードでメニューからファイルIDをコピー出来るように `#15441'
- Enhance: ノートに埋め込まれたメディアのコンテキストメニューから管理者用のファイル管理画面を開けるように ( #15440 )
+- Enhance: リアクションする際に確認ダイアログを表示できるように
- Fix: コンディショナルロールを手動で割り当てできる導線を削除 `#13529`
- Fix: 埋め込みプレイヤーから外部ページに移動できない問題を修正
- Fix: Play の再読込時に UI が以前の状態を引き継いでしまう問題を修正 `#14378`
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 4fe605490e..c7996b2ca9 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -5254,6 +5254,14 @@ export interface Locale extends ILocale {
* このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。
*/
"federationDisabled": string;
+ /**
+ * リアクションする際に確認する
+ */
+ "confirmOnReact": string;
+ /**
+ * " {emoji} " をリアクションしますか?
+ */
+ "reactAreYouSure": ParameterizedString<"emoji">;
"_accountSettings": {
/**
* コンテンツの表示にログインを必須にする
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 57b11e9e04..1aed7c21ae 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1309,6 +1309,8 @@ availableRoles: "利用可能なロール"
acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。"
federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。"
federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。"
+confirmOnReact: "リアクションする際に確認する"
+reactAreYouSure: "\" {emoji} \" をリアクションしますか?"
_accountSettings:
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 919734f763..193dfe5b7e 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -489,7 +489,16 @@ function react(): void {
}
} else {
blur();
- reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+ reactionPicker.show(reactButton.value ?? null, note.value, async (reaction) => {
+ if (defaultStore.state.confirmOnReact) {
+ const confirm = await os.confirm({
+ type: 'question',
+ text: i18n.tsx.reactAreYouSure({ emoji: reaction.replace('@.', '') }),
+ });
+
+ if (confirm.canceled) return;
+ }
+
sound.playMisskeySfx('reaction');
if (props.mock) {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index fa1358da3d..835a663868 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -452,7 +452,16 @@ function react(): void {
}
} else {
blur();
- reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+ reactionPicker.show(reactButton.value ?? null, note.value, async (reaction) => {
+ if (defaultStore.state.confirmOnReact) {
+ const confirm = await os.confirm({
+ type: 'question',
+ text: i18n.tsx.reactAreYouSure({ emoji: reaction.replace('@.', '') }),
+ });
+
+ if (confirm.canceled) return;
+ }
+
sound.playMisskeySfx('reaction');
misskeyApi('notes/reactions/create', {
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index b65038aadc..41e475eade 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -90,6 +90,15 @@ async function toggleReaction() {
}
});
} else {
+ if (defaultStore.state.confirmOnReact) {
+ const confirm = await os.confirm({
+ type: 'question',
+ text: i18n.tsx.reactAreYouSure({ emoji: props.reaction.replace('@.', '') }),
+ });
+
+ if (confirm.canceled) return;
+ }
+
sound.playMisskeySfx('reaction');
if (mock) {
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 1ee7909aa8..4449d6169f 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -170,6 +170,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.enableHorizontalSwipe }}
{{ i18n.ts.alwaysConfirmFollow }}
{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}
+ {{ i18n.ts.confirmOnReact }}
{{ i18n.ts.whenServerDisconnected }}
@@ -320,6 +321,7 @@ const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHori
const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer'));
const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow'));
const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia'));
+const confirmOnReact = computed(defaultStore.makeGetterSetter('confirmOnReact'));
const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu'));
watch(lang, () => {
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 16b900e052..e2243e067a 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -479,6 +479,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
+ confirmOnReact: {
+ where: 'device',
+ default: false,
+ },
sound_masterVolume: {
where: 'device',