Skip to content

Commit

Permalink
feat: replace emoji with SVGs (#129) (#584)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <[email protected]>
  • Loading branch information
cyberalien and antfu authored Jan 2, 2023
1 parent 41c5f94 commit fa9c418
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 102 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dist
.netlify/

public/shiki
public/emojis

*~
*swp
Expand Down
54 changes: 33 additions & 21 deletions composables/content-parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
import type { Emoji } from 'masto'
import type { Node } from 'ultrahtml'
import { TEXT_NODE, parse, render, walkSync } from 'ultrahtml'
import createEmojiRegex from 'emoji-regex'

export const EMOJI_REGEX = createEmojiRegex()
import { findAndReplaceEmojisInText } from '@iconify/utils'
import { emojiRegEx, getEmojiAttributes } from '../config/emojis'

const decoder = process.client ? document.createElement('textarea') : null as any as HTMLTextAreaElement
export function decodeHtml(text: string) {
Expand All @@ -16,17 +15,17 @@ export function decodeHtml(text: string) {
* Parse raw HTML form Mastodon server to AST,
* with interop of custom emojis and inline Markdown syntax
*/
export function parseMastodonHTML(html: string, customEmojis: Record<string, Emoji> = {}, markdown = true) {
let processed = html
// custom emojis
.replace(/:([\w-]+?):/g, (_, name) => {
const emoji = customEmojis[name]

return emoji
? `<img src="${emoji.url}" alt=":${name}:" class="custom-emoji" data-emoji-id="${name}" />`
: `:${name}:`
})
.replace(EMOJI_REGEX, '<em-emoji native="$&" fallback="$&" />')
export function parseMastodonHTML(html: string, customEmojis: Record<string, Emoji> = {}, markdown = true, forTiptap = false) {
// unicode emojis to images, but only if not converting HTML for Tiptap
let processed = forTiptap ? html : replaceUnicodeEmoji(html)

// custom emojis
processed = processed.replace(/:([\w-]+?):/g, (_, name) => {
const emoji = customEmojis[name]
if (emoji)
return `<img src="${emoji.url}" alt=":${name}:" class="custom-emoji" data-emoji-id="${name}" />`
return `:${name}:`
})

if (markdown) {
// handle code blocks
Expand Down Expand Up @@ -66,8 +65,11 @@ export function parseMastodonHTML(html: string, customEmojis: Record<string, Emo
return parse(processed)
}

/**
* Converts raw HTML form Mastodon server to HTML for Tiptap editor
*/
export function convertMastodonHTML(html: string, customEmojis: Record<string, Emoji> = {}) {
const tree = parseMastodonHTML(html, customEmojis)
const tree = parseMastodonHTML(html, customEmojis, true, true)
return render(tree)
}

Expand Down Expand Up @@ -118,12 +120,22 @@ export function treeToText(input: Node): string {
if ('children' in input)
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' && input.attributes.class?.includes('custom-emoji'))
return ` :${input.attributes['data-emoji-id']}: `

if (input.name === 'em-emoji')
return `${input.attributes.native}`
if (input.name === 'img') {
if (input.attributes.class?.includes('custom-emoji'))
return `:${input.attributes['data-emoji-id']}:`
if (input.attributes.class?.includes('iconify-emoji'))
return input.attributes.alt
}

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
}
26 changes: 12 additions & 14 deletions composables/tiptap/emoji.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
mergeAttributes,
nodeInputRule,
} from '@tiptap/core'
import { emojiRegEx, getEmojiAttributes } from '~/config/emojis'

export const Emoji = Node.create({
name: 'em-emoji',
Expand All @@ -14,50 +15,47 @@ export const Emoji = Node.create({
parseHTML() {
return [
{
tag: 'em-emoji[native]',
tag: 'img.iconify-emoji',
},
]
},

addAttributes() {
return {
native: {
alt: {
default: null,
},
fallback: {
src: {
default: null,
},
class: {
default: null,
},
}
},

renderHTML(args) {
return ['em-emoji', mergeAttributes(this.options.HTMLAttributes, args.HTMLAttributes)]
return ['img', mergeAttributes(this.options.HTMLAttributes, args.HTMLAttributes)]
},

addCommands() {
return {
insertEmoji: name => ({ commands }) => {
insertEmoji: code => ({ commands }) => {
return commands.insertContent({
type: this.name,
attrs: {
native: name,
fallback: name,
},
attrs: getEmojiAttributes(code),
})
},
}
},

addInputRules() {
const inputRule = nodeInputRule({
find: EMOJI_REGEX,
find: emojiRegEx as RegExp,
type: this.type,
getAttributes: (match) => {
const [native] = match
return {
native,
fallback: native,
}
return getEmojiAttributes(native)
},
})
// Error catch for unsupported emoji
Expand Down
22 changes: 22 additions & 0 deletions config/emojis.ts
Original file line number Diff line number Diff line change
@@ -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,
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
},
"dependencies": {
"@fnando/sparkline": "^0.3.10",
"@iconify-emoji/twemoji": "^1.0.2",
"@iconify/utils": "^2.0.7",
"@nuxtjs/color-mode": "^3.2.0",
"@tiptap/extension-character-count": "2.0.0-beta.204",
"@tiptap/extension-code-block": "2.0.0-beta.204",
Expand Down
9 changes: 0 additions & 9 deletions plugins/setup-emojis.ts

This file was deleted.

Loading

0 comments on commit fa9c418

Please sign in to comment.