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