@tanstack/markdown
Version:
A tiny, fast, deterministic Markdown renderer for blogs and documentation.
91 lines (90 loc) • 2.82 kB
JavaScript
const htmlEscapes = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'`': '`',
};
const escapeCharacter = (char) => htmlEscapes[char];
export function normalizeInput(value) {
return value.replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
}
export function escapeHtml(value) {
return value.replace(/[&<>"']/g, escapeCharacter);
}
export function escapeAttr(value) {
return value.replace(/[&<>"'`]/g, escapeCharacter);
}
export function isBlank(value) {
return /^\s*$/.test(value);
}
export function stripIndent(value, size) {
let index = 0;
while (index < value.length && index < size && value[index] === ' ')
index++;
return value.slice(index);
}
export function plainText(nodes) {
let value = '';
for (const node of nodes) {
if (node.type === 'text' || node.type === 'inlineCode')
value += node.value;
else if ('children' in node)
value += plainText(node.children);
else if (node.type === 'image')
value += node.alt;
}
return value;
}
export function createSlugger(normalize = headingSlug) {
const seen = new Map();
return (value) => {
const base = normalize(value);
let count = seen.get(base) ?? 1;
let id = base;
while (seen.has(id))
id = `${base}-${++count}`;
seen.set(base, count);
seen.set(id, 1);
return id;
};
}
function headingSlug(value) {
return value
.toLowerCase()
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/&[a-z0-9#]+;/gi, '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '') || 'section';
}
export function sanitizeUrl(value) {
const trimmed = value.replace(/[\u0000-\u001F\u007F\s]+/g, '');
return /^(?!https?:|mailto:|tel:)[a-z][a-z0-9+.-]*:/i.test(trimmed) ? '' : trimmed;
}
export function parseDestination(value) {
const match = value.match(/^(?:<([^<>\n]*)>|([^<>\s]*?))(?:\s+(?:"([^"]*)"|'([^']*)'|\(([^)]*)\)))?$/);
if (!match)
return undefined;
const href = match[1] ?? match[2];
const title = match[3] ?? match[4] ?? match[5];
return { href: href.replace(/\\([!-/:-@\[-`{-~])/g, '$1'), ...(title ? { title } : {}) };
}
export function normalizeReferenceLabel(value) {
return value.trim().replace(/\s+/g, ' ').toLowerCase();
}
export function footnoteId(label) {
return normalizeReferenceLabel(label)
.replace(/[^a-z0-9_-]+/g, '-')
.replace(/^-+|-+$/g, '') || 'footnote';
}
export function footnoteReferenceId(id, index = 1) {
return index > 1 ? `${id}-${index}` : id;
}
export function splitLines(value) {
const lines = value.split('\n');
if (lines.at(-1) === '')
lines.pop();
return lines;
}