client/packages/markdown/src/emojiData.ts
Alois 88a094c8bf
Some checks failed
Dependency builds / Build web (pull_request) Has been skipped
Dependency builds / Build desktop (pull_request) Has been skipped
Dependency builds / Test native MTP (pull_request) Has been skipped
Dependency builds / Build mobile (pull_request) Has been skipped
/ release (push) Has been cancelled
/ build-web (push) Has been cancelled
/ build-desktop (linux) (push) Has been cancelled
/ build-mobile (push) Has been cancelled
feat(pwa): remove update toast
2026-08-25 20:46:14 +02:00

94 lines
2.5 KiB
TypeScript

import shortcodeData from "emojibase-data/en/shortcodes/joypixels.json";
export type EmojiDefinition = {
aliases: readonly string[];
hexcode: string;
name: string;
shortcode: string;
};
function normalizeName(value: string) {
return value
.trim()
.replace(/^:+|:+$/g, "")
.toLowerCase();
}
export const emojis: readonly EmojiDefinition[] = Object.entries(
shortcodeData as Record<string, string | string[]>,
).map(([hexcode, value]) => {
const aliases = Array.isArray(value) ? value : [value];
const name = aliases[0];
return {
aliases,
hexcode: hexcode.toLowerCase().replaceAll("_", "-"),
name,
shortcode: `:${name}:`,
};
});
const emojiByName = new Map<string, EmojiDefinition>();
for (const emoji of emojis) {
for (const alias of emoji.aliases) {
emojiByName.set(normalizeName(alias), emoji);
}
}
export function resolveEmoji(value: string): EmojiDefinition | undefined {
return emojiByName.get(normalizeName(value));
}
export function normalizeShortcode(value: string): string | undefined {
return resolveEmoji(value)?.shortcode;
}
export function findEmojiShortcodes(value: string) {
const matches: Array<{
emoji: EmojiDefinition;
from: number;
to: number;
}> = [];
let searchFrom = 0;
while (searchFrom < value.length) {
const from = value.indexOf(":", searchFrom);
if (from === -1) break;
const candidate = value.slice(from).match(/^:([a-z0-9_+-]+):/i);
if (!candidate) {
searchFrom = from + 1;
continue;
}
const emoji = resolveEmoji(candidate[1]);
if (!emoji) {
// The closing colon may also open the next valid shortcode.
searchFrom = from + candidate[0].length - 1;
continue;
}
const to = from + candidate[0].length;
matches.push({ emoji, from, to });
searchFrom = to;
}
return matches;
}
export function searchEmojis(query: string): EmojiDefinition[] {
const normalizedQuery = normalizeName(query);
if (!normalizedQuery) return [...emojis];
return emojis
.map((emoji) => {
const names = emoji.aliases.map(normalizeName);
const exact = names.includes(normalizedQuery);
const prefix = names.some((name) => name.startsWith(normalizedQuery));
const contains = names.some((name) => name.includes(normalizedQuery));
return { emoji, rank: exact ? 0 : prefix ? 1 : contains ? 2 : 3 };
})
.filter(({ rank }) => rank < 3)
.sort((a, b) => a.rank - b.rank || a.emoji.name.localeCompare(b.emoji.name))
.map(({ emoji }) => emoji);
}