0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-03-24 06:28:11 +00:00
Files
homebrewery/client/components/codeEditor/customMarkdownGrammar.js
Víctor Losada Hernández 02ae7176f7 subscript and superscript
2026-03-23 23:55:35 +01:00

113 lines
3.5 KiB
JavaScript

// customMarkdownGrammar.js
// --- Custom tags with CM6-compatible class names ---
export const customTags = {
pageLine: "pageLine", // .cm-pageLine
snippetLine: "snippetLine", // .cm-snippetLine
columnSplit: "columnSplit", // .cm-columnSplit
snippetBreak: "snippetBreak", // .cm-snippetBreak
inlineBlock: "inline-block", // .cm-inline-block
block: "block", // .cm-block
emoji: "emoji", // .cm-emoji
superscript: "superscript", // .cm-superscript
subscript: "subscript", // .cm-subscript
definitionTerm: "dt-highlight", // .cm-dt-highlight
definitionDesc: "dd-highlight", // .cm-dd-highlight
injection: "injection", // .cm-injection
};
// --- Tokenizer function ---
export function tokenizeCustomMarkdown(text) {
const tokens = [];
const lines = text.split("\n");
// Track multi-line blocks
let inBlock = false;
let blockStart = 0;
lines.forEach((lineText, lineNumber) => {
// --- Page / snippet lines ---
if (/\\page/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.pageLine });
if (/\\snippet/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetLine });
if (/^\\column(?:break)?$/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.columnSplit });
if (/\\snippet/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetBreak });
// --- Emoji ---
if (/:\w+?:/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.emoji });
// --- Superscript / Subscript ---
if (/\^/.test(lineText)) {
let startIndex = lineText.indexOf("^");
const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy;
const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy;
while (startIndex >= 0) {
superRegex.lastIndex = subRegex.lastIndex = startIndex;
let match = subRegex.exec(lineText);
let type = customTags.subscript;
if (!match) {
match = superRegex.exec(lineText);
type = customTags.superscript;
}
if (match) {
tokens.push({
line: lineNumber,
type,
from: match.index,
to: match.index + match[0].length,
});
}
startIndex = lineText.indexOf(
"^",
Math.max(startIndex + 1, superRegex.lastIndex || 0, subRegex.lastIndex || 0),
);
}
};
// --- Definition lists ---
if (/::/.test(lineText)) {
tokens.push({ line: lineNumber, type: customTags.definitionDesc });
tokens.push({ line: lineNumber, type: customTags.definitionTerm });
}
// --- Injection `{…}` ---
const injectorRegex = /{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1}/g;
let match;
while ((match = injectorRegex.exec(lineText)) !== null) {
tokens.push({
line: lineNumber,
type: customTags.injection,
from: match.index,
to: match.index + match[0].length,
});
}
// --- Inline block `{{…}}` on the same line ---
const inlineRegex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *}}/g;
while ((match = inlineRegex.exec(lineText)) !== null) {
tokens.push({
line: lineNumber,
type: customTags.inlineBlock,
from: match.index,
to: match.index + match[0].length,
});
}
// --- Multi-line blocks `{{…}}` --- only start/end lines
if (lineText.trimLeft().startsWith("{{") && !lineText.trimLeft().endsWith("}}")) {
inBlock = true;
blockStart = lineNumber;
tokens.push({ line: lineNumber, type: customTags.block });
}
if (lineText.trimLeft().startsWith("}}") && inBlock) {
tokens.push({ line: lineNumber, type: customTags.block });
inBlock = false;
}
});
return tokens;
}