108 lines
2.8 KiB
TypeScript
108 lines
2.8 KiB
TypeScript
import path from "path";
|
|
import { SKIP } from "unist-util-visit";
|
|
import { processMatches } from "./unified-processors";
|
|
import type { Plugin, Compiler } from "unified";
|
|
import { Element, Text, type Root } from "hast";
|
|
|
|
const EMOJI_REGEX = /:[a-zA-Z\d-_]+:/gims;
|
|
|
|
export type CustomEmoji = {
|
|
id: string;
|
|
name: string;
|
|
keywords: string[];
|
|
skins: { src: string }[];
|
|
native?: undefined;
|
|
// added by the library; we don't need to set it
|
|
shortcodes?: string;
|
|
};
|
|
type CustomEmojiCategory = {
|
|
id: string;
|
|
name: string;
|
|
emojis: CustomEmoji[];
|
|
};
|
|
export type CustomEmojiSet = CustomEmojiCategory[];
|
|
|
|
type NativeEmoji = {
|
|
id: string;
|
|
keywords: string[];
|
|
name: string;
|
|
native: string;
|
|
shortcodes: string;
|
|
unified: string;
|
|
};
|
|
|
|
export type Emoji = NativeEmoji | CustomEmoji;
|
|
|
|
export const customEmoji: CustomEmoji[] = [];
|
|
|
|
export const cohostPlusCustomEmoji: CustomEmoji[] = [];
|
|
|
|
export const indexableCustomEmoji = new Map<string, CustomEmoji>(
|
|
customEmoji.reduce<[string, CustomEmoji][]>((collector, emoji) => {
|
|
return [...collector, [emoji.name, emoji]];
|
|
}, [])
|
|
);
|
|
|
|
export const indexableCohostPlusCustomEmoji = new Map<string, CustomEmoji>(
|
|
cohostPlusCustomEmoji.reduce<[string, CustomEmoji][]>((collector, emoji) => {
|
|
return [...collector, [emoji.name, emoji]];
|
|
}, [])
|
|
);
|
|
|
|
type ParseOptions = {
|
|
cohostPlus: boolean;
|
|
};
|
|
|
|
export const parseEmoji = (options: ParseOptions) => {
|
|
const compiler: Compiler<Root> = processMatches(
|
|
EMOJI_REGEX,
|
|
(matches, splits, node, index, parent) => {
|
|
const els = splits.reduce<Array<Element | Text>>(
|
|
(collector, curr, index) => {
|
|
const currNode: Text = {
|
|
type: "text",
|
|
value: curr,
|
|
};
|
|
|
|
const pending = [...collector, currNode];
|
|
|
|
if (index < matches.length) {
|
|
const emojiName = matches[index].slice(
|
|
1,
|
|
matches[index].length - 1
|
|
);
|
|
let emoji = indexableCustomEmoji.get(emojiName);
|
|
if (!emoji && options.cohostPlus) {
|
|
emoji = indexableCohostPlusCustomEmoji.get(emojiName);
|
|
}
|
|
|
|
if (emoji) {
|
|
pending.push({
|
|
type: "element",
|
|
tagName: "CustomEmoji",
|
|
properties: {
|
|
name: emoji.name,
|
|
url: emoji.skins[0].src,
|
|
},
|
|
children: [],
|
|
} as Element);
|
|
} else {
|
|
pending.push({
|
|
type: "text",
|
|
value: matches[index],
|
|
});
|
|
}
|
|
}
|
|
|
|
return pending;
|
|
},
|
|
[] as Array<Element | Text>
|
|
);
|
|
parent.children.splice(index, 1, ...els);
|
|
// skip over all the new elements we just created
|
|
return [SKIP, index + els.length];
|
|
}
|
|
);
|
|
return compiler;
|
|
};
|