core/converters/markdown.converter.ts

core/converters/markdown.converter.ts is a file in GovLab Docs. 97 lines of code and 27 definitions.

import { bodyStart, codeLineMask } from "#core/parsers/markdown.parser";
import type { SpanDelims } from "#types/reference.types";
import type { TextSpan } from "#types/markdown.types";
import { pathSpans } from "#core/selectors/markdown.selector";

const TICK = "`";
const CARRIAGE_RETURN = "\r";
const BACKTICK_DELIMS: SpanDelims = { close: TICK, open: TICK };
const LINK_DELIMS: SpanDelims = { close: ")", open: "](" };

const runLength = function runLength(line: string, from: number): number {
    let at = from;
    while (at < line.length && line.charAt(at) === TICK) {
        at += 1;
    }
    return at - from;
};

const findCloseEnd = function findCloseEnd(line: string, from: number, run: number): number {
    let at = from;
    while (at < line.length) {
        if (line.charAt(at) !== TICK) {
            at += 1;
            continue;
        }
        const closeRun = runLength(line, at);
        if (closeRun === run) {
            return at + closeRun;
        }
        at += closeRun;
    }
    return -1;
};

const codeSpanAt = function codeSpanAt(line: string, at: number): TextSpan {
    const run = runLength(line, at);
    const closeEnd = findCloseEnd(line, at + run, run);
    const spanEnd = closeEnd === -1 ? at + run : closeEnd;
    return { next: spanEnd, text: " ".repeat(spanEnd - at) };
};

export const stripInlineCode = function stripInlineCode(line: string): string {
    let out = "";
    let at = 0;
    while (at < line.length) {
        if (line.charAt(at) === TICK) {
            const span = codeSpanAt(line, at);
            out += span.text;
            at = span.next;
        } else {
            out += line.charAt(at);
            at += 1;
        }
    }
    return out;
};

const nextBlankSpan = function nextBlankSpan(line: string, delims: SpanDelims, from: number): TextSpan {
    const start = line.indexOf(delims.open, from);
    if (start === -1) {
        return { next: line.length, text: line.slice(from) };
    }
    const before = line.slice(from, start);
    const end = line.indexOf(delims.close, start + delims.open.length);
    if (end === -1) {
        return { next: line.length, text: before + " ".repeat(line.length - start) };
    }
    const stop = end + delims.close.length;
    return { next: stop, text: before + " ".repeat(stop - start) };
};

const blankSpans = function blankSpans(line: string, delims: SpanDelims): string {
    let out = "";
    let at = 0;
    while (at < line.length) {
        const span = nextBlankSpan(line, delims, at);
        out += span.text;
        at = span.next;
    }
    return out;
};

export const blankMarkup = function blankMarkup(line: string): string {
    return blankSpans(blankSpans(line, BACKTICK_DELIMS), LINK_DELIMS);
};

const wrapLinePaths = function wrapLinePaths(line: string): string {
    return pathSpans(blankMarkup(line))
        .toReversed()
        .reduce(
            (out, [start, end]) => `${out.slice(0, start)}${TICK}${out.slice(start, end)}${TICK}${out.slice(end)}`,
            line,
        );
};

const wrapLineKeepingReturn = function wrapLineKeepingReturn(line: string): string {
    if (!line.endsWith(CARRIAGE_RETURN)) {
        return wrapLinePaths(line);
    }
    return wrapLinePaths(line.slice(0, -1)) + CARRIAGE_RETURN;
};

export const backtickBarePaths = function backtickBarePaths(source: string): string {
    const lines = source.split("\n");
    const start = bodyStart(lines);
    const mask = codeLineMask(lines, start);
    return lines.map((line, at) => (at < start || mask[at] === true ? line : wrapLineKeepingReturn(line))).join("\n");
};