tabletop-commander/lib/tcmd/tokenizeInline.ts
2024-02-29 02:37:50 -07:00

168 lines
3.7 KiB
TypeScript

import { zipArrays } from "../zip";
export const tokenizeInline = (line: string) => {
line = line.trim();
const originalLine = line;
const insertMarker = "\u{03A9}";
const tokens: InlineTokenInsert[] = [];
for (const token of inlineTokens) {
const rx = new RegExp(token.rx);
let match;
while ((match = rx.exec(line)) !== null) {
const tokenStart = match.index;
const tokenEnd = match.index + match[0].length;
const wrappingToken = tokens.find((t) =>
t.start < tokenStart && t.end > tokenStart
);
if (wrappingToken) continue;
let wrappedToken;
while (
(wrappedToken = tokens.findIndex((t) =>
t.start > tokenStart && t.start < tokenEnd
)) !== -1
) {
tokens.splice(wrappedToken, 1);
}
token.create(match, tokenStart, tokenEnd, tokens);
}
}
if (tokens.length) {
for (const insert of tokens) {
line = line.slice(0, insert.start) +
"".padStart(insert.end - insert.start, insertMarker) +
line.slice(insert.end, line.length);
}
return zipArrays(
line.split(new RegExp(insertMarker + "{2,}")).map((t): InlineToken => ({
content: t,
type: "text",
uuid: crypto.randomUUID(),
})),
tokens,
).filter((t) => t.content);
}
return originalLine;
};
const joiner = "<><>";
export const inlineTokens: {
rx: RegExp;
create: (
content: RegExpExecArray,
start: number,
end: number,
tokens: InlineTokenInsert[],
) => void;
replace: (line: string) => string;
}[] = [
{
rx: /(\*\*)(.*?)(\*\*)/g,
create(content, start, end, tokens) {
tokens.push({
content: this.replace(content[0]),
type: "bold",
end,
start,
uuid: crypto.randomUUID(),
});
},
replace(l) {
return l.replace(this.rx, (_, __, val) => val);
},
},
{
rx: /(?<!\*)\*([^\*]+?)\*(?!\*)/g,
create(content, start, end, tokens) {
tokens.push({
content: this.replace(content[0]),
type: "italic",
end,
start,
uuid: crypto.randomUUID(),
});
},
replace(l) {
return l.replace(this.rx, (...all) => all[1]);
},
},
{
rx: /(?<![\!\?|^])\[(.*?)\]\((.*?)\)/g,
create(content, start, end, tokens) {
let [_, label, href] = content;
const style = [
{
classes: "btn-primary inline-block",
rx: /^```button\s/,
},
{
classes: "btn-secondary inline-block uppercase",
rx: /^```cta\s/,
},
].find((s) => s.rx.test(label));
if (style) label = label.replace(style.rx, "");
tokens.push({
content: label,
type: "anchor",
data: {
href,
style,
},
start,
end,
uuid: crypto.randomUUID(),
});
},
replace(l) {
return l.replace(this.rx, (_, label, href) => [label, href].join(joiner));
// return l
},
},
{
rx: /!\[(.*?)\]\((.*?)\)/g,
create(content, start, end, tokens) {
const [_, alt, src] = content;
tokens.push({
content: alt,
end,
start,
type: "image",
data: {
src,
},
uuid: crypto.randomUUID(),
});
},
replace(l) {
return l;
},
},
{
rx: /\^\[(.*?)\]<<(.*?)>>/gm,
create(content, start, end, tokens) {
const [_, text, popover] = content;
tokens.push({
content: text,
end,
start,
type: "popover",
data: {
popover: tokenizeInline(popover),
},
uuid: crypto.randomUUID(),
});
},
replace(l) {
return l;
},
},
];