-
Notifications
You must be signed in to change notification settings - Fork 0
/
html.js
41 lines (37 loc) · 1.41 KB
/
html.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* Safe-by-construction HTML strings via [template literals]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates}.
*
* Interpolations in the template literal are automatically HTML-escaped.
*
* @module html
*/
/* If we start being limited by this, switch to Observable's much more feature
* rich version of this idea: Hypertext Literal.
* <https://github.com/observablehq/htl>
* -trs, 15 Feb 2024
*/
class LiteralHTML extends String {}
export default function html(literalParts, ...exprParts) {
/* The tagged template literal is given to us as two lists: the literal parts
* and the interpolated expression values (i.e. the evaluation of … inside
* ${…}). We zip and concatenate the two lists together while HTML-escaping
* the interpolated values.
*
* There is always one more literal part than expression part—though it may
* be the empty string—and the whole template literal starts with the first
* literal part.
* -trs, 4 May 2023
*/
return literalParts.slice(1).reduce(
(str, literalPart, idx) => new LiteralHTML(`${str}${htmlEscape(exprParts[idx])}${literalPart}`),
literalParts[0]
);
}
function htmlEscape(value) {
if (Array.isArray(value)) {
return value.map(htmlEscape).join("");
}
return value instanceof LiteralHTML
? value
: String(value).replace(/[&<>"']/g, x => `&#${x.charCodeAt(0)};`)
}