core/normalizers/markdown.normalizer.ts

core/normalizers/markdown.normalizer.ts is a file in Bane's Lab Site. 97 lines of code and 20 definitions.

import {
    CODE_MARK,
    EMPHASIS_MARKS,
    ESCAPES,
    LINK_CLOSE,
    LINK_MIDDLE,
    LINK_OPEN,
    STRONG_MARK,
} from "#configuration/constants/markdown.constants";
import { anchorMarkup } from "#core/formatters/link.formatter";

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: anchorMarkup(escaped(href), 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: `<code>${escaped(text.slice(at + 1, close))}</code>` };
};

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>" : "<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 ? "</em>" : "<em>"),
        };
    }
    return { ...scan, at: scan.at + 1, out: scan.out + escaped(char) };
};

export const inlineCode = function inlineCode(text: string): string[] {
    const found: string[] = [];
    let at = 0;
    while (at < text.length) {
        const char = text.charAt(at);
        const middle = char === LINK_OPEN ? text.indexOf(LINK_MIDDLE, at) : -1;
        const span = spanAt(text, at);
        const code = span !== null && char === CODE_MARK;
        if (code) {
            found.push(text.slice(at + 1, span.end - 1));
        }
        if (!code && span !== null && middle !== -1) {
            found.push(...inlineCode(text.slice(at + 1, middle)));
        }
        at = span === null ? at + 1 : span.end;
    }
    return found;
};

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