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; };