html.ts

1.2 KB · raw

1/** Markup that is already safe: built by `html`, or rendered by markdown-it with raw HTML off. */2export class Html {3	constructor(readonly value: string) {}4	toString(): string {5		return this.value;6	}7}89export type Part = Html | string | number | null | undefined | false | Part[];1011const ENTITIES: Record<string, string> = {12	"&": "&amp;",13	"<": "&lt;",14	">": "&gt;",15	'"': "&quot;",16	"'": "&#39;",17};1819/** The one escaping helper (§18): every interpolated value goes through it unless it is `Html`. */20export function escapeHtml(text: string): string {21	return text.replace(/[&<>"']/g, (c) => ENTITIES[c] as string);22}2324function render(part: Part): string {25	if (part instanceof Html) return part.value;26	if (Array.isArray(part)) return part.map(render).join("");27	if (part === null || part === undefined || part === false) return "";28	return escapeHtml(String(part));29}3031export function html(strings: TemplateStringsArray, ...values: Part[]): Html {32	let out = strings[0] as string;33	values.forEach((value, i) => {34		out += render(value) + strings[i + 1];35	});36	return new Html(out);37}3839/** Trusts markup produced elsewhere; only for markdown-it output. */40export function trusted(markup: string): Html {41	return new Html(markup);42}