mirror of https://github.com/requarks/wiki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
4.6 KiB
124 lines
4.6 KiB
/*
|
|
Generates `src/assets/emoji.generated.js` — the grouping the emoji picker is built from.
|
|
|
|
Two datasets have to meet for a picker to work here:
|
|
|
|
- `markdown-it-emoji` is the renderer's own vocabulary: `:shortcode:` is what gets written into a
|
|
page, and anything outside that map would go in as text that never becomes an emoji. It has no
|
|
notion of categories.
|
|
- `unicode-emoji-json` carries the CLDR grouping and ordering — Smileys & Emotion, Animals & Nature,
|
|
and so on — which is what the picker's tabs are, and no notion of shortcodes.
|
|
|
|
So this writes out the intersection: each group, in Unicode's own order, as `[shortcode, character]`
|
|
pairs — the shortcode is what gets written into the page, the character is what the picker draws in its
|
|
grid. The picker cannot offer an emoji the renderer would not draw, because every pair came from the
|
|
renderer's own map.
|
|
|
|
The characters are written here rather than looked up at runtime, even though the editor already
|
|
bundles that map for rendering. Reading it would mean importing `markdown-it-emoji/lib/data/full.mjs`
|
|
from app code — a path into the package's internals rather than an entry point it publishes, and one
|
|
more dependency for the bundler to discover. A few kB of duplicated characters is the cheaper side of
|
|
that trade.
|
|
|
|
`unicode-emoji-json` is a devDependency for that reason: its data ends up here, and here is committed.
|
|
|
|
Usage: node scripts/generate-emoji.mjs [--check]
|
|
*/
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import emojiByGroup from 'unicode-emoji-json/data-by-group.json' with { type: 'json' }
|
|
import shortcodeToEmoji from 'markdown-it-emoji/lib/data/full.mjs'
|
|
|
|
const ROOT = fileURLToPath(new URL('../', import.meta.url))
|
|
const OUT = path.join(ROOT, 'src/assets/emoji.generated.js')
|
|
|
|
/**
|
|
* One shortcode per emoji, since several can point at the same character — `laughing` and `satisfied`
|
|
* are both 😆, `+1` and `thumbsup` are both 👍.
|
|
*
|
|
* The first one wins, except that a name starting with a letter beats one that does not: the map's own
|
|
* order is close to canonical, and `thumbsup` reads better in a page's source than `+1`.
|
|
*/
|
|
function buildShortcodeIndex() {
|
|
const index = new Map()
|
|
for (const [shortcode, emoji] of Object.entries(shortcodeToEmoji)) {
|
|
const current = index.get(emoji)
|
|
if (!current || (!/^[a-z]/.test(current) && /^[a-z]/.test(shortcode))) {
|
|
index.set(emoji, shortcode)
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
function build() {
|
|
const index = buildShortcodeIndex()
|
|
const groups = []
|
|
const claimed = new Set()
|
|
|
|
for (const group of emojiByGroup) {
|
|
const emoji = []
|
|
for (const entry of group.emojis) {
|
|
const shortcode = index.get(entry.emoji)
|
|
// -> Skipped rather than invented: an emoji the renderer has no shortcode for cannot be written
|
|
// into a page as an emoji, so it has no business in a picker
|
|
if (shortcode && !claimed.has(shortcode)) {
|
|
claimed.add(shortcode)
|
|
emoji.push([shortcode, entry.emoji])
|
|
}
|
|
}
|
|
if (emoji.length > 0) {
|
|
groups.push({ slug: group.slug, name: group.name, emoji })
|
|
}
|
|
}
|
|
|
|
return { groups, claimed }
|
|
}
|
|
|
|
function serialize({ groups, claimed }) {
|
|
const body = groups
|
|
.map((group) => {
|
|
const pairs = group.emoji.map(([code, char]) => `['${code}', '${char}']`).join(', ')
|
|
// -> Single quotes, so the generated file matches what oxfmt would write for the rest of `src`
|
|
return ` {
|
|
slug: '${group.slug}',
|
|
name: '${group.name}',
|
|
emoji: [${pairs}]
|
|
}`
|
|
})
|
|
.join(',\n')
|
|
|
|
return `/*
|
|
GENERATED by scripts/generate-emoji.mjs — do not edit.
|
|
|
|
The CLDR emoji groups, in Unicode's order, as \`[shortcode, character]\` pairs. The shortcode is one
|
|
\`markdown-it-emoji\` accepts — what a page stores and what the renderer draws — and the character is
|
|
what a picker shows. Regenerate with \`npm run emoji\`.
|
|
|
|
${claimed.size} emoji in ${groups.length} groups.
|
|
*/
|
|
export const EMOJI_GROUPS = [
|
|
${body}
|
|
]
|
|
`
|
|
}
|
|
|
|
const data = build()
|
|
const output = serialize(data)
|
|
|
|
if (process.argv.includes('--check')) {
|
|
const current = fs.existsSync(OUT) ? fs.readFileSync(OUT, 'utf8') : ''
|
|
if (current !== output) {
|
|
console.error('emoji.generated.js is out of date — run `npm run emoji`')
|
|
process.exit(1)
|
|
}
|
|
console.log(`OK ${data.claimed.size} emoji, data up to date`)
|
|
} else {
|
|
fs.mkdirSync(path.dirname(OUT), { recursive: true })
|
|
fs.writeFileSync(OUT, output)
|
|
console.log(
|
|
`wrote ${data.claimed.size} emoji in ${data.groups.length} groups to src/assets/emoji.generated.js (${Buffer.byteLength(output).toLocaleString()} B)`
|
|
)
|
|
}
|