-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhtml.js
138 lines (121 loc) · 3.25 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
var whitespace = /[\t\n]+/g;
var fragmentPattern = /\[fragment:([0-9]+)\]/;
// Takes a list of Nodes, returns a fragment containing them
function makeFrag(items) {
var frag = document.createDocumentFragment();
frag.append(...items);
return frag;
}
// Input string and out comes a
// DOM fragment with the string's nodes
var renderStringToFrag = (function() {
// in node we just forward the input
if(!document) {
return str => str
}
var wrap = document.createElement('div');
return (str) => {
wrap.innerHTML = str;
return makeFrag(wrap.children);
}
}())
// Renders list of text, html strings
// and fragments into a fragment
function toTag(list) {
return Promise.all(list)
.then(list => {
// console.log('toTag', list);
// Concatenate sequential strings
// Append nodes and strings to fragment
// Replace all document fragments in the array
// with placeholders
var [str, fragments] = swapFragsForRefs(list);
var root = renderStringToFrag(str);
replaceRefsWithFrags(root, fragments);
return Promise.resolve(root);
})
}
// Returns a string produced from input
// strings & fragments, where fragments
// have been replaced with placeholders,
// as well as an array of fragments for
// future swap back.
function swapFragsForRefs(list) {
var nodes = [];
var str = list.reduce((str, val) => {
if(typeof val === 'string') {
return `${str}${val}`;
}
if(val instanceof Node) {
let i = nodes.length;
nodes.push(val);
return `${str}[fragment:${i}]`;
}
return str;
}, '');
return [str, nodes];
}
// Replace fragment references in the
// input text node.
// Mutates the DOM tree passed in.
function replaceRefWithFrag(textNode, fragments) {
// Split the text node into multiple nodes:
// Text: foo [fragment:$ref] bar
// becomes
// Text: foo | Fragment | Text: bar
var match = textNode.textContent.match(fragmentPattern);
if(!match) {
return;
}
var [whole, ref] = match;
var fragmentTextNode = textNode.splitText(match.index);
var remainingText = fragmentTextNode.splitText(whole.length);
fragmentTextNode.replaceWith(fragments[ref]);
// Recurse into remaining text node
if(remainingText && remainingText.textContent) {
return replaceRefWithFrag(remainingText, fragments);
}
}
// Iterate over child nodes (text and element),
// recurse into elements, replace all [fragment:$ref]
// with the fragment from fragments
function replaceRefsWithFrags(root, fragments) {
for(let el of root.childNodes) {
if(el instanceof Text) {
// replace fragment references with fragments
replaceRefWithFrag(el, fragments);
}
else {
// recurse
replaceRefsWithFrags(el, fragments);
}
}
}
function refine(something) {
if(something instanceof Promise) {
return something;
}
if(something instanceof Array) {
return Promise.all(something)
.then(result => Promise.resolve(makeFrag(result)))
}
if(typeof something === 'function') {
return something();
}
if(typeof something === 'undefined' ||
typeof something === 'boolean') {
return '';
}
if(typeof something === 'string') {
something.replace(whitespace, '');
}
return something;
}
function html(strings, ...keys) {
var output = [];
for(let [i, string] of Object.entries(strings)) {
output.push(string, refine(keys[i]));
}
return toTag(output);
}
module.exports = html;