feat: replace emoji with SVGs (#129) (#584)

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
This commit is contained in:
Vjacheslav Trushkin 2023-01-02 06:53:53 +02:00 committed by GitHub
parent 41c5f94fbf
commit fa9c418e21
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 136 additions and 101 deletions

1
.gitignore vendored
View file

@ -10,6 +10,7 @@ dist
.netlify/ .netlify/
public/shiki public/shiki
public/emojis
*~ *~
*swp *swp

View file

@ -2,9 +2,8 @@
import type { Emoji } from 'masto' import type { Emoji } from 'masto'
import type { Node } from 'ultrahtml' import type { Node } from 'ultrahtml'
import { TEXT_NODE, parse, render, walkSync } from 'ultrahtml' import { TEXT_NODE, parse, render, walkSync } from 'ultrahtml'
import createEmojiRegex from 'emoji-regex' import { findAndReplaceEmojisInText } from '@iconify/utils'
import { emojiRegEx, getEmojiAttributes } from '../config/emojis'
export const EMOJI_REGEX = createEmojiRegex()
const decoder = process.client ? document.createElement('textarea') : null as any as HTMLTextAreaElement const decoder = process.client ? document.createElement('textarea') : null as any as HTMLTextAreaElement
export function decodeHtml(text: string) { export function decodeHtml(text: string) {
@ -16,17 +15,17 @@ export function decodeHtml(text: string) {
* Parse raw HTML form Mastodon server to AST, * Parse raw HTML form Mastodon server to AST,
* with interop of custom emojis and inline Markdown syntax * with interop of custom emojis and inline Markdown syntax
*/ */
export function parseMastodonHTML(html: string, customEmojis: Record<string, Emoji> = {}, markdown = true) { export function parseMastodonHTML(html: string, customEmojis: Record<string, Emoji> = {}, markdown = true, forTiptap = false) {
let processed = html // unicode emojis to images, but only if not converting HTML for Tiptap
// custom emojis let processed = forTiptap ? html : replaceUnicodeEmoji(html)
.replace(/:([\w-]+?):/g, (_, name) => {
const emoji = customEmojis[name]
return emoji // custom emojis
? `<img src="${emoji.url}" alt=":${name}:" class="custom-emoji" data-emoji-id="${name}" />` processed = processed.replace(/:([\w-]+?):/g, (_, name) => {
: `:${name}:` const emoji = customEmojis[name]
if (emoji)
return `<img src="${emoji.url}" alt=":${name}:" class="custom-emoji" data-emoji-id="${name}" />`
return `:${name}:`
}) })
.replace(EMOJI_REGEX, '<em-emoji native="$&" fallback="$&" />')
if (markdown) { if (markdown) {
// handle code blocks // handle code blocks
@ -66,8 +65,11 @@ export function parseMastodonHTML(html: string, customEmojis: Record<string, Emo
return parse(processed) return parse(processed)
} }
/**
* Converts raw HTML form Mastodon server to HTML for Tiptap editor
*/
export function convertMastodonHTML(html: string, customEmojis: Record<string, Emoji> = {}) { export function convertMastodonHTML(html: string, customEmojis: Record<string, Emoji> = {}) {
const tree = parseMastodonHTML(html, customEmojis) const tree = parseMastodonHTML(html, customEmojis, true, true)
return render(tree) return render(tree)
} }
@ -118,12 +120,22 @@ export function treeToText(input: Node): string {
if ('children' in input) if ('children' in input)
body = (input.children as Node[]).map(n => treeToText(n)).join('') body = (input.children as Node[]).map(n => treeToText(n)).join('')
// add spaces around emoji to prevent parsing errors: 2 or more consecutive emojis will not be parsed if (input.name === 'img') {
if (input.name === 'img' && input.attributes.class?.includes('custom-emoji')) if (input.attributes.class?.includes('custom-emoji'))
return `:${input.attributes['data-emoji-id']}:` return `:${input.attributes['data-emoji-id']}:`
if (input.attributes.class?.includes('iconify-emoji'))
if (input.name === 'em-emoji') return input.attributes.alt
return `${input.attributes.native}` }
return pre + body + post return pre + body + post
} }
/**
* Replace unicode emojis with locally hosted images
*/
export function replaceUnicodeEmoji(html: string) {
return findAndReplaceEmojisInText(emojiRegEx, html, (match) => {
const attrs = getEmojiAttributes(match)
return `<img src="${attrs.src}" alt="${attrs.alt}" class="${attrs.class}" />`
}) || html
}

View file

@ -3,6 +3,7 @@ import {
mergeAttributes, mergeAttributes,
nodeInputRule, nodeInputRule,
} from '@tiptap/core' } from '@tiptap/core'
import { emojiRegEx, getEmojiAttributes } from '~/config/emojis'
export const Emoji = Node.create({ export const Emoji = Node.create({
name: 'em-emoji', name: 'em-emoji',
@ -14,35 +15,35 @@ export const Emoji = Node.create({
parseHTML() { parseHTML() {
return [ return [
{ {
tag: 'em-emoji[native]', tag: 'img.iconify-emoji',
}, },
] ]
}, },
addAttributes() { addAttributes() {
return { return {
native: { alt: {
default: null, default: null,
}, },
fallback: { src: {
default: null,
},
class: {
default: null, default: null,
}, },
} }
}, },
renderHTML(args) { renderHTML(args) {
return ['em-emoji', mergeAttributes(this.options.HTMLAttributes, args.HTMLAttributes)] return ['img', mergeAttributes(this.options.HTMLAttributes, args.HTMLAttributes)]
}, },
addCommands() { addCommands() {
return { return {
insertEmoji: name => ({ commands }) => { insertEmoji: code => ({ commands }) => {
return commands.insertContent({ return commands.insertContent({
type: this.name, type: this.name,
attrs: { attrs: getEmojiAttributes(code),
native: name,
fallback: name,
},
}) })
}, },
} }
@ -50,14 +51,11 @@ export const Emoji = Node.create({
addInputRules() { addInputRules() {
const inputRule = nodeInputRule({ const inputRule = nodeInputRule({
find: EMOJI_REGEX, find: emojiRegEx as RegExp,
type: this.type, type: this.type,
getAttributes: (match) => { getAttributes: (match) => {
const [native] = match const [native] = match
return { return getEmojiAttributes(native)
native,
fallback: native,
}
}, },
}) })
// Error catch for unsupported emoji // Error catch for unsupported emoji

22
config/emojis.ts Normal file
View file

@ -0,0 +1,22 @@
import { emojiFilename, emojiPrefix, emojiRegEx } from '@iconify-emoji/twemoji'
import type { EmojiRegexMatch } from '@iconify/utils/lib/emoji/replace/find'
import { getEmojiMatchesInText } from '@iconify/utils/lib/emoji/replace/find'
// Re-export everything from package
export * from '@iconify-emoji/twemoji'
// Package name
export const iconifyEmojiPackage = '@iconify-emoji/twemoji'
export function getEmojiAttributes(input: EmojiRegexMatch | string) {
const match = typeof input === 'string'
? getEmojiMatchesInText(emojiRegEx, input)?.[0]
: input
const file = emojiFilename(match)
const className = `iconify-emoji iconify-emoji--${emojiPrefix}${file.padding ? ' iconify-emoji-padded' : ''}`
return {
class: className,
src: `/emojis/${emojiPrefix}/${file.filename}`,
alt: match.match,
}
}

View file

@ -26,6 +26,8 @@
}, },
"dependencies": { "dependencies": {
"@fnando/sparkline": "^0.3.10", "@fnando/sparkline": "^0.3.10",
"@iconify-emoji/twemoji": "^1.0.2",
"@iconify/utils": "^2.0.7",
"@nuxtjs/color-mode": "^3.2.0", "@nuxtjs/color-mode": "^3.2.0",
"@tiptap/extension-character-count": "2.0.0-beta.204", "@tiptap/extension-character-count": "2.0.0-beta.204",
"@tiptap/extension-code-block": "2.0.0-beta.204", "@tiptap/extension-code-block": "2.0.0-beta.204",

View file

@ -1,9 +0,0 @@
export default defineNuxtPlugin(() => {
if (process.server)
return
const promise = import('@emoji-mart/data').then(r => r.default)
import('emoji-mart').then(r => r.init({
data: () => promise,
}))
})

View file

@ -5,12 +5,14 @@ specifiers:
'@antfu/ni': ^0.18.8 '@antfu/ni': ^0.18.8
'@emoji-mart/data': ^1.1.0 '@emoji-mart/data': ^1.1.0
'@fnando/sparkline': ^0.3.10 '@fnando/sparkline': ^0.3.10
'@iconify-emoji/twemoji': ^1.0.2
'@iconify-json/carbon': ^1.1.11 '@iconify-json/carbon': ^1.1.11
'@iconify-json/logos': ^1.1.19 '@iconify-json/logos': ^1.1.19
'@iconify-json/material-symbols': ^1.1.26 '@iconify-json/material-symbols': ^1.1.26
'@iconify-json/ph': ^1.1.3 '@iconify-json/ph': ^1.1.3
'@iconify-json/ri': ^1.1.4 '@iconify-json/ri': ^1.1.4
'@iconify-json/twemoji': ^1.1.7 '@iconify-json/twemoji': ^1.1.7
'@iconify/utils': ^2.0.7
'@nuxtjs/color-mode': ^3.2.0 '@nuxtjs/color-mode': ^3.2.0
'@nuxtjs/i18n': ^8.0.0-beta.7 '@nuxtjs/i18n': ^8.0.0-beta.7
'@pinia/nuxt': ^0.4.6 '@pinia/nuxt': ^0.4.6
@ -86,6 +88,8 @@ specifiers:
dependencies: dependencies:
'@fnando/sparkline': 0.3.10 '@fnando/sparkline': 0.3.10
'@iconify-emoji/twemoji': 1.0.2
'@iconify/utils': 2.0.8
'@nuxtjs/color-mode': 3.2.0 '@nuxtjs/color-mode': 3.2.0
'@tiptap/extension-character-count': 2.0.0-beta.204 '@tiptap/extension-character-count': 2.0.0-beta.204
'@tiptap/extension-code-block': 2.0.0-beta.204 '@tiptap/extension-code-block': 2.0.0-beta.204
@ -273,7 +277,6 @@ packages:
dependencies: dependencies:
execa: 5.1.1 execa: 5.1.1
find-up: 5.0.0 find-up: 5.0.0
dev: true
/@antfu/ni/0.18.8: /@antfu/ni/0.18.8:
resolution: {integrity: sha512-0m++AudwQq+wWAz/Ax7g+sh/wFW51HHQ6BtPLsuTAsFIzWB/bv/0COwZE7BRS+u0nqMb6Ks6nlk6cY1TpPDwHg==} resolution: {integrity: sha512-0m++AudwQq+wWAz/Ax7g+sh/wFW51HHQ6BtPLsuTAsFIzWB/bv/0COwZE7BRS+u0nqMb6Ks6nlk6cY1TpPDwHg==}
@ -286,7 +289,6 @@ packages:
/@antfu/utils/0.7.2: /@antfu/utils/0.7.2:
resolution: {integrity: sha512-vy9fM3pIxZmX07dL+VX1aZe7ynZ+YyB0jY+jE6r3hOK6GNY2t6W8rzpFC4tgpbXUYABkFQwgJq2XYXlxbXAI0g==} resolution: {integrity: sha512-vy9fM3pIxZmX07dL+VX1aZe7ynZ+YyB0jY+jE6r3hOK6GNY2t6W8rzpFC4tgpbXUYABkFQwgJq2XYXlxbXAI0g==}
dev: true
/@apideck/better-ajv-errors/0.3.6_ajv@8.11.2: /@apideck/better-ajv-errors/0.3.6_ajv@8.11.2:
resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==}
@ -1564,6 +1566,10 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true dev: true
/@iconify-emoji/twemoji/1.0.2:
resolution: {integrity: sha512-C4W6ov4BkDXiVU3GzyqyVo8SBbU21KivXnZERgAnrYZEKjuiI3JwPDnu9oVJPsUkNI/Q4SM8iVnXjGW6kxt9DQ==}
dev: false
/@iconify-json/carbon/1.1.11: /@iconify-json/carbon/1.1.11:
resolution: {integrity: sha512-IHkHSNmTM6q6b8DuKSzd+AEMYPZywSxcb+37kZU7ywtcwsGen3aVEvWFykopIWjjwj3xdZ/5UdwJRqhZDQMlNg==} resolution: {integrity: sha512-IHkHSNmTM6q6b8DuKSzd+AEMYPZywSxcb+37kZU7ywtcwsGen3aVEvWFykopIWjjwj3xdZ/5UdwJRqhZDQMlNg==}
dependencies: dependencies:
@ -1602,20 +1608,18 @@ packages:
/@iconify/types/2.0.0: /@iconify/types/2.0.0:
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
dev: true
/@iconify/utils/2.0.5: /@iconify/utils/2.0.8:
resolution: {integrity: sha512-UMT1WhBkr7oYggc69dFl/1RHE9XDisCrWaKXXQLpIccLCytHWZEX3247b/wR+sexYIKBSWs8YIKmMBe3g4oGCw==} resolution: {integrity: sha512-e/1Rng92uxQTM+481EZaV1t7S03PFKIiWyc7io2/923DRUvOMcB1hwP6a2dvQ1Uf/0ncwXcwD+5bMTOkZlEdYw==}
dependencies: dependencies:
'@antfu/install-pkg': 0.1.1 '@antfu/install-pkg': 0.1.1
'@antfu/utils': 0.5.2 '@antfu/utils': 0.7.2
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
debug: 4.3.4 debug: 4.3.4
kolorist: 1.6.0 kolorist: 1.6.0
local-pkg: 0.4.2 local-pkg: 0.4.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true
/@intlify/bundle-utils/3.4.0_vue-i18n@9.3.0-beta.10: /@intlify/bundle-utils/3.4.0_vue-i18n@9.3.0-beta.10:
resolution: {integrity: sha512-2UQkqiSAOSPEHMGWlybqWm4G2K0X+FyYho5AwXz6QklSX1EY5EDmOSxZmwscn2qmKBnp6OYsme5kUrnN9xrWzQ==} resolution: {integrity: sha512-2UQkqiSAOSPEHMGWlybqWm4G2K0X+FyYho5AwXz6QklSX1EY5EDmOSxZmwscn2qmKBnp6OYsme5kUrnN9xrWzQ==}
@ -1629,8 +1633,8 @@ packages:
vue-i18n: vue-i18n:
optional: true optional: true
dependencies: dependencies:
'@intlify/message-compiler': 9.3.0-beta.12 '@intlify/message-compiler': 9.3.0-beta.11
'@intlify/shared': 9.3.0-beta.12 '@intlify/shared': 9.3.0-beta.11
jsonc-eslint-parser: 1.4.1 jsonc-eslint-parser: 1.4.1
source-map: 0.6.1 source-map: 0.6.1
vue-i18n: 9.3.0-beta.10 vue-i18n: 9.3.0-beta.10
@ -1662,8 +1666,8 @@ packages:
source-map: 0.6.1 source-map: 0.6.1
dev: true dev: true
/@intlify/message-compiler/9.3.0-beta.12: /@intlify/message-compiler/9.3.0-beta.11:
resolution: {integrity: sha512-A8/s7pb3v8nf6HG77qFPJntxgQKI9GXxGnkn7aO+b03/X/GkF/4WceDSAIk3i+yLeIgszeBn9GZ23tSg4sTEHA==} resolution: {integrity: sha512-gGGfBGzM7JBXp1Q9gbDAy5jELz9ho3ILqnpxp2yp64+gkqohrqc2YXIvCdwZoc6AtKIh/Zmv4sWVqxkvMsBWtQ==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
dependencies: dependencies:
'@intlify/shared': 9.3.0-beta.11 '@intlify/shared': 9.3.0-beta.11
@ -1680,11 +1684,6 @@ packages:
engines: {node: '>= 14'} engines: {node: '>= 14'}
dev: true dev: true
/@intlify/shared/9.3.0-beta.12:
resolution: {integrity: sha512-WsmaS54sA8xuwezPKpa/OMoaX1v2VF2fCgAmYS6prDr2ir0CkUFWPm9A8ilmxzv4nkS61/v8+vf4lGGkn5uBdA==}
engines: {node: '>= 14'}
dev: true
/@intlify/unplugin-vue-i18n/0.8.0_vue-i18n@9.3.0-beta.10: /@intlify/unplugin-vue-i18n/0.8.0_vue-i18n@9.3.0-beta.10:
resolution: {integrity: sha512-bqMDYrbmV0oMLGHTdYMUXfcEsy2rPwQnGrQAg4gvw5FimvJfTQt3RliLVayT5ldOfeT2g0IUc/0t7LPeGrFUag==} resolution: {integrity: sha512-bqMDYrbmV0oMLGHTdYMUXfcEsy2rPwQnGrQAg4gvw5FimvJfTQt3RliLVayT5ldOfeT2g0IUc/0t7LPeGrFUag==}
engines: {node: '>= 14.16'} engines: {node: '>= 14.16'}
@ -1701,7 +1700,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@intlify/bundle-utils': 3.4.0_vue-i18n@9.3.0-beta.10 '@intlify/bundle-utils': 3.4.0_vue-i18n@9.3.0-beta.10
'@intlify/shared': 9.3.0-beta.12 '@intlify/shared': 9.3.0-beta.11
'@rollup/pluginutils': 4.2.1 '@rollup/pluginutils': 4.2.1
'@vue/compiler-sfc': 3.2.45 '@vue/compiler-sfc': 3.2.45
debug: 4.3.4 debug: 4.3.4
@ -2968,7 +2967,7 @@ packages:
/@unocss/preset-icons/0.48.0: /@unocss/preset-icons/0.48.0:
resolution: {integrity: sha512-3vro36gTkjEic5rO9BcUudby8tQ9ZRCduKZ1+4CKP0hKoB58nDm1QZM+kvWQ8RVN2xoSU9vWkHhx1RLl8miE0g==} resolution: {integrity: sha512-3vro36gTkjEic5rO9BcUudby8tQ9ZRCduKZ1+4CKP0hKoB58nDm1QZM+kvWQ8RVN2xoSU9vWkHhx1RLl8miE0g==}
dependencies: dependencies:
'@iconify/utils': 2.0.5 '@iconify/utils': 2.0.8
'@unocss/core': 0.48.0 '@unocss/core': 0.48.0
ohmyfetch: 0.4.21 ohmyfetch: 0.4.21
transitivePeerDependencies: transitivePeerDependencies:
@ -4490,7 +4489,6 @@ packages:
path-key: 3.1.1 path-key: 3.1.1
shebang-command: 2.0.0 shebang-command: 2.0.0
which: 2.0.2 which: 2.0.2
dev: true
/crypto-random-string/2.0.0: /crypto-random-string/2.0.0:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
@ -5651,7 +5649,6 @@ packages:
onetime: 5.1.2 onetime: 5.1.2
signal-exit: 3.0.7 signal-exit: 3.0.7
strip-final-newline: 2.0.0 strip-final-newline: 2.0.0
dev: true
/execa/6.1.0: /execa/6.1.0:
resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==}
@ -5770,7 +5767,6 @@ packages:
dependencies: dependencies:
locate-path: 6.0.0 locate-path: 6.0.0
path-exists: 4.0.0 path-exists: 4.0.0
dev: true
/flat-cache/3.0.4: /flat-cache/3.0.4:
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
@ -5982,7 +5978,6 @@ packages:
/get-stream/6.0.1: /get-stream/6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/get-symbol-description/1.0.0: /get-symbol-description/1.0.0:
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
@ -6260,7 +6255,6 @@ packages:
/human-signals/2.1.0: /human-signals/2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'} engines: {node: '>=10.17.0'}
dev: true
/human-signals/3.0.1: /human-signals/3.0.1:
resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==}
@ -6569,7 +6563,6 @@ packages:
/is-stream/2.0.1: /is-stream/2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true
/is-stream/3.0.0: /is-stream/3.0.0:
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
@ -6613,7 +6606,6 @@ packages:
/isexe/2.0.0: /isexe/2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
/iso-639-1/2.1.15: /iso-639-1/2.1.15:
resolution: {integrity: sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==} resolution: {integrity: sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==}
@ -6822,7 +6814,6 @@ packages:
/kolorist/1.6.0: /kolorist/1.6.0:
resolution: {integrity: sha512-dLkz37Ab97HWMx9KTes3Tbi3D1ln9fCAy2zr2YVExJasDRPGRaKcoE4fycWNtnCAJfjFqe0cnY+f8KT2JePEXQ==} resolution: {integrity: sha512-dLkz37Ab97HWMx9KTes3Tbi3D1ln9fCAy2zr2YVExJasDRPGRaKcoE4fycWNtnCAJfjFqe0cnY+f8KT2JePEXQ==}
dev: true
/lazystream/1.0.1: /lazystream/1.0.1:
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
@ -6932,7 +6923,6 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
p-locate: 5.0.0 p-locate: 5.0.0
dev: true
/lodash._reinterpolate/3.0.0: /lodash._reinterpolate/3.0.0:
resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==} resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==}
@ -7115,7 +7105,6 @@ packages:
/merge-stream/2.0.0: /merge-stream/2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true
/merge2/1.4.1: /merge2/1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
@ -7168,7 +7157,6 @@ packages:
/mimic-fn/2.1.0: /mimic-fn/2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true
/mimic-fn/4.0.0: /mimic-fn/4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
@ -7467,7 +7455,6 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
path-key: 3.1.1 path-key: 3.1.1
dev: true
/npm-run-path/5.1.0: /npm-run-path/5.1.0:
resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==}
@ -7643,7 +7630,6 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dependencies: dependencies:
mimic-fn: 2.1.0 mimic-fn: 2.1.0
dev: true
/onetime/6.0.0: /onetime/6.0.0:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
@ -7721,7 +7707,6 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
yocto-queue: 0.1.0 yocto-queue: 0.1.0
dev: true
/p-locate/4.1.0: /p-locate/4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
@ -7735,7 +7720,6 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
p-limit: 3.1.0 p-limit: 3.1.0
dev: true
/p-map/4.0.0: /p-map/4.0.0:
resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
@ -7832,7 +7816,6 @@ packages:
/path-exists/4.0.0: /path-exists/4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true
/path-is-absolute/1.0.1: /path-is-absolute/1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
@ -7842,7 +7825,6 @@ packages:
/path-key/3.1.1: /path-key/3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true
/path-key/4.0.0: /path-key/4.0.0:
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
@ -8825,12 +8807,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
shebang-regex: 3.0.0 shebang-regex: 3.0.0
dev: true
/shebang-regex/3.0.0: /shebang-regex/3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true
/shiki-es/0.1.2: /shiki-es/0.1.2:
resolution: {integrity: sha512-eqtfk8idlYlSLAn0gp0Ly2+FbKc2d78IddigHSS4iHAnpXoY2kdRzyFGZOdi6TvemYMnRhZBi1HsSqZc5eNKqg==} resolution: {integrity: sha512-eqtfk8idlYlSLAn0gp0Ly2+FbKc2d78IddigHSS4iHAnpXoY2kdRzyFGZOdi6TvemYMnRhZBi1HsSqZc5eNKqg==}
@ -8854,7 +8834,6 @@ packages:
/signal-exit/3.0.7: /signal-exit/3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: true
/simple-git-hooks/2.8.1: /simple-git-hooks/2.8.1:
resolution: {integrity: sha512-DYpcVR1AGtSfFUNzlBdHrQGPsOhuuEJ/FkmPOOlFysP60AHd3nsEpkGq/QEOdtUyT1Qhk7w9oLmFoMG+75BDog==} resolution: {integrity: sha512-DYpcVR1AGtSfFUNzlBdHrQGPsOhuuEJ/FkmPOOlFysP60AHd3nsEpkGq/QEOdtUyT1Qhk7w9oLmFoMG+75BDog==}
@ -9109,7 +9088,6 @@ packages:
/strip-final-newline/2.0.0: /strip-final-newline/2.0.0:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true
/strip-final-newline/3.0.0: /strip-final-newline/3.0.0:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
@ -10200,7 +10178,7 @@ packages:
vue-router: vue-router:
optional: true optional: true
dependencies: dependencies:
'@intlify/shared': 9.3.0-beta.12 '@intlify/shared': 9.3.0-beta.11
'@intlify/vue-i18n-bridge': 0.8.0_vue-i18n@9.3.0-beta.10 '@intlify/vue-i18n-bridge': 0.8.0_vue-i18n@9.3.0-beta.10
'@intlify/vue-router-bridge': 0.8.0 '@intlify/vue-router-bridge': 0.8.0
ufo: 1.0.1 ufo: 1.0.1
@ -10376,7 +10354,6 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
isexe: 2.0.0 isexe: 2.0.0
dev: true
/wide-align/1.1.5: /wide-align/1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
@ -10652,7 +10629,6 @@ packages:
/yocto-queue/0.1.0: /yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/zip-stream/4.1.0: /zip-stream/4.1.0:
resolution: {integrity: sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==} resolution: {integrity: sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==}

View file

@ -1,4 +1,5 @@
import { copy } from 'fs-extra' import { copy } from 'fs-extra'
import { emojiPrefix, iconifyEmojiPackage } from '../config/emojis'
const dereference = process.platform === 'win32' ? true : undefined const dereference = process.platform === 'win32' ? true : undefined
@ -8,3 +9,4 @@ await copy('node_modules/shiki-es/dist/assets', 'public/shiki/', {
}) })
await copy('node_modules/theme-vitesse/themes', 'public/shiki/themes', { dereference }) await copy('node_modules/theme-vitesse/themes', 'public/shiki/themes', { dereference })
await copy('node_modules/theme-vitesse/themes', 'node_modules/shiki/themes', { overwrite: true, dereference }) await copy('node_modules/theme-vitesse/themes', 'node_modules/shiki/themes', { overwrite: true, dereference })
await copy(`node_modules/${iconifyEmojiPackage}/icons`, `public/emojis/${emojiPrefix}`, { overwrite: true, dereference })

View file

@ -30,11 +30,6 @@ html {
font-weight: 400; font-weight: 400;
src: url(/fonts/homemade-apple-v18.ttf) format('truetype'); src: url(/fonts/homemade-apple-v18.ttf) format('truetype');
} }
@font-face {
font-display: swap;
font-family: 'EmojiMart';
src: url('/fonts/seguiemj.ttf') format('truetype');
}
* { * {
scrollbar-color: #8885 var(--c-border); scrollbar-color: #8885 var(--c-border);
@ -88,6 +83,19 @@ body {
vertical-align: text-bottom; vertical-align: text-bottom;
} }
.iconify-emoji {
display: inline-block;
overflow: hidden;
max-height: 1.2em;
max-width: 1.2em;
vertical-align: text-bottom;
margin: 0 0.1em;
}
.iconify-emoji-padded {
transform: scale(1.2);
}
.content-rich { .content-rich {
line-height: calc(4 / 3 * 1em); line-height: calc(4 / 3 * 1em);
overflow-wrap: break-word; overflow-wrap: break-word;
@ -195,10 +203,6 @@ html[dir="rtl"] .rtl-flip {
em-emoji-picker { em-emoji-picker {
--border-radius: 0; --border-radius: 0;
} }
em-emoji {
font-size: 1.2em;
line-height: 1em;
}
footer { footer {
a { a {

View file

@ -36,7 +36,13 @@ exports[`content-rich > empty 1`] = `""`;
exports[`content-rich > link + mention 1`] = ` exports[`content-rich > link + mention 1`] = `
"<p> "<p>
Happy <em-emoji native=\\"🤗\\" fallback=\\"🤗\\"></em-emoji> were now using Happy
<img
src=\\"/emojis/twemoji/1f917.svg\\"
alt=\\"🤗\\"
class=\\"iconify-emoji iconify-emoji--twemoji\\"
/>
were now using
<span class=\\"h-card\\" <span class=\\"h-card\\"
><a ><a
class=\\"u-url mention\\" class=\\"u-url mention\\"

View file

@ -55,10 +55,25 @@ exports[`html-parse > custom emoji > html 1`] = `
exports[`html-parse > custom emoji > text 1`] = `"Daniel Roe :nuxt:"`; exports[`html-parse > custom emoji > text 1`] = `"Daniel Roe :nuxt:"`;
exports[`html-parse > emojis > html 1`] = ` exports[`html-parse > emojis > html 1`] = `
"<em-emoji native=\\"🇫🇷\\" fallback=\\"🇫🇷\\"></em-emoji> "<img
<em-emoji native=\\"👨‍👩‍👦\\" fallback=\\"👨‍👩‍👦\\"></em-emoji> src=\\"/emojis/twemoji/1f1eb-1f1f7.svg\\"
<em-emoji native=\\"👩‍🚒\\" fallback=\\"👩‍🚒\\"></em-emoji alt=\\"🇫🇷\\"
><em-emoji native=\\"🧑🏽‍🚀\\" fallback=\\"🧑🏽‍🚀\\"></em-emoji> class=\\"iconify-emoji iconify-emoji--twemoji\\"
/>
<img
src=\\"/emojis/twemoji/1f468-200d-1f469-200d-1f466.svg\\"
alt=\\"👨‍👩‍👦\\"
class=\\"iconify-emoji iconify-emoji--twemoji\\"
/>
<img
src=\\"/emojis/twemoji/1f469-200d-1f692.svg\\"
alt=\\"👩‍🚒\\"
class=\\"iconify-emoji iconify-emoji--twemoji\\"
/><img
src=\\"/emojis/twemoji/1f9d1-1f3fd-200d-1f680.svg\\"
alt=\\"🧑🏽‍🚀\\"
class=\\"iconify-emoji iconify-emoji--twemoji\\"
/>
" "
`; `;
@ -87,7 +102,13 @@ code block
exports[`html-parse > link + mention > html 1`] = ` exports[`html-parse > link + mention > html 1`] = `
"<p> "<p>
Happy <em-emoji native=\\"🤗\\" fallback=\\"🤗\\"></em-emoji> were now using Happy
<img
src=\\"/emojis/twemoji/1f917.svg\\"
alt=\\"🤗\\"
class=\\"iconify-emoji iconify-emoji--twemoji\\"
/>
were now using
<span class=\\"h-card\\" <span class=\\"h-card\\"
><a ><a
href=\\"https://mas.to/@vitest\\" href=\\"https://mas.to/@vitest\\"