help pages: adds a way to extract the table of contents to the parent, smoothes out the rough loading on help pages
376 lines
8.3 KiB
TypeScript
376 lines
8.3 KiB
TypeScript
type TokenIdentifier = {
|
|
rx: RegExp;
|
|
parse: (s: string) => Token;
|
|
search?: (s: string, start: number, end: number) => {
|
|
start: number;
|
|
end: number;
|
|
text: string;
|
|
lastIndex: number;
|
|
};
|
|
};
|
|
|
|
export const TokenIdentifiers = new Map<
|
|
string,
|
|
TokenIdentifier
|
|
>();
|
|
|
|
// TokenIdentifiers.set("p", {
|
|
// rx: /\n{2,}((?:.|\n)*?)\n{2,}/g,
|
|
// parse(s) {
|
|
// const [_, content] = s.match(new RegExp(this.rx, ""))!;
|
|
|
|
// return {
|
|
// // content,
|
|
// content,
|
|
// raw: s,
|
|
// metadata: {},
|
|
// type: "p",
|
|
// uuid: crypto.randomUUID(),
|
|
// };
|
|
// },
|
|
// });
|
|
const rendersContentOnly = true;
|
|
const rendersChildrenOnly = true;
|
|
TokenIdentifiers.set("grid", {
|
|
search(s, start, end) {
|
|
const rx = /(?<!\/)(?:\[\])+/g;
|
|
const closeRx = /\/\[\]/g;
|
|
return search(s, start, end, rx, closeRx);
|
|
},
|
|
rx: /(?:\[\])+\n+((?:.|\n)*?)\n+\/\[\]/g,
|
|
parse(s) {
|
|
const rx = /((?:\[\])+)\n+([\s\S]*)\n+\/\[\]/;
|
|
const [_, columns, content] = s.match(rx) ||
|
|
["", "..", "Unable to parse grid"];
|
|
return {
|
|
content,
|
|
raw: s,
|
|
metadata: {
|
|
columns: (columns.length / 2).toString(),
|
|
},
|
|
type: "grid",
|
|
uuid: crypto.randomUUID(),
|
|
rendersChildrenOnly,
|
|
};
|
|
},
|
|
});
|
|
|
|
TokenIdentifiers.set("card", {
|
|
rx: /\[{2}\n+([\s\S]*?)\n+\]{2}/g,
|
|
search(s, start, end) {
|
|
const rx = /\[\[/g;
|
|
const crx = /\]\]/g;
|
|
return search(s, start, end, rx, crx);
|
|
},
|
|
parse(s) {
|
|
const rx = /\[{2}\n+([\s\S]*)\n+\]{2}/;
|
|
const [_, content] = s.match(rx) || ["", "Unable to parse card"];
|
|
|
|
return {
|
|
content: content.trim(),
|
|
raw: s,
|
|
metadata: {},
|
|
type: "card",
|
|
uuid: crypto.randomUUID(),
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("code", {
|
|
rx: /`{3}\n+((?:.|\n)*?)\n+`{3}/g,
|
|
parse(s) {
|
|
return {
|
|
content: s.match(new RegExp(this.rx, ""))?.at(1) ||
|
|
"Unable to parse code",
|
|
raw: s,
|
|
metadata: {},
|
|
type: "code",
|
|
uuid: crypto.randomUUID(),
|
|
rendersContentOnly,
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("list", {
|
|
rx: /^\s*-\s([\s\S]*?)\n\n/gm,
|
|
parse(s) {
|
|
return {
|
|
content: s.match(new RegExp(this.rx, ""))?.at(1) ||
|
|
"Unable to parse list",
|
|
raw: s,
|
|
metadata: {
|
|
initialDepth: s.replace("\n", "").split("-").at(0)?.length.toString() ||
|
|
"1",
|
|
},
|
|
type: "list",
|
|
uuid: crypto.randomUUID(),
|
|
rendersChildrenOnly,
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("list-item", {
|
|
rx: /^\s*-\s(.*?)$/gm,
|
|
parse(s) {
|
|
return {
|
|
content: s.match(new RegExp(this.rx, ""))?.at(1) ||
|
|
"Unable to parse list-item",
|
|
raw: s,
|
|
metadata: {
|
|
initialDepth: s.replace("\n", "").split("-").at(0)?.length.toString() ||
|
|
"1",
|
|
},
|
|
type: "list-item",
|
|
uuid: crypto.randomUUID(),
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("heading", {
|
|
rx: /^#+\s(.*?)$/gm,
|
|
parse(s) {
|
|
return {
|
|
content: s.match(new RegExp(this.rx, ""))?.at(1) ||
|
|
"Unable to parse heading",
|
|
raw: s,
|
|
metadata: {
|
|
strength: s.match(/#/g)?.length.toString() || "1",
|
|
},
|
|
type: "heading",
|
|
uuid: crypto.randomUUID(),
|
|
rendersContentOnly,
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("image", {
|
|
rx: /\!\[(.*?)\]\((.*?)\)/g,
|
|
parse(s) {
|
|
const [_, title, src] = s.match(new RegExp(this.rx, ""))!;
|
|
|
|
return {
|
|
// content: inline,
|
|
content: title.trim(),
|
|
raw: s,
|
|
metadata: {
|
|
src,
|
|
},
|
|
type: "image",
|
|
uuid: crypto.randomUUID(),
|
|
rendersContentOnly,
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("anchor", {
|
|
rx: /(?<![\!^])\[(.*?)\]\((.*?)\)/g,
|
|
parse(s) {
|
|
let preset, [_, title, href] = s.match(new RegExp(this.rx, ""))!;
|
|
const match = title.match(/`{3}(cta|button)?(.*)/);
|
|
|
|
if (match) {
|
|
[_, preset, title] = match;
|
|
}
|
|
|
|
const classes = {
|
|
button: "btn-primary inline-block",
|
|
cta: "btn-secondary inline-block uppercase",
|
|
};
|
|
return {
|
|
// content: inline,
|
|
content: title.trim(),
|
|
raw: s,
|
|
metadata: {
|
|
href,
|
|
classes: classes[preset as keyof typeof classes],
|
|
},
|
|
type: "anchor",
|
|
uuid: crypto.randomUUID(),
|
|
rendersContentOnly,
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("inline-code", {
|
|
rx: /\s?`(.{3,}|[a-z0-9]*?)`[^`a-z0-9\n]/gi,
|
|
parse(s) {
|
|
return {
|
|
// content: inline,
|
|
content: s.match(new RegExp(this.rx, "i"))?.at(1) ||
|
|
"Unable to parse inline-code",
|
|
raw: s,
|
|
metadata: {},
|
|
type: "inline-code",
|
|
uuid: crypto.randomUUID(),
|
|
rendersContentOnly,
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("bold", {
|
|
rx: /\*{2}(.*?)\*{2}/g,
|
|
parse(s) {
|
|
return {
|
|
// content: inline,
|
|
content: s.match(new RegExp(this.rx, "i"))?.at(1) ||
|
|
"Unable to parse bold",
|
|
raw: s,
|
|
metadata: {},
|
|
type: "bold",
|
|
uuid: crypto.randomUUID(),
|
|
rendersContentOnly,
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("italic", {
|
|
rx: /(?<!\*)\*([^\*]+?)\*(?!\*)/g,
|
|
parse(s) {
|
|
return {
|
|
// content: inline,
|
|
content: s.match(new RegExp(this.rx, "i"))?.at(1) ||
|
|
"Unable to parse italic",
|
|
raw: s,
|
|
metadata: {},
|
|
type: "italic",
|
|
uuid: crypto.randomUUID(),
|
|
rendersContentOnly,
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("popover", {
|
|
rx: /\^\[(.*?)\]\<<(.*?)\>>/g,
|
|
parse(s) {
|
|
const [_, title, content] = s.match(new RegExp(this.rx, ""))!;
|
|
|
|
return {
|
|
// content,
|
|
content,
|
|
raw: s,
|
|
metadata: { title },
|
|
type: "popover",
|
|
uuid: crypto.randomUUID(),
|
|
rendersContentOnly,
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("accordion", {
|
|
rx: /\[accordion(\s.*?)?]\n+((?:.|\n)*?)\n+\[\/accordion\]/g,
|
|
parse(s) {
|
|
const [_, title, content] = s.match(new RegExp(this.rx, ""))!;
|
|
|
|
return {
|
|
// content,
|
|
content,
|
|
raw: s,
|
|
metadata: { title },
|
|
type: "accordion",
|
|
uuid: crypto.randomUUID(),
|
|
};
|
|
},
|
|
});
|
|
TokenIdentifiers.set("p", {
|
|
// rx: /(?<=\n)\n?([\s\S]*?)\n(?=\n)/g,
|
|
rx: /(?<=\n\n)([\s\S]*?)(?=\n\n)/g,
|
|
parse(s) {
|
|
// const [_, content] = s.match(new RegExp(this.rx, ""))!;
|
|
|
|
return {
|
|
// content,
|
|
content: s,
|
|
raw: s,
|
|
metadata: {},
|
|
type: "p",
|
|
uuid: crypto.randomUUID(),
|
|
};
|
|
},
|
|
});
|
|
|
|
TokenIdentifiers.set("hr", {
|
|
rx: /^-{3,}$/gm,
|
|
parse(s) {
|
|
return {
|
|
content: s,
|
|
raw: s,
|
|
metadata: {},
|
|
type: "hr",
|
|
uuid: crypto.randomUUID(),
|
|
rendersContentOnly,
|
|
};
|
|
},
|
|
});
|
|
|
|
// const p = TokenIdentifiers.get("p");
|
|
// TokenIdentifiers.clear();
|
|
// p && TokenIdentifiers.set("p", p);
|
|
|
|
function findMatchingClosedParenthesis(
|
|
str: string,
|
|
openRegex: RegExp,
|
|
closedRegex: RegExp,
|
|
): number | null {
|
|
let openings = 0;
|
|
let closings = 0;
|
|
|
|
openRegex = new RegExp(openRegex, "g");
|
|
closedRegex = new RegExp(closedRegex, "g");
|
|
|
|
let lastOpeningSuccessIndex = 0;
|
|
let lastClosingSuccessIndex = 0;
|
|
|
|
do {
|
|
const openingMatch = openRegex.exec(str);
|
|
const closingMatch = closedRegex.exec(str);
|
|
|
|
if ((openingMatch && !closingMatch)) {
|
|
throw Error("Things have gone horribly wrong");
|
|
}
|
|
|
|
// if ((!openingMatch && closingMatch) || (!openingMatch && !closingMatch)) break;
|
|
|
|
if (
|
|
openingMatch && closingMatch && openingMatch.index < closingMatch.index
|
|
) {
|
|
openings++;
|
|
lastOpeningSuccessIndex = openingMatch.index + openingMatch[0].length;
|
|
closedRegex.lastIndex = lastClosingSuccessIndex;
|
|
} else if (
|
|
(!openingMatch && closingMatch) ||
|
|
(openingMatch && closingMatch && openingMatch.index > closingMatch.index)
|
|
) {
|
|
closings++;
|
|
lastClosingSuccessIndex = closingMatch.index + closingMatch[0].length;
|
|
openRegex.lastIndex = lastOpeningSuccessIndex;
|
|
} else {
|
|
return closingMatch?.index ?? null;
|
|
}
|
|
} while (openings > closings);
|
|
|
|
return closedRegex.lastIndex;
|
|
}
|
|
|
|
interface SearchResult {
|
|
start: number;
|
|
end: number;
|
|
text: string;
|
|
lastIndex: number;
|
|
}
|
|
|
|
function search(
|
|
s: string,
|
|
start: number,
|
|
end: number,
|
|
openRx: RegExp,
|
|
closeRx: RegExp,
|
|
): SearchResult {
|
|
const oldEnd = end;
|
|
|
|
const newEnd = findMatchingClosedParenthesis(
|
|
s,
|
|
openRx,
|
|
closeRx,
|
|
);
|
|
|
|
if (newEnd === null) throw Error("BAD BAD BAD BAD");
|
|
|
|
end = newEnd + start;
|
|
|
|
return {
|
|
start,
|
|
end,
|
|
text: s.substring(0, newEnd),
|
|
lastIndex: oldEnd === end ? end : start + s.match(openRx)![0].length,
|
|
};
|
|
}
|