From 19fe32bd7e416a6a408887e8cc5b03bc775c10a2 Mon Sep 17 00:00:00 2001
From: 1Step621 <86859447+1STEP621@users.noreply.github.com>
Date: Sat, 13 Jan 2024 15:25:11 +0900
Subject: [PATCH] =?UTF-8?q?Feat(frontend):=20=E3=83=AA=E3=82=A2=E3=82=AF?=
 =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=83=BB=E3=83=8E=E3=83=BC=E3=83=88?=
 =?UTF-8?q?=E5=86=85=E7=B5=B5=E6=96=87=E5=AD=97=E3=83=BB/about#emojis?=
 =?UTF-8?q?=E3=81=A7=E7=B5=B5=E6=96=87=E5=AD=97=E8=A9=B3=E7=B4=B0=E3=81=8C?=
 =?UTF-8?q?=E8=A6=8B=E3=82=89=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?=
 =?UTF-8?q?=20(#12984)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* リアクション・ノート内絵文字・/about#emojisで絵文字詳細が見られるように

* update CHANGELOG.md

* fix locale & type errors

* fix locale etc

* fix

* fix type

* lint fixes

* lint fixes(2)
---
 CHANGELOG.md                                  |   1 +
 .../MkCustomEmojiDetailedDialog.vue           | 102 ++++++++++++++++++
 .../components/MkReactionsViewer.reaction.vue |  18 ++++
 .../src/components/global/MkCustomEmoji.vue   |  16 ++-
 packages/frontend/src/pages/emojis.emoji.vue  |  23 ++--
 5 files changed, 146 insertions(+), 14 deletions(-)
 create mode 100644 packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 13ad3a350..a5e1f27b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@
 
 ### Client
 - Feat: 新しいゲームを追加
+- Feat: 絵文字の詳細ダイアログを追加
 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
 - Enhance: チャンネルノートのピン留めをノートのメニューからできるように
 - Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように
diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
new file mode 100644
index 000000000..c53bbca37
--- /dev/null
+++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
@@ -0,0 +1,102 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+  <MkModalWindow ref="dialogEl" @close="cancel()" @closed="$emit('closed')">
+    <template #header>:{{ emoji.name }}:</template>
+    <template #default>
+      <MkSpacer>
+        <div style="display: flex; flex-direction: column; gap: 1em;">
+          <div :class="$style.emojiImgWrapper">
+            <MkCustomEmoji :name="emoji.name" :normal="true" style="height: 100%;"></MkCustomEmoji>
+          </div>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.name }}</template>
+            <template #value>{{ emoji.name }}</template>
+          </MkKeyValue>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.tags }}</template>
+            <template #value>
+              <div v-if="emoji.aliases.length === 0">{{ i18n.ts.none }}</div>
+              <div v-else :class="$style.aliases">
+                <span v-for="alias in emoji.aliases" :key="alias" :class="$style.alias">
+                  {{ alias }}
+                </span>
+              </div>
+            </template>
+          </MkKeyValue>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.category }}</template>
+            <template #value>{{ emoji.category ?? i18n.ts.none }}</template>
+          </MkKeyValue>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.sensitive }}</template>
+            <template #value>{{ emoji.isSensitive ? i18n.ts.yes : i18n.ts.no }}</template>
+          </MkKeyValue>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.localOnly }}</template>
+            <template #value>{{ emoji.localOnly ? i18n.ts.yes : i18n.ts.no }}</template>
+          </MkKeyValue>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.license }}</template>
+            <template #value>{{ emoji.license ?? i18n.ts.none }}</template>
+          </MkKeyValue>
+          <MkKeyValue :copy="emoji.url">
+            <template #key>{{ i18n.ts.emojiUrl }}</template>
+            <template #value>
+              <a :href="emoji.url" target="_blank">{{ emoji.url }}</a>
+            </template>
+          </MkKeyValue>
+        </div>
+      </MkSpacer>
+    </template>
+  </MkModalWindow>
+</template>
+
+<script lang="ts" setup>
+import * as Misskey from 'misskey-js';
+import { defineProps, shallowRef } from 'vue';
+import { i18n } from '@/i18n.js';
+import MkModalWindow from '@/components/MkModalWindow.vue';
+import MkKeyValue from '@/components/MkKeyValue.vue';
+const props = defineProps<{
+  emoji: Misskey.entities.EmojiDetailed,
+}>();
+const emit = defineEmits<{
+	(ev: 'ok', cropped: Misskey.entities.DriveFile): void;
+	(ev: 'cancel'): void;
+	(ev: 'closed'): void;
+}>();
+const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const cancel = () => {
+	emit('cancel');
+	dialogEl.value!.close();
+};
+</script>
+
+<style lang="scss" module>
+.emojiImgWrapper {
+  max-width: 100%;
+  height: 40cqh;
+  background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--X5) 8px, var(--X5) 14px);
+  border-radius: var(--radius);
+  margin: auto;
+  overflow-y: hidden;
+}
+
+.aliases {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 3px;
+}
+
+.alias {
+  display: inline-block;
+  padding: 3px 10px;
+  background-color: var(--X5);
+  border: solid 1px var(--divider);
+  border-radius: var(--radius);
+}
+</style>
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index 5ca09fa82..330e54f08 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	class="_button"
 	:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
 	@click="toggleReaction()"
+	@contextmenu.prevent.stop="menu"
 >
 	<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
 	<span :class="$style.count">{{ count }}</span>
@@ -21,6 +22,7 @@ import { computed, inject, onMounted, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import XDetails from '@/components/MkReactionsViewer.details.vue';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
+import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
 import * as os from '@/os.js';
 import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
@@ -98,6 +100,22 @@ async function toggleReaction() {
 	}
 }
 
+async function menu(ev) {
+	if (!canToggle.value) return;
+	if (!props.reaction.includes(":")) return;
+	os.popupMenu([{
+		text: i18n.ts.info,
+		icon: 'ti ti-info-circle',
+		action: async () => {
+			os.popup(MkCustomEmojiDetailedDialog, {
+				emoji: await misskeyApiGet('emoji', {
+					name: props.reaction.replace(/:/g, '').replace(/@\./, ''),
+				}),
+			});
+		},
+	}], ev.currentTarget ?? ev.target);
+}
+
 function anime() {
 	if (document.hidden) return;
 	if (!defaultStore.state.animation) return;
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index dd3fe7725..b384e8afc 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -24,9 +24,11 @@ import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy.js'
 import { defaultStore } from '@/store.js';
 import { customEmojisMap } from '@/custom-emojis.js';
 import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import * as sound from '@/scripts/sound.js';
 import { i18n } from '@/i18n.js';
+import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
 
 const props = defineProps<{
 	name: string;
@@ -93,7 +95,19 @@ function onClick(ev: MouseEvent) {
 				react(`:${props.name}:`);
 				sound.playMisskeySfx('reaction');
 			},
-		}] : [])], ev.currentTarget ?? ev.target);
+		}] : []), {
+			text: i18n.ts.info,
+			icon: 'ti ti-info-circle',
+			action: async () => {
+				os.popup(MkCustomEmojiDetailedDialog, {
+					emoji: await misskeyApiGet('emoji', {
+						name: customEmojiName.value,
+					}),
+				}, {
+					anchor: ev.target,
+				});
+			},
+		}], ev.currentTarget ?? ev.target);
 	}
 }
 </script>
diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue
index ea6947bbb..faa7acdcb 100644
--- a/packages/frontend/src/pages/emojis.emoji.vue
+++ b/packages/frontend/src/pages/emojis.emoji.vue
@@ -14,19 +14,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
 import * as os from '@/os.js';
+import * as Misskey from 'misskey-js';
 import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import { i18n } from '@/i18n.js';
+import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
 
 const props = defineProps<{
-	emoji: {
-		name: string;
-		aliases: string[];
-		category: string;
-		url: string;
-	};
+  emoji: Misskey.entities.EmojiSimple;
 }>();
 
 function menu(ev) {
@@ -43,12 +39,13 @@ function menu(ev) {
 	}, {
 		text: i18n.ts.info,
 		icon: 'ti ti-info-circle',
-		action: () => {
-			misskeyApiGet('emoji', { name: props.emoji.name }).then(res => {
-				os.alert({
-					type: 'info',
-					text: `Name: ${res.name}\nAliases: ${res.aliases.join(' ')}\nCategory: ${res.category}\nisSensitive: ${res.isSensitive}\nlocalOnly: ${res.localOnly}\nLicense: ${res.license}\nURL: ${res.url}`,
-				});
+		action: async () => {
+			os.popup(MkCustomEmojiDetailedDialog, {
+				emoji: await misskeyApiGet('emoji', {
+					name: props.emoji.name,
+				})
+			}, {
+				anchor: ev.target,
 			});
 		},
 	}], ev.currentTarget ?? ev.target);