diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-01-26 15:48:12 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-01-26 15:48:12 +0900 |
| commit | 452bd6db2501a1f862b167f56230acd40e62731b (patch) | |
| tree | e0640cb62d8fef0112c7f239e21e5129c1540e1b /packages/frontend/src | |
| parent | chore: check emoji host (diff) | |
| download | misskey-452bd6db2501a1f862b167f56230acd40e62731b.tar.gz misskey-452bd6db2501a1f862b167f56230acd40e62731b.tar.bz2 misskey-452bd6db2501a1f862b167f56230acd40e62731b.zip | |
tweak custom emoji handling
Close #9721
Diffstat (limited to 'packages/frontend/src')
15 files changed, 120 insertions, 76 deletions
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 2cb3aeb3d8..27997eb330 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -17,7 +17,7 @@ </ol> <ol v-else-if="emojis.length > 0" ref="suggests" :class="$style.list"> <li v-for="emoji in emojis" :key="emoji.emoji" :class="$style.item" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown"> - <MkEmoji :emoji="emoji.emoji" :class="$style.emoji"/> + <MkCustomEmoji :name="emoji.emoji" :class="$style.emoji"/> <!-- eslint-disable-next-line vue/no-v-html --> <span v-if="q" :class="$style.emojiName" v-html="sanitizeHtml(emoji.name.replace(q, `<b>${q}</b>`))"></span> <span v-else v-text="emoji.name"></span> @@ -112,7 +112,7 @@ const emojiDb = computed(() => { customEmojiDB.sort((a, b) => a.name.length - b.name.length); //#endregion - return markRaw([ ...customEmojiDB, ...unicodeEmojiDB ]); + return markRaw([...customEmojiDB, ...unicodeEmojiDB]); }); export default { diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index f64cc6e9aa..39e274ba11 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -12,7 +12,7 @@ tabindex="0" @click="chosen(emoji, $event)" > - <MkEmoji class="emoji" :emoji="`:${emoji.name}:`"/> + <MkCustomEmoji class="emoji" :name="emoji.name"/> </button> </div> <div v-if="searchResultUnicode.length > 0" class="body"> @@ -39,7 +39,8 @@ tabindex="0" @click="chosen(emoji, $event)" > - <MkEmoji class="emoji" :emoji="emoji" :normal="true"/> + <MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/> + <MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/> </button> </div> </section> @@ -53,7 +54,8 @@ class="_button item" @click="chosen(emoji, $event)" > - <MkEmoji class="emoji" :emoji="emoji" :normal="true"/> + <MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/> + <MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/> </button> </div> </section> diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 1f6a2883d7..351861ac17 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -48,12 +48,12 @@ <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" v-once :text="appearNote.text" :author="appearNote.user" :i="$i"/> + <Mfm v-if="appearNote.text" v-once :text="appearNote.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/> <div v-if="translating || translation" :class="$style.translation"> <MkLoading v-if="translating" mini/> <div v-else :class="$style.translated"> <b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b> - <Mfm :text="translation.text" :author="appearNote.user" :i="$i"/> + <Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/> </div> </div> </div> diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 48ace56d9c..0da06c4f14 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -65,13 +65,13 @@ <div class="text"> <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> - <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i"/> + <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/> <a v-if="appearNote.renote != null" class="rp">RN:</a> <div v-if="translating || translation" class="translation"> <MkLoading v-if="translating" mini/> <div v-else class="translated"> <b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b> - <Mfm :text="translation.text" :author="appearNote.user" :i="$i"/> + <Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/> </div> </div> </div> diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index 828bbee5af..757b325a06 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -5,7 +5,7 @@ <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" :i="$i"/> + <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i" :emoji-urls="note.emojis"/> <MkCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent"> diff --git a/packages/frontend/src/components/MkReactionIcon.vue b/packages/frontend/src/components/MkReactionIcon.vue index 6e9d2b1a6c..29b3f9b85b 100644 --- a/packages/frontend/src/components/MkReactionIcon.vue +++ b/packages/frontend/src/components/MkReactionIcon.vue @@ -1,5 +1,6 @@ <template> -<MkEmoji :emoji="reaction" :is-reaction="true" :normal="true" :no-style="noStyle"/> +<MkCustomEmoji v-if="reaction[0] === ':'" :name="reaction" :normal="true" :no-style="noStyle" :url="emojiUrl"/> +<MkEmoji v-else :emoji="reaction" :normal="true" :no-style="noStyle"/> </template> <script lang="ts" setup> @@ -8,5 +9,6 @@ import { } from 'vue'; const props = defineProps<{ reaction: string; noStyle?: boolean; + emojiUrl?: string; }>(); </script> diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index eed6b46594..83fdf0f988 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -6,7 +6,7 @@ :class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle }]" @click="toggleReaction()" > - <MkReactionIcon :class="$style.icon" :reaction="reaction"/> + <MkReactionIcon :class="$style.icon" :reaction="reaction" :emoji-url="note.reactionEmojis[reaction.substr(1, reaction.length - 2)]"/> <span :class="$style.count">{{ count }}</span> </button> </template> diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 482a43f474..7e6833dae9 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -4,7 +4,7 @@ <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" v-once :text="note.text" :author="note.user" :i="$i"/> + <Mfm v-if="note.text" v-once :text="note.text" :author="note.user" :i="$i" :emoji-urls="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/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue new file mode 100644 index 0000000000..93c47f0c27 --- /dev/null +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -0,0 +1,61 @@ +<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"/> +</template> + +<script lang="ts" setup> +import { computed } from 'vue'; +import { getStaticImageUrl } from '@/scripts/media-proxy'; +import { defaultStore } from '@/store'; +import { customEmojis } from '@/custom-emojis'; + +const props = defineProps<{ + name: string; + normal?: boolean; + noStyle?: boolean; + host?: string | null; + url?: string; +}>(); + +const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substr(1, props.name.length - 2) : props.name).replace('@.', '')); +const url = computed(() => { + if (props.url) { + return props.url; + } else if (props.host == null && !customEmojiName.value.includes('@')) { + const found = customEmojis.value.find(x => x.name === customEmojiName.value); + return found ? defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(found.url) : found.url : null; + } else { + const rawUrl = props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`; + return defaultStore.state.disableShowingAnimatedImages + ? getStaticImageUrl(rawUrl) + : rawUrl; + } +}); +const alt = computed(() => `:${customEmojiName.value}:`); +let errored = $ref(url.value == null); +</script> + +<style lang="scss" module> +.root { + height: 2.5em; + vertical-align: middle; + transition: transform 0.2s ease; + + &:hover { + transform: scale(1.2); + } +} + +.normal { + height: 1.25em; + vertical-align: -0.25em; + + &:hover { + transform: none; + } +} + +.noStyle { + height: auto !important; +} +</style> diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index b554d5e47c..1b0d34a445 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -1,54 +1,29 @@ <template> -<span v-if="isCustom && errored">:{{ customEmojiName }}:</span> -<img v-else-if="isCustom" :class="[$style.root, $style.custom, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async" @error="errored = true" @load="errored = false"/> -<img v-else-if="char && !useOsNativeEmojis" :class="$style.root" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/> -<span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span> +<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle"/> +<span v-else-if="useOsNativeEmojis" :alt="props.emoji" @pointerenter="computeTitle">{{ props.emoji }}</span> <span v-else>{{ emoji }}</span> </template> <script lang="ts" setup> import { computed } from 'vue'; -import { getStaticImageUrl } from '@/scripts/media-proxy'; import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base'; import { defaultStore } from '@/store'; import { getEmojiName } from '@/scripts/emojilist'; -import { customEmojis } from '@/custom-emojis'; const props = defineProps<{ emoji: string; - normal?: boolean; - noStyle?: boolean; - isReaction?: boolean; - host?: string | null; }>(); const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; -const isCustom = computed(() => props.emoji.startsWith(':')); -const customEmojiName = computed(() => props.emoji.substr(1, props.emoji.length - 2).replace('@.', '')); -const char = computed(() => isCustom.value ? undefined : props.emoji); -const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native' && !props.isReaction); +const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native'); const url = computed(() => { - if (char.value) { - return char2path(char.value); - } else if (props.host == null && !customEmojiName.value.includes('@')) { - const found = customEmojis.value.find(x => x.name === customEmojiName.value); - return found ? defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(found.url) : found.url : null; - } else { - const rawUrl = props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`; - return defaultStore.state.disableShowingAnimatedImages - ? getStaticImageUrl(rawUrl) - : rawUrl; - } + return char2path(props.emoji); }); -const alt = computed(() => isCustom.value ? `:${customEmojiName.value}:` : char.value); -let errored = $ref(isCustom.value && url.value == null); // Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter function computeTitle(event: PointerEvent): void { - const title = isCustom.value - ? `:${customEmojiName.value}:` - : (getEmojiName(char.value as string) ?? char.value as string); + const title = getEmojiName(props.emoji as string) ?? props.emoji as string; (event.target as HTMLElement).title = title; } </script> @@ -58,27 +33,4 @@ function computeTitle(event: PointerEvent): void { height: 1.25em; vertical-align: -0.25em; } - -.custom { - height: 2.5em; - vertical-align: middle; - transition: transform 0.2s ease; - - &:hover { - transform: scale(1.2); - } -} - -.normal { - height: 1.25em; - vertical-align: -0.25em; - - &:hover { - transform: none; - } -} - -.noStyle { - height: auto !important; -} </style> diff --git a/packages/frontend/src/components/global/MkUserName.vue b/packages/frontend/src/components/global/MkUserName.vue index fc08310acc..4186a4a4fb 100644 --- a/packages/frontend/src/components/global/MkUserName.vue +++ b/packages/frontend/src/components/global/MkUserName.vue @@ -1,5 +1,5 @@ <template> -<Mfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap"/> +<Mfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap" :emoji-urls="user.emojis"/> </template> <script lang="ts" setup> diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 8639257003..560870f84c 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -5,6 +5,7 @@ import MkA from './global/MkA.vue'; import MkAcct from './global/MkAcct.vue'; import MkAvatar from './global/MkAvatar.vue'; import MkEmoji from './global/MkEmoji.vue'; +import MkCustomEmoji from './global/MkCustomEmoji.vue'; import MkUserName from './global/MkUserName.vue'; import MkEllipsis from './global/MkEllipsis.vue'; import MkTime from './global/MkTime.vue'; @@ -26,6 +27,7 @@ export default function(app: App) { app.component('MkAcct', MkAcct); app.component('MkAvatar', MkAvatar); app.component('MkEmoji', MkEmoji); + app.component('MkCustomEmoji', MkCustomEmoji); app.component('MkUserName', MkUserName); app.component('MkEllipsis', MkEllipsis); app.component('MkTime', MkTime); @@ -47,6 +49,7 @@ declare module '@vue/runtime-core' { MkAcct: typeof MkAcct; MkAvatar: typeof MkAvatar; MkEmoji: typeof MkEmoji; + MkCustomEmoji: typeof MkCustomEmoji; MkUserName: typeof MkUserName; MkEllipsis: typeof MkEllipsis; MkTime: typeof MkTime; diff --git a/packages/frontend/src/components/mfm.ts b/packages/frontend/src/components/mfm.ts index b9ed9002ac..7cfb19aeb6 100644 --- a/packages/frontend/src/components/mfm.ts +++ b/packages/frontend/src/components/mfm.ts @@ -4,6 +4,7 @@ import MkUrl from '@/components/global/MkUrl.vue'; import MkLink from '@/components/MkLink.vue'; import MkMention from '@/components/MkMention.vue'; import MkEmoji from '@/components/global/MkEmoji.vue'; +import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; import { concat } from '@/scripts/array'; import MkCode from '@/components/MkCode.vue'; import MkGoogle from '@/components/MkGoogle.vue'; @@ -47,6 +48,10 @@ export default defineComponent({ type: Boolean, default: true, }, + emojiUrls: { + type: Object, + default: null, + }, }, render() { @@ -301,20 +306,35 @@ export default defineComponent({ } case 'emojiCode': { - return [h(MkEmoji, { - key: Math.random(), - emoji: `:${token.props.name}:`, - normal: this.plain, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (this.author?.host == null) { + return [h(MkCustomEmoji, { + key: Math.random(), + name: token.props.name, + normal: this.plain, + host: null, + })]; + } else { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - host: this.author?.host, - })]; + if (this.emojiUrls && (this.emojiUrls[token.props.name] == null)) { + return [h('span', `:${token.props.name}:`)]; + } else { + return [h(MkCustomEmoji, { + key: Math.random(), + name: token.props.name, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + url: this.emojiUrls ? this.emojiUrls[token.props.name] : null, + normal: this.plain, + host: this.author.host, + })]; + } + } } case 'unicodeEmoji': { return [h(MkEmoji, { key: Math.random(), emoji: token.props.emoji, - normal: this.plain, })]; } diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index bc63d7159a..6d88feceaf 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -9,7 +9,10 @@ <img src="/client-assets/about-icon.png" alt="" class="icon" draggable="false" @load="iconLoaded" @click="gravity"/> <div class="misskey">Misskey</div> <div class="version">v{{ version }}</div> - <span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :is-reaction="false" :normal="true" :no-style="true"/></span> + <span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"> + <MkCustomEmoji v-if="emoji.emoji[0] === ':'" class="emoji" :name="emoji.emoji" :normal="true" :no-style="true"/> + <MkEmoji v-else class="emoji" :emoji="emoji.emoji" :normal="true" :no-style="true"/> + </span> </div> <button v-if="thereIsTreasure" class="_button treasure" @click="getTreasure"><img src="/fluent-emoji/1f3c6.png" class="treasureImg"></button> </div> diff --git a/packages/frontend/src/pages/settings/reaction.vue b/packages/frontend/src/pages/settings/reaction.vue index e307abe2d4..c8b47b8299 100644 --- a/packages/frontend/src/pages/settings/reaction.vue +++ b/packages/frontend/src/pages/settings/reaction.vue @@ -6,7 +6,8 @@ <Sortable v-model="reactions" class="zoaiodol" :item-key="item => item" :animation="150" :delay="100" :delay-on-touch-only="true"> <template #item="{element}"> <button class="_button item" @click="remove(element, $event)"> - <MkEmoji :emoji="element" :normal="true"/> + <MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true"/> + <MkEmoji v-else :emoji="element" :normal="true"/> </button> </template> <template #footer> |