summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-11-04 18:27:22 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-11-04 18:27:22 +0900
commit5e9f6a90df9c999d36283d2ba9eb8e23ccfd7d7b (patch)
tree9c70fc902fbaa8929598e9ff5ebab95ed7e4d0f2 /packages/frontend/src
parentenhance(frontend): tweak settings page (diff)
downloadmisskey-5e9f6a90df9c999d36283d2ba9eb8e23ccfd7d7b.tar.gz
misskey-5e9f6a90df9c999d36283d2ba9eb8e23ccfd7d7b.tar.bz2
misskey-5e9f6a90df9c999d36283d2ba9eb8e23ccfd7d7b.zip
enhance(frontend): ノート内のカスタム絵文字をクリックすることで、コピーおよびリアクションができるように
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/components/MkNote.vue22
-rw-r--r--packages/frontend/src/components/MkNoteDetailed.vue6
-rw-r--r--packages/frontend/src/components/MkNoteSimple.vue2
-rw-r--r--packages/frontend/src/components/MkNoteSub.vue2
-rw-r--r--packages/frontend/src/components/MkSubNoteContent.vue2
-rw-r--r--packages/frontend/src/components/MkUserInfo.vue2
-rw-r--r--packages/frontend/src/components/MkUserPopup.vue2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.User.vue2
-rw-r--r--packages/frontend/src/components/global/MkCustomEmoji.vue43
-rw-r--r--packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts5
-rw-r--r--packages/frontend/src/components/page/page.text.vue2
-rw-r--r--packages/frontend/src/pages/channel.vue2
-rw-r--r--packages/frontend/src/pages/clip.vue2
-rw-r--r--packages/frontend/src/pages/user/home.vue4
-rw-r--r--packages/frontend/src/pages/welcome.timeline.vue2
15 files changed, 79 insertions, 21 deletions
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 30a68f38f2..0ae3423a21 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -53,19 +53,28 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
<div style="container-type: inline-size;">
<p v-if="appearNote.cw != null" :class="$style.cw">
- <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'" :i="$i"/>
+ <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'"/>
<MkCwButton v-model="showContent" :note="appearNote" style="margin: 4px 0;"/>
</p>
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
<div :class="$style.text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
- <Mfm v-if="appearNote.text" :parsedNodes="parsed" :text="appearNote.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
+ <Mfm
+ v-if="appearNote.text"
+ :parsedNodes="parsed"
+ :text="appearNote.text"
+ :author="appearNote.user"
+ :nyaize="'account'"
+ :emojiUrls="appearNote.emojis"
+ :enableEmojiMenu="true"
+ :enableEmojiMenuReaction="true"
+ />
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<div v-else>
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
- <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
+ <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/>
</div>
</div>
</div>
@@ -242,6 +251,13 @@ const keymap = {
's': () => showContent.value !== showContent.value,
};
+provide('react', (reaction: string) => {
+ os.api('notes/reactions/create', {
+ noteId: appearNote.id,
+ reaction: reaction,
+ });
+});
+
if (props.mock) {
watch(() => props.note, (to) => {
note = deepClone(to);
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 9e9b1035d7..ff2941344e 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -67,19 +67,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</header>
<div :class="$style.noteContent">
<p v-if="appearNote.cw != null" :class="$style.cw">
- <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'" :i="$i"/>
+ <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'"/>
<MkCwButton v-model="showContent" :note="appearNote"/>
</p>
<div v-show="appearNote.cw == null || showContent">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
- <Mfm v-if="appearNote.text" :parsedNodes="parsed" :text="appearNote.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
+ <Mfm v-if="appearNote.text" :parsedNodes="parsed" :text="appearNote.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/>
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<div v-else>
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
- <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
+ <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/>
</div>
</div>
<div v-if="appearNote.files.length > 0">
diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index dc401a7ecb..28b00af246 100644
--- a/packages/frontend/src/components/MkNoteSimple.vue
+++ b/packages/frontend/src/components/MkNoteSimple.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div>
<p v-if="note.cw != null" :class="$style.cw">
- <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'" :i="$i" :emojiUrls="note.emojis"/>
+ <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'" :emojiUrls="note.emojis"/>
<MkCwButton v-model="showContent" :note="note"/>
</p>
<div v-show="note.cw == null || showContent">
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index 3cc8767007..f61b963d9b 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div>
<p v-if="note.cw != null" :class="$style.cw">
- <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'" :i="$i"/>
+ <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'"/>
<MkCwButton v-model="showContent" :note="note"/>
</p>
<div v-show="note.cw == null || showContent">
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 51dabee161..e9f2b838d2 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
<MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
- <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :emojiUrls="note.emojis"/>
+ <Mfm v-if="note.text" :text="note.text" :author="note.user" :emojiUrls="note.emojis"/>
<MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
</div>
<details v-if="note.files.length > 0">
diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue
index c13ef60f3b..eaebbf03e7 100644
--- a/packages/frontend/src/components/MkUserInfo.vue
+++ b/packages/frontend/src/components/MkUserInfo.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-if="$i && $i.id !== user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span>
<div :class="$style.description">
<div v-if="user.description" :class="$style.mfm">
- <Mfm :text="user.description" :author="user" :i="$i"/>
+ <Mfm :text="user.description" :author="user"/>
</div>
<span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>
</div>
diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue
index bcba4196b5..20eb9b3e93 100644
--- a/packages/frontend/src/components/MkUserPopup.vue
+++ b/packages/frontend/src/components/MkUserPopup.vue
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.username"><MkAcct :user="user"/></div>
</div>
<div :class="$style.description">
- <Mfm v-if="user.description" :class="$style.mfm" :text="user.description" :author="user" :i="$i"/>
+ <Mfm v-if="user.description" :class="$style.mfm" :text="user.description" :author="user"/>
<div v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</div>
</div>
<div :class="$style.status">
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue
index 746781d71f..4fbaf75454 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.User.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div :class="$style.description">
<div v-if="user.description" :class="$style.mfm">
- <Mfm :text="user.description" :author="user" :i="$i"/>
+ <Mfm :text="user.description" :author="user"/>
</div>
<span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>
</div>
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index 063b122f8b..1e17bab849 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -5,14 +5,27 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<span v-if="errored">:{{ customEmojiName }}:</span>
-<img v-else :class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async" @error="errored = true" @load="errored = false"/>
+<img
+ v-else
+ :class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
+ :src="url"
+ :alt="alt"
+ :title="alt"
+ decoding="async"
+ @error="errored = true"
+ @load="errored = false"
+ @click="onClick"
+/>
</template>
<script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, inject } from 'vue';
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 copyToClipboard from '@/scripts/copy-to-clipboard.js';
+import { i18n } from '@/i18n.js';
const props = defineProps<{
name: string;
@@ -21,8 +34,12 @@ const props = defineProps<{
host?: string | null;
url?: string;
useOriginalSize?: boolean;
+ menu?: boolean;
+ menuReaction?: boolean;
}>();
+const react = inject<((name: string) => void) | null>('react', null);
+
const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substring(1, props.name.length - 1) : props.name).replace('@.', ''));
const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@')));
@@ -55,6 +72,28 @@ const url = computed(() => {
const alt = computed(() => `:${customEmojiName.value}:`);
let errored = $ref(url.value == null);
+
+function onClick(ev: MouseEvent) {
+ if (props.menu) {
+ os.popupMenu([{
+ type: 'label',
+ text: `:${props.name}:`,
+ }, {
+ text: i18n.ts.copy,
+ icon: 'ti ti-copy',
+ action: () => {
+ copyToClipboard(`:${props.name}:`);
+ os.success();
+ },
+ }, ...(props.menuReaction && react ? [{
+ text: i18n.ts.doReaction,
+ icon: 'ti ti-plus',
+ action: () => {
+ react(`:${props.name}:`);
+ },
+ }] : [])], ev.currentTarget ?? ev.target);
+ }
+}
</script>
<style lang="scss" module>
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
index ab8a342691..d7e1490502 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
+++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
@@ -33,12 +33,13 @@ type MfmProps = {
plain?: boolean;
nowrap?: boolean;
author?: Misskey.entities.UserLite;
- i?: Misskey.entities.UserLite | null;
isNote?: boolean;
emojiUrls?: string[];
rootScale?: number;
nyaize: boolean | 'account';
parsedNodes?: mfm.MfmNode[] | null;
+ enableEmojiMenu?: boolean;
+ enableEmojiMenuReaction?: boolean;
};
// eslint-disable-next-line import/no-default-export
@@ -328,6 +329,8 @@ export default function(props: MfmProps) {
normal: props.plain,
host: null,
useOriginalSize: scale >= 2.5,
+ menu: props.enableEmojiMenu,
+ menuReaction: props.enableEmojiMenuReaction,
})];
} else {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue
index 35021be95e..e0f1a4af90 100644
--- a/packages/frontend/src/components/page/page.text.vue
+++ b/packages/frontend/src/components/page/page.text.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
- <Mfm :text="block.text" :isNote="false" :i="$i"/>
+ <Mfm :text="block.text" :isNote="false"/>
<MkUrlPreview v-for="url in urls" :key="url" :url="url"/>
</div>
</template>
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 911f4e95d2..1d41fe7529 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.bannerFade"></div>
</div>
<div v-if="channel.description" :class="$style.description">
- <Mfm :text="channel.description" :isNote="false" :i="$i"/>
+ <Mfm :text="channel.description" :isNote="false"/>
</div>
</div>
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index 80b94acb6b..4573bbb81c 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="clip" class="_gaps">
<div class="_panel">
<div v-if="clip.description" :class="$style.description">
- <Mfm :text="clip.description" :isNote="false" :i="$i"/>
+ <Mfm :text="clip.description" :isNote="false"/>
</div>
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike rounded primary @click="unfavorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton>
<MkButton v-else v-tooltip="i18n.ts.favorite" asLike rounded @click="favorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton>
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 4c425898d5..7ff490bf8b 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -76,7 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div class="description">
<MkOmit>
- <Mfm v-if="user.description" :text="user.description" :isNote="false" :author="user" :i="$i"/>
+ <Mfm v-if="user.description" :text="user.description" :isNote="false" :author="user"/>
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
</MkOmit>
</div>
@@ -100,7 +100,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<Mfm :text="field.name" :plain="true" :colored="false"/>
</dt>
<dd class="value">
- <Mfm :text="field.value" :author="user" :i="$i" :colored="false"/>
+ <Mfm :text="field.value" :author="user" :colored="false"/>
<i v-if="user.verifiedLinks.includes(field.value)" v-tooltip:dialog="i18n.ts.verifiedLink" class="ti ti-circle-check" :class="$style.verifiedLink"></i>
</dd>
</dl>
diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue
index f2e151468a..8e2192074d 100644
--- a/packages/frontend/src/pages/welcome.timeline.vue
+++ b/packages/frontend/src/pages/welcome.timeline.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_panel" :class="$style.content">
<div>
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
- <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i"/>
+ <Mfm v-if="note.text" :text="note.text" :author="note.user"/>
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
</div>
<div v-if="note.files.length > 0" :class="$style.richcontent">