feat: show emoji tooltip (#2485)

This commit is contained in:
Shinigami 2023-12-22 13:16:46 +01:00 committed by GitHub
parent 74138a9a58
commit c0bb6e293c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 4 deletions

1
.gitignore vendored
View file

@ -2,6 +2,7 @@ node_modules
*.log *.log
dist dist
.output .output
.pnpm-store
.nuxt .nuxt
.env .env
.DS_Store .DS_Store

View file

@ -0,0 +1,30 @@
<script setup lang="ts">
const { as, alt, dataEmojiId } = $defineProps<{
as: string
alt?: string
dataEmojiId?: string
}>()
let title = $ref<string | undefined>()
if (alt) {
if (alt.startsWith(':')) {
title = alt.replace(/:/g, '')
}
else {
import('node-emoji').then(({ find }) => {
title = find(alt)?.key.replace(/_/g, ' ')
})
}
}
// if it has a data-emoji-id, use that as the title instead
if (dataEmojiId)
title = dataEmojiId
</script>
<template>
<component :is="as" v-bind="$attrs" :alt="alt" :data-emoji-id="dataEmojiId" :title="title">
<slot />
</component>
</template>

View file

@ -6,6 +6,7 @@ import { RouterLink } from 'vue-router'
import { decode } from 'tiny-decode' import { decode } from 'tiny-decode'
import type { ContentParseOptions } from './content-parse' import type { ContentParseOptions } from './content-parse'
import { parseMastodonHTML } from './content-parse' import { parseMastodonHTML } from './content-parse'
import Emoji from '~/components/emoji/Emoji.vue'
import ContentCode from '~/components/content/ContentCode.vue' import ContentCode from '~/components/content/ContentCode.vue'
import ContentMentionGroup from '~/components/content/ContentMentionGroup.vue' import ContentMentionGroup from '~/components/content/ContentMentionGroup.vue'
import AccountHoverWrapper from '~/components/account/AccountHoverWrapper.vue' import AccountHoverWrapper from '~/components/account/AccountHoverWrapper.vue'
@ -19,8 +20,11 @@ function getTextualAstComponents(astChildren: Node[]): string {
} }
/** /**
* Raw HTML to VNodes * Raw HTML to VNodes.
*/ *
* @param content HTML content.
* @param options Options.
*/
export function contentToVNode( export function contentToVNode(
content: string, content: string,
options?: ContentParseOptions, options?: ContentParseOptions,
@ -43,6 +47,17 @@ export function nodeToVNode(node: Node): VNode | string | null {
if (node.name === 'mention-group') if (node.name === 'mention-group')
return h(ContentMentionGroup, node.attributes, () => node.children.map(treeToVNode)) return h(ContentMentionGroup, node.attributes, () => node.children.map(treeToVNode))
// add tooltip to emojis
if (node.name === 'picture' || (node.name === 'img' && node.attributes?.alt)) {
const props = node.attributes ?? {}
props.as = node.name
return h(
Emoji,
props,
() => node.children.map(treeToVNode),
)
}
if ('children' in node) { if ('children' in node) {
if (node.name === 'a' && (node.attributes.href?.startsWith('/') || node.attributes.href?.startsWith('.'))) { if (node.name === 'a' && (node.attributes.href?.startsWith('/') || node.attributes.href?.startsWith('.'))) {
node.attributes.to = node.attributes.href node.attributes.to = node.attributes.href

View file

@ -79,6 +79,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lru-cache": "^10.0.0", "lru-cache": "^10.0.0",
"masto": "^5.11.3", "masto": "^5.11.3",
"node-emoji": "^2.1.3",
"nuxt-security": "^0.13.1", "nuxt-security": "^0.13.1",
"page-lifecycle": "^0.1.2", "page-lifecycle": "^0.1.2",
"pinia": "^2.1.4", "pinia": "^2.1.4",

View file

@ -164,6 +164,9 @@ importers:
masto: masto:
specifier: ^5.11.3 specifier: ^5.11.3
version: 5.11.3 version: 5.11.3
node-emoji:
specifier: ^2.1.3
version: 2.1.3
nuxt-security: nuxt-security:
specifier: ^0.13.1 specifier: ^0.13.1
version: 0.13.1(patch_hash=bd6cmp7ukwwiwrxafbbotwkihe)(rollup@2.79.1) version: 0.13.1(patch_hash=bd6cmp7ukwwiwrxafbbotwkihe)(rollup@2.79.1)
@ -4141,6 +4144,11 @@ packages:
/@sinclair/typebox@0.27.8: /@sinclair/typebox@0.27.8:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
/@sindresorhus/is@4.6.0:
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
engines: {node: '>=10'}
dev: false
/@sindresorhus/merge-streams@1.0.0: /@sindresorhus/merge-streams@1.0.0:
resolution: {integrity: sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==} resolution: {integrity: sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -6865,6 +6873,11 @@ packages:
snake-case: 3.0.4 snake-case: 3.0.4
tslib: 2.6.0 tslib: 2.6.0
/char-regex@1.0.2:
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
engines: {node: '>=10'}
dev: false
/char-regex@2.0.1: /char-regex@2.0.1:
resolution: {integrity: sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==} resolution: {integrity: sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==}
engines: {node: '>=12.20'} engines: {node: '>=12.20'}
@ -7573,6 +7586,10 @@ packages:
/emoji-regex@9.2.2: /emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
/emojilib@2.4.0:
resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==}
dev: false
/emoticon@4.0.1: /emoticon@4.0.1:
resolution: {integrity: sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==} resolution: {integrity: sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==}
dev: true dev: true
@ -10773,6 +10790,16 @@ packages:
lodash: 4.17.21 lodash: 4.17.21
dev: true dev: true
/node-emoji@2.1.3:
resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==}
engines: {node: '>=18'}
dependencies:
'@sindresorhus/is': 4.6.0
char-regex: 1.0.2
emojilib: 2.4.0
skin-tone: 2.0.0
dev: false
/node-fetch-native@1.4.1: /node-fetch-native@1.4.1:
resolution: {integrity: sha512-NsXBU0UgBxo2rQLOeWNZqS3fvflWePMECr8CoSWoSTqCqGbVVsvl9vZu1HfQicYN0g5piV9Gh8RTEvo/uP752w==} resolution: {integrity: sha512-NsXBU0UgBxo2rQLOeWNZqS3fvflWePMECr8CoSWoSTqCqGbVVsvl9vZu1HfQicYN0g5piV9Gh8RTEvo/uP752w==}
@ -13007,6 +13034,13 @@ packages:
/sisteransi@1.0.5: /sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
/skin-tone@2.0.0:
resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==}
engines: {node: '>=8'}
dependencies:
unicode-emoji-modifier-base: 1.0.0
dev: false
/slash@3.0.0: /slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -13837,6 +13871,11 @@ packages:
engines: {node: '>=4'} engines: {node: '>=4'}
dev: false dev: false
/unicode-emoji-modifier-base@1.0.0:
resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==}
engines: {node: '>=4'}
dev: false
/unicode-match-property-ecmascript@2.0.0: /unicode-match-property-ecmascript@2.0.0:
resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==}
engines: {node: '>=4'} engines: {node: '>=4'}

View file

@ -58,7 +58,7 @@ exports[`content-rich > code frame no lang 1`] = `"<p><pre class="code-block">he
exports[`content-rich > custom emoji 1`] = ` exports[`content-rich > custom emoji 1`] = `
"Daniel Roe "Daniel Roe
<picture alt=":nuxt:" class="custom-emoji" data-emoji-id="nuxt" <picture class="custom-emoji" alt=":nuxt:" data-emoji-id="nuxt" title="nuxt"
><source ><source
srcset=" srcset="
https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png
@ -67,6 +67,7 @@ exports[`content-rich > custom emoji 1`] = `
<img <img
src="https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png" src="https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png"
alt=":nuxt:" alt=":nuxt:"
title="nuxt"
/></picture> /></picture>
" "
`; `;
@ -129,8 +130,8 @@ exports[`content-rich > link + mention 1`] = `
Happy Happy
<img <img
src="/emojis/twemoji/1f917.svg" src="/emojis/twemoji/1f917.svg"
alt="🤗"
class="iconify-emoji iconify-emoji--twemoji" class="iconify-emoji iconify-emoji--twemoji"
alt="🤗"
/> />
were now using were now using
<span class="h-card" <span class="h-card"