import {
CODE_MARK,
EMPHASIS_MARKS,
ESCAPES,
LINK_CLOSE,
LINK_MIDDLE,
LINK_OPEN,
STRONG_MARK,
} from "#configuration/constants/markdown.constants";
interface Span {
readonly end: number;
readonly markup: string;
}
interface Scan {
readonly at: number;
readonly emphasis: boolean;
readonly out: string;
readonly strong: boolean;
}
export const escaped = function escaped(text: string): string {
let out = "";
for (const char of text) {
out += ESCAPES.get(char) ?? char;
}
return out;
};
const linkAt = function linkAt(text: string, at: number): Span | null {
const middle = text.indexOf(LINK_MIDDLE, at);
const close = middle === -1 ? -1 : text.indexOf(LINK_CLOSE, middle);
if (middle === -1 || close === -1) {
return null;
}
const label = inlineMarkup(text.slice(at + 1, middle));
const href = text.slice(middle + LINK_MIDDLE.length, close);
return { end: close + 1, markup: `${label}` };
};
const codeAt = function codeAt(text: string, at: number): Span | null {
const close = text.indexOf(CODE_MARK, at + 1);
return close === -1 ? null : { end: close + 1, markup: `${escaped(text.slice(at + 1, close))}` };
};
const spanAt = function spanAt(text: string, at: number): Span | null {
const char = text.charAt(at);
if (char === LINK_OPEN) {
return linkAt(text, at);
}
return char === CODE_MARK ? codeAt(text, at) : null;
};
const step = function step(text: string, scan: Scan): Scan {
const span = spanAt(text, scan.at);
if (span !== null) {
return { ...scan, at: span.end, out: scan.out + span.markup };
}
if (text.startsWith(STRONG_MARK, scan.at)) {
return {
...scan,
at: scan.at + STRONG_MARK.length,
out: scan.out + (scan.strong ? "" : ""),
strong: !scan.strong,
};
}
const char = text.charAt(scan.at);
if (EMPHASIS_MARKS.has(char)) {
return {
...scan,
at: scan.at + 1,
emphasis: !scan.emphasis,
out: scan.out + (scan.emphasis ? "" : ""),
};
}
return { ...scan, at: scan.at + 1, out: scan.out + escaped(char) };
};
export const inlineMarkup = function inlineMarkup(text: string): string {
let scan: Scan = { at: 0, emphasis: false, out: "", strong: false };
while (scan.at < text.length) {
scan = step(text, scan);
}
return scan.out;
};