@longform/longform 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/longform.cjs +426 -365
- package/dist/longform.cjs.map +1 -1
- package/dist/longform.d.ts +2 -2
- package/dist/longform.js +426 -365
- package/dist/longform.js.br +0 -0
- package/dist/longform.js.gz +0 -0
- package/dist/longform.js.map +1 -1
- package/dist/longform.min.js +1 -735
- package/dist/longform.min.js.br +0 -0
- package/dist/longform.min.js.gz +0 -0
- package/dist/longform.min.js.map +1 -1
- package/dist/mod.d.ts +2 -2
- package/dist/types.d.ts +31 -24
- package/lib/longform.ts +528 -447
- package/lib/mod.ts +17 -2
- package/lib/types.ts +33 -29
- package/package.json +4 -2
package/dist/longform.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
const LINE = 0, INDENT = 1, DIRECTIVE_KEY = 2, DIRECTIVE = 3, DIRECTIVE_INLINE_ARGS = 4, ID_TYPE = 5, ID = 6, FRAGMENT_TYPE = 7, ELEMENT = 8, ATTR = 9
|
|
2
|
-
const sniffTestRe = /^((?: )*)(?:(@)([a-z][a-z\-]*(?::[a-z][a-z\-]*)?)(?:(?::: (.*))| *)?|(##?)([a-z][a-z\-]*)(?: ?(?: +([\["]))? *|(?: *))?|(?:[a-z][a-z\-]*(?::[a-z][a-z\-])?.*(::).*)|(?:(\[)[a-z][a-z\-]?.*(?:=.+)?\]\w*)|(.+))$/gmi
|
|
1
|
+
const LINE = 0, INDENT = 1, DIRECTIVE_KEY = 2, DIRECTIVE = 3, DIRECTIVE_INLINE_ARGS = 4, ID_TYPE = 5, ID = 6, FRAGMENT_TYPE = 7, ELEMENT = 8, ATTR = 9, COMMENT = 10, sniffTestRe = /^((?: )*)(?:(@)([a-z][a-z\-]*(?::[a-z][a-z\-]*)?)(?:(?:(?::: (.*)))|(?:::)? *)?|(##?)([a-z][a-z\-]*)(?: ?(?: +([\["]))? *|(?: *))?|(?:[a-z][a-z\-]*(?::[a-z][a-z\-])?.*(::).*)|(?:(\[)[a-z][a-z\-]?.*(?:=.+)?\]\w*)|(--).*|(.+))$/gmi
|
|
3
2
|
// captures a single element definition which could be in a chain.
|
|
4
3
|
// id, class and attributes are matched as a single block for a later loop
|
|
5
4
|
// if in chained situation the regexp will need to be looped over for each element
|
|
6
5
|
// we know the element is the last if the final capture group has a positive match
|
|
7
6
|
// or if the line is consumed by the regexp.
|
|
8
|
-
,
|
|
7
|
+
, outerRe = /([a-z][\w\-]*(?::[a-z][\w\-]*)?)((?:(?:[^:])|(?::(?!:)))*)::(?: (?:({{?)|(.*)))?/gi
|
|
9
8
|
// captures each id, class and attribute declaration in an element
|
|
10
|
-
,
|
|
11
|
-
let m1, m2, m3, m4;
|
|
9
|
+
, innerRe = /(?:\.([a-z][\w\-]+)|#([a-z][\w\-]+)|\[([a-z][a-z\-]+(?::[a-z][a-z|\-]*)?)(?:=(?:"([^"]+)"|'([^']+)'|([^\]]+)))?\])/gi, attributeRe = /((?:\ \ )+)\[(\w[\w-]*(?::\w[\w-]*)?)(?:=([^\n]+))?\]/, attributeDirectiveRe = /^@([a-z][\w\-]*(?::[a-z][\w\-]*)?)$/i, idRe = /((?:\ \ )+)?#(#)?([\w\-]+)(?: ([\["]))?/i, refRe = /#\[([\w\-]+)\]/g, escapeRe = /([&<>"'#\[\]{}])/g, templateLinesRe = /^(\ \ )?([^\n]+)$/gm, templateRe = /#(#)?{([a-z][\w\-]*(?::[a-z][\w\-]*)?)}|#\[([a-z][\w\-]*(?::[a-z][\w\-]*)?)\]/g, preformatClose = new Set(['}', '}}']), voids = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wrb']);
|
|
10
|
+
let m1 = null, m2 = null, m3 = null, m4 = null;
|
|
12
11
|
const entities = {
|
|
13
12
|
'&': '&',
|
|
14
13
|
'<': '<',
|
|
@@ -44,6 +43,7 @@ function makeElement(indent = 0) {
|
|
|
44
43
|
}
|
|
45
44
|
function makeFragment(type = 'bare') {
|
|
46
45
|
return {
|
|
46
|
+
id: undefined,
|
|
47
47
|
type,
|
|
48
48
|
html: '',
|
|
49
49
|
template: false,
|
|
@@ -99,10 +99,10 @@ const directiveValidator = /^[a-z][a-z\-]*\:[a-z][a-z\-]*$/i;
|
|
|
99
99
|
* @param input - The Longform document to parse.
|
|
100
100
|
* @param args - Arguments for the Longform parser.
|
|
101
101
|
*/
|
|
102
|
-
function longform(input, args) {
|
|
103
|
-
let
|
|
102
|
+
async function longform(input, args) {
|
|
103
|
+
let skipping = false, verbatimText = '', verbatimIndent = 0, verbatimType = 0, element = makeElement(), fragment = makeFragment(), directive
|
|
104
104
|
// the root fragment
|
|
105
|
-
, root = null, id = args?.id, lang = args?.lang, dir = args?.dir, meta = {}, doc;
|
|
105
|
+
, root = null, id = args?.id, lang = args?.lang, dir = args?.dir, meta = {}, data = {}, doc, asyncCount = 0;
|
|
106
106
|
// ids of claimed fragments
|
|
107
107
|
const claimed = new Set()
|
|
108
108
|
// parsed fragments
|
|
@@ -110,7 +110,7 @@ function longform(input, args) {
|
|
|
110
110
|
id: { attr: ctx => ctx.doc.id },
|
|
111
111
|
dir: { attr: ctx => ctx.doc.dir },
|
|
112
112
|
lang: { attr: ctx => ctx.doc.lang },
|
|
113
|
-
};
|
|
113
|
+
}, promises = [];
|
|
114
114
|
let key = args?.key;
|
|
115
115
|
if (!args?.predictable && key == null) {
|
|
116
116
|
const arr = new Uint8Array(10);
|
|
@@ -124,245 +124,209 @@ function longform(input, args) {
|
|
|
124
124
|
output.templates = {};
|
|
125
125
|
if (args?.directives != null) {
|
|
126
126
|
const entries = Object.entries(args.directives);
|
|
127
|
-
for (let i = 0, l = entries.length; i < l; i++) {
|
|
128
|
-
if (!directiveValidator.test(
|
|
129
|
-
console.warn(`Invalid custom directive name '${
|
|
127
|
+
for (let i = 0, l = entries.length, e = entries[i]; i < l; i++) {
|
|
128
|
+
if (!directiveValidator.test(e[0])) {
|
|
129
|
+
console.warn(`Invalid custom directive name '$e{[0]}'`);
|
|
130
130
|
continue;
|
|
131
131
|
}
|
|
132
|
-
directives[
|
|
132
|
+
directives[e[0]] = e[1];
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
id: element.id,
|
|
143
|
-
tag: element.tag,
|
|
144
|
-
class: element.class,
|
|
145
|
-
attrs: element.attrs,
|
|
146
|
-
};
|
|
147
|
-
const chain = [];
|
|
148
|
-
for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
|
|
149
|
-
chain.push({
|
|
150
|
-
id: el.id,
|
|
151
|
-
tag: el.tag,
|
|
152
|
-
class: el.class,
|
|
153
|
-
attrs: el.attrs,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
for (let i = 0, l = element.beforeRender.length, def = element.beforeRender[i]; i < l; i++) {
|
|
157
|
-
def.beforeRender({
|
|
158
|
-
el,
|
|
159
|
-
chain,
|
|
160
|
-
doc: doc,
|
|
161
|
-
inlineArg: def.inlineArg,
|
|
162
|
-
blockArg: def.blockArg,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
135
|
+
// This is a hack to allow open verbatim blocks to detect
|
|
136
|
+
// when they close via an extra step in the loop.
|
|
137
|
+
input += '\n ';
|
|
138
|
+
sniffTestRe.lastIndex = 0;
|
|
139
|
+
main: while ((m1 = sniffTestRe.exec(input))) {
|
|
140
|
+
if (m1[COMMENT] === '--') {
|
|
141
|
+
continue;
|
|
165
142
|
}
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
143
|
+
else if (fragment.template) {
|
|
144
|
+
fragment.html += m1[0];
|
|
145
|
+
}
|
|
146
|
+
const indent = m1[INDENT].length / 2;
|
|
147
|
+
// only one root fragment is allowed. Skip until beginning of next fragment / directive.
|
|
148
|
+
if (skipping && indent !== 0)
|
|
149
|
+
continue;
|
|
150
|
+
// I hear avoiding branching is faster than avoiding assignments...
|
|
151
|
+
skipping = false;
|
|
152
|
+
// verbatim blocks collect the string as is as a continuous block
|
|
153
|
+
// and do processing on it once the full block has been collected.
|
|
154
|
+
if (verbatimType !== 0) {
|
|
155
|
+
if (indent >= verbatimIndent && (
|
|
156
|
+
// text verbatim type should exit when other Longform constructs begin.
|
|
157
|
+
verbatimType !== 1 ||
|
|
158
|
+
(m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) === undefined)) {
|
|
159
|
+
if (verbatimType === 1) {
|
|
160
|
+
// Text verbatim blocks join with blank spaces
|
|
161
|
+
verbatimText += ' ' + m1[0].trim();
|
|
182
162
|
}
|
|
183
163
|
else {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (fragment.type === 'root') {
|
|
189
|
-
fragment.html += ` data-${key}-root`;
|
|
190
|
-
}
|
|
191
|
-
else if (fragment.type === 'bare' || fragment.type === 'range') {
|
|
192
|
-
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
193
|
-
}
|
|
194
|
-
else if (fragment.type === 'embed' && !args?.predictable) {
|
|
195
|
-
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
164
|
+
if (verbatimText !== '')
|
|
165
|
+
verbatimText += '\n';
|
|
166
|
+
// other verbatim blocks normalize the Longform indent of the block away
|
|
167
|
+
verbatimText += m1[0].replace(' '.repeat(verbatimIndent), '');
|
|
196
168
|
}
|
|
169
|
+
continue;
|
|
197
170
|
}
|
|
198
|
-
if (
|
|
199
|
-
|
|
171
|
+
else if (m1[0].trim() === '' &&
|
|
172
|
+
input.length !== m1.index + m1[0].length) {
|
|
173
|
+
// blank line in verbatim are ignored by text types
|
|
174
|
+
if (verbatimType !== 1)
|
|
175
|
+
verbatimText += '\n';
|
|
176
|
+
continue;
|
|
200
177
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
178
|
+
else {
|
|
179
|
+
// verbatim block is finished
|
|
180
|
+
switch (verbatimType) {
|
|
181
|
+
case 1:
|
|
182
|
+
// text
|
|
183
|
+
fragment.html += verbatimText;
|
|
184
|
+
// locate reference points in text
|
|
185
|
+
while ((m2 = refRe.exec(verbatimText))) {
|
|
186
|
+
const start = fragment.html.length + m2.index - verbatimText.length;
|
|
187
|
+
fragment.refs.push({
|
|
188
|
+
id: m2[1],
|
|
189
|
+
start,
|
|
190
|
+
end: start + m2[0].length,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
194
|
+
break;
|
|
195
|
+
case 2:
|
|
196
|
+
// escaped preformatted text
|
|
197
|
+
fragment.html += escape(verbatimText);
|
|
198
|
+
break;
|
|
199
|
+
case 3:
|
|
200
|
+
// preformatted
|
|
201
|
+
fragment.html += verbatimText + '\n';
|
|
202
|
+
break;
|
|
203
|
+
case 4:
|
|
204
|
+
// directive block args
|
|
205
|
+
if (directive.def == null)
|
|
206
|
+
break;
|
|
207
|
+
if (doc == null) {
|
|
208
|
+
if (typeof directive.def.meta === 'function') {
|
|
209
|
+
meta[m1[DIRECTIVE]] = directive.def.meta({
|
|
210
|
+
inlineArgs: directive.inlineArgs,
|
|
211
|
+
blockArgs: verbatimText,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else if (typeof directive.def.render === 'function') {
|
|
216
|
+
try {
|
|
217
|
+
element.html += directive.def.render({
|
|
218
|
+
doc,
|
|
219
|
+
inlineArgs: directive.inlineArgs,
|
|
220
|
+
blockArgs: verbatimText,
|
|
221
|
+
}) + ' ';
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
console.error(`Error in calling directive ${directive.name}`);
|
|
225
|
+
console.error(err);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (typeof directive.def.asyncRender === 'function') {
|
|
229
|
+
asyncCount++;
|
|
230
|
+
// async rendering uses the #[ref] feature to insert the
|
|
231
|
+
// eventual response.
|
|
232
|
+
const directiveFragment = makeFragment('embed');
|
|
233
|
+
directiveFragment.id = `@${asyncCount}`;
|
|
234
|
+
parsed.set(directiveFragment.id, directiveFragment);
|
|
235
|
+
fragment.refs.push({
|
|
236
|
+
id: directiveFragment.id,
|
|
237
|
+
start: fragment.html.length,
|
|
238
|
+
end: fragment.html.length,
|
|
239
|
+
});
|
|
240
|
+
// in case text follows we want a space separating the render output
|
|
241
|
+
fragment.html += ' ';
|
|
242
|
+
promises.push(directive.def.asyncRender({
|
|
243
|
+
doc,
|
|
244
|
+
inlineArgs: directive.inlineArgs,
|
|
245
|
+
blockArgs: verbatimText,
|
|
246
|
+
}).then(res => {
|
|
247
|
+
directiveFragment.html = res ?? '';
|
|
248
|
+
}).catch(err => {
|
|
249
|
+
console.error(`Error in calling directive ${directive.name}`);
|
|
250
|
+
console.error(err);
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
else if (typeof directive.def.element === 'function') {
|
|
254
|
+
if (element.beforeRender == null)
|
|
255
|
+
element.beforeRender = [];
|
|
256
|
+
element.beforeRender.push({
|
|
257
|
+
blockArgs: verbatimText,
|
|
258
|
+
inlineArgs: directive.inlineArgs,
|
|
259
|
+
element: directive.def.element,
|
|
260
|
+
});
|
|
216
261
|
}
|
|
217
262
|
else {
|
|
218
|
-
|
|
263
|
+
console.warn(`Directive used in incorrect context ${directive.name}`);
|
|
219
264
|
}
|
|
220
|
-
}
|
|
221
|
-
fragment.html += '>';
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
if (!voids.has(element.tag) && element.text != null) {
|
|
225
|
-
fragment.html += element.text;
|
|
226
|
-
}
|
|
227
|
-
if (!voids.has(element.tag)) {
|
|
228
|
-
fragment.els.push(element);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (targetIndent <= element.indent) {
|
|
232
|
-
element = makeElement(targetIndent);
|
|
233
|
-
while (fragment.els.length !== 0 && (targetIndent == null ||
|
|
234
|
-
fragment.els[fragment.els.length - 1].indent !== targetIndent - 1)) {
|
|
235
|
-
const element = fragment.els.pop();
|
|
236
|
-
if (Array.isArray(element.chain)) {
|
|
237
|
-
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
238
|
-
fragment.html += `</${element.chain[i].tag}>`;
|
|
239
|
-
}
|
|
240
265
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (fragment.template) {
|
|
245
|
-
output.templates[fragment.id] = fragment.html;
|
|
246
|
-
}
|
|
247
|
-
else if (fragment.type === 'root') {
|
|
248
|
-
root = fragment;
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
parsed.set(fragment.id, fragment);
|
|
252
|
-
}
|
|
253
|
-
fragment = makeFragment();
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
element = makeElement(targetIndent);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
main: while ((m1 = sniffTestRe.exec(input))) {
|
|
261
|
-
if (m1[1] === '--') {
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
else if (fragment.template) {
|
|
265
|
-
fragment.html += m1[0];
|
|
266
|
-
}
|
|
267
|
-
// If this is a script tag or preformatted block
|
|
268
|
-
// we want to retain the intended formatting less
|
|
269
|
-
// the indent. Pre-formatting can apply to any element
|
|
270
|
-
// by ending the declaration with `:: {`.
|
|
271
|
-
if (verbatimIndent != null) {
|
|
272
|
-
// inside a script or preformatted block
|
|
273
|
-
identRe.lastIndex = 0;
|
|
274
|
-
m2 = identRe.exec(m1[0]);
|
|
275
|
-
const indent = m2 == null
|
|
276
|
-
? null
|
|
277
|
-
: m2[0].length / 2;
|
|
278
|
-
if (m2 == null || indent <= verbatimIndent) {
|
|
279
|
-
fragment.html += '\n';
|
|
280
|
-
applyIndent(indent);
|
|
281
|
-
verbatimIndent = null;
|
|
282
|
-
verbatimFirst = false;
|
|
283
|
-
textIndent = indent;
|
|
284
|
-
if (preformattedClose.test(m1[0])) {
|
|
266
|
+
verbatimType = 0;
|
|
267
|
+
verbatimText = '';
|
|
268
|
+
if (preformatClose.has(m1[0].trim()))
|
|
285
269
|
continue;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
const line = m1[0].replace(' '.repeat(verbatimIndent + 1), '');
|
|
290
|
-
if (element.tag != null) {
|
|
291
|
-
applyIndent(indent);
|
|
292
|
-
}
|
|
293
|
-
if (verbatimFirst) {
|
|
294
|
-
verbatimFirst = false;
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
fragment.html += '\n';
|
|
298
|
-
}
|
|
299
|
-
if (verbatimSerialize) {
|
|
300
|
-
fragment.html += line;
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
fragment.html += escape(line);
|
|
304
|
-
}
|
|
305
|
-
continue;
|
|
306
270
|
}
|
|
307
271
|
}
|
|
308
272
|
if (m1[LINE].trim() === '') {
|
|
273
|
+
// empty lines have no effect from here on
|
|
309
274
|
continue;
|
|
310
275
|
}
|
|
311
|
-
// The id and lang directives should proceed all other directives and
|
|
312
|
-
// fragment declarations.
|
|
313
276
|
if (doc === undefined) {
|
|
277
|
+
// The meta directives get special treatment.
|
|
278
|
+
// Parsers can use the head output type to output
|
|
279
|
+
// the meta results only.
|
|
280
|
+
let parseBlock = false;
|
|
314
281
|
const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? '';
|
|
315
282
|
switch (m1[DIRECTIVE]) {
|
|
316
|
-
case 'id':
|
|
283
|
+
case 'id':
|
|
317
284
|
const url = inlineArgs.trim();
|
|
285
|
+
parseBlock = true;
|
|
318
286
|
try {
|
|
319
287
|
id = id ?? new URL(url, args?.base).toString();
|
|
320
288
|
}
|
|
321
289
|
catch (err) {
|
|
322
|
-
console.warn(`Invalid URL given to @id directive: ${url} base=${args
|
|
290
|
+
console.warn(`Invalid URL given to @id directive: ${url} base=${args?.base}`);
|
|
323
291
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
292
|
+
break;
|
|
293
|
+
case 'lang':
|
|
294
|
+
parseBlock = true;
|
|
327
295
|
lang = lang ?? inlineArgs.trim();
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
296
|
+
break;
|
|
297
|
+
case 'dir':
|
|
298
|
+
parseBlock = true;
|
|
331
299
|
dir = dir ?? inlineArgs.trim();
|
|
332
|
-
continue main;
|
|
333
|
-
}
|
|
334
|
-
default: {
|
|
335
|
-
const def = directives[m1[DIRECTIVE]];
|
|
336
|
-
if (typeof def?.meta === 'function') {
|
|
337
|
-
if (Object.keys(def).length > 1) {
|
|
338
|
-
throw new Error(`A custom directive performing the meta role cannot be used for other purposes. ` +
|
|
339
|
-
`See @${m1[DIRECTIVE]}`);
|
|
340
|
-
}
|
|
341
|
-
meta[m1[DIRECTIVE]] = def.meta({ inlineArgs });
|
|
342
|
-
continue main;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
300
|
}
|
|
346
|
-
|
|
347
|
-
|
|
301
|
+
// Even though the builtin meta directives do not support block args
|
|
302
|
+
// the args need to be parsed so they do not end up in the output.
|
|
303
|
+
const def = directives[m1[DIRECTIVE]];
|
|
304
|
+
if (typeof def?.meta === 'function' || parseBlock) {
|
|
305
|
+
verbatimIndent = indent + 1;
|
|
306
|
+
verbatimType = 4;
|
|
307
|
+
directive = {
|
|
308
|
+
name: m1[DIRECTIVE],
|
|
309
|
+
inlineArgs,
|
|
310
|
+
def,
|
|
311
|
+
};
|
|
312
|
+
continue main;
|
|
313
|
+
}
|
|
314
|
+
if (args?.outputMode === 'head') {
|
|
348
315
|
return {
|
|
349
316
|
key,
|
|
350
317
|
id,
|
|
351
318
|
lang,
|
|
352
319
|
dir,
|
|
353
|
-
meta
|
|
320
|
+
meta,
|
|
321
|
+
data,
|
|
354
322
|
};
|
|
355
323
|
}
|
|
324
|
+
doc = new Doc(id, lang, dir, meta, args?.allowAll, args?.allowedAttributes, args?.allowedElements);
|
|
356
325
|
}
|
|
357
326
|
switch (m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) {
|
|
358
327
|
case '#':
|
|
359
|
-
case '##':
|
|
360
|
-
|
|
361
|
-
const indent = (m1[INDENT].length ?? 0) / 2;
|
|
362
|
-
if (element.tag != null || textIndent != null) {
|
|
363
|
-
applyIndent(indent);
|
|
364
|
-
textIndent = null;
|
|
365
|
-
}
|
|
328
|
+
case '##':
|
|
329
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
366
330
|
fragment.id = m1[ID];
|
|
367
331
|
if (indent === 0) {
|
|
368
332
|
if (m1[FRAGMENT_TYPE] == '[') {
|
|
@@ -383,104 +347,93 @@ function longform(input, args) {
|
|
|
383
347
|
element.id = fragment.id;
|
|
384
348
|
}
|
|
385
349
|
break;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (element.tag != null || textIndent != null) {
|
|
390
|
-
applyIndent(indent);
|
|
350
|
+
case '@':
|
|
351
|
+
if (element.tag !== undefined) {
|
|
352
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
391
353
|
}
|
|
354
|
+
const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? '';
|
|
392
355
|
switch (m1[DIRECTIVE]) {
|
|
393
|
-
case '
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
else if (m2[1] == null && indented) {
|
|
416
|
-
sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
fragment.html += '\n' + m2[0];
|
|
421
|
-
if (m2[1] != null)
|
|
422
|
-
indented = true;
|
|
356
|
+
case 'template':
|
|
357
|
+
if (indent === 0) {
|
|
358
|
+
let indented = false;
|
|
359
|
+
fragment.template = true;
|
|
360
|
+
templateLinesRe.lastIndex = sniffTestRe.lastIndex;
|
|
361
|
+
while ((m2 = templateLinesRe.exec(input))) {
|
|
362
|
+
if (m2[1] == null && !indented && fragment.id == null) {
|
|
363
|
+
m3 = idRe.exec(m2[0]);
|
|
364
|
+
if (m3 != null)
|
|
365
|
+
fragment.id = m3[3];
|
|
366
|
+
fragment.html += m2[0];
|
|
367
|
+
}
|
|
368
|
+
else if (m2[1] == null && indented) {
|
|
369
|
+
sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
fragment.html += '\n' + m2[0];
|
|
374
|
+
if (m2[1] != null)
|
|
375
|
+
indented = true;
|
|
376
|
+
}
|
|
423
377
|
}
|
|
378
|
+
[element, fragment] = applyIndent(0, key, element, fragment, doc, parsed, output, args);
|
|
424
379
|
}
|
|
425
|
-
|
|
380
|
+
continue main;
|
|
381
|
+
case 'doctype':
|
|
382
|
+
fragment.html += `<!doctype ${(inlineArgs.trim() || 'html').trim()}>`;
|
|
426
383
|
break;
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
384
|
+
case 'xml':
|
|
385
|
+
fragment.html += `<?xml ${inlineArgs.trim() || 'version="1.0" encoding="UTF-8"'}?>`;
|
|
386
|
+
break;
|
|
387
|
+
case 'mount':
|
|
388
|
+
if (args?.outputMode !== 'mountable')
|
|
389
|
+
break;
|
|
390
|
+
if (inlineArgs === '') {
|
|
391
|
+
console.warn('Mount points must have a name');
|
|
431
392
|
}
|
|
432
393
|
else if (fragment.type !== 'root') {
|
|
433
|
-
|
|
394
|
+
console.warn('Mounting is only allowed on a root element');
|
|
434
395
|
}
|
|
435
396
|
fragment.mountable = true;
|
|
436
|
-
element.mount =
|
|
397
|
+
element.mount = inlineArgs.trim();
|
|
437
398
|
break;
|
|
438
|
-
}
|
|
439
|
-
default: {
|
|
440
|
-
const def = directives[m1[DIRECTIVE]];
|
|
441
|
-
if (def == null)
|
|
442
|
-
break;
|
|
443
|
-
if (typeof def.beforeRender === 'function') {
|
|
444
|
-
if (element.id != null) {
|
|
445
|
-
applyIndent(indent);
|
|
446
|
-
}
|
|
447
|
-
if (element.beforeRender == null)
|
|
448
|
-
element.beforeRender = [];
|
|
449
|
-
// TODO: Parse block args
|
|
450
|
-
const applied = {
|
|
451
|
-
...def,
|
|
452
|
-
inlineArg: m1[DIRECTIVE_INLINE_ARGS],
|
|
453
|
-
};
|
|
454
|
-
element.beforeRender.push(applied);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
399
|
}
|
|
400
|
+
const def = directives[m1[DIRECTIVE]];
|
|
401
|
+
// A directive may not be defined but we want to process
|
|
402
|
+
// any block args to keep the output valid. Builtin directives
|
|
403
|
+
// will be ignored unless they require block args.
|
|
404
|
+
verbatimIndent = indent + 1;
|
|
405
|
+
verbatimType = 4;
|
|
406
|
+
directive = {
|
|
407
|
+
name: m1[DIRECTIVE],
|
|
408
|
+
inlineArgs: m1[DIRECTIVE_INLINE_ARGS],
|
|
409
|
+
def,
|
|
410
|
+
};
|
|
458
411
|
break;
|
|
459
|
-
|
|
460
|
-
case '[':
|
|
461
|
-
case '::': {
|
|
412
|
+
case '::':
|
|
462
413
|
if (m1[ELEMENT] !== undefined) {
|
|
463
|
-
const indent = (m1[INDENT]?.length ?? 0) / 2;
|
|
464
414
|
let preformattedType;
|
|
465
415
|
let inlineText;
|
|
466
416
|
if (element.tag !== undefined ||
|
|
467
417
|
element.indent > indent) {
|
|
468
|
-
applyIndent(indent);
|
|
418
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
469
419
|
}
|
|
470
420
|
element.indent = indent;
|
|
471
|
-
textIndent = null;
|
|
472
421
|
if (indent === 0 && fragment.id == null) {
|
|
473
|
-
if (root != null)
|
|
422
|
+
if (root != null) {
|
|
423
|
+
// skip if root is found and this fragment
|
|
424
|
+
// has no id
|
|
425
|
+
skipping = true;
|
|
426
|
+
}
|
|
474
427
|
else {
|
|
475
428
|
fragment.type = 'root';
|
|
476
429
|
root = fragment;
|
|
477
430
|
}
|
|
478
431
|
}
|
|
479
432
|
const parent = element;
|
|
480
|
-
|
|
433
|
+
outerRe.lastIndex = 0;
|
|
481
434
|
// Looping through chained element declarations
|
|
482
435
|
// foo#x.y::bar::free::
|
|
483
|
-
while ((m2 =
|
|
436
|
+
while ((m2 = outerRe.exec(m1[LINE]))) {
|
|
484
437
|
let working;
|
|
485
438
|
preformattedType = m2[3];
|
|
486
439
|
inlineText = m2[4];
|
|
@@ -495,9 +448,9 @@ function longform(input, args) {
|
|
|
495
448
|
working.tag = m2[1];
|
|
496
449
|
parent.chain.push(working);
|
|
497
450
|
}
|
|
498
|
-
|
|
451
|
+
innerRe.lastIndex = 0;
|
|
499
452
|
// Looping through ids, classes and attrs
|
|
500
|
-
while ((m3 =
|
|
453
|
+
while ((m3 = innerRe.exec(m2[2]))) {
|
|
501
454
|
if (m3[2] !== undefined) {
|
|
502
455
|
working.id = m3[2];
|
|
503
456
|
}
|
|
@@ -514,8 +467,8 @@ function longform(input, args) {
|
|
|
514
467
|
let value = m3[4] ?? m3[5] ?? m3[6];
|
|
515
468
|
// attribute directives
|
|
516
469
|
if (value[0] === '@') {
|
|
517
|
-
|
|
518
|
-
m4 =
|
|
470
|
+
attributeDirectiveRe.lastIndex = 0;
|
|
471
|
+
m4 = attributeDirectiveRe.exec(value);
|
|
519
472
|
if (m4 != null) {
|
|
520
473
|
const def = directives[m4[1]];
|
|
521
474
|
if (def != null && typeof def.attr === 'function') {
|
|
@@ -530,20 +483,21 @@ function longform(input, args) {
|
|
|
530
483
|
}
|
|
531
484
|
switch (m3[3]) {
|
|
532
485
|
case 'id':
|
|
533
|
-
if (!working.id) {
|
|
486
|
+
if (!working.id && typeof value === 'string') {
|
|
534
487
|
working.id = value;
|
|
535
488
|
}
|
|
536
489
|
break;
|
|
537
490
|
case 'class':
|
|
538
|
-
if (!working.class) {
|
|
491
|
+
if (!working.class && typeof value === 'string') {
|
|
539
492
|
working.class = value;
|
|
540
493
|
}
|
|
541
|
-
else {
|
|
494
|
+
else if (typeof value === 'string') {
|
|
542
495
|
working.class += ' ' + value;
|
|
543
496
|
}
|
|
544
497
|
break;
|
|
545
498
|
default:
|
|
546
|
-
|
|
499
|
+
if (value !== false)
|
|
500
|
+
working.attrs[m3[3]] = value;
|
|
547
501
|
}
|
|
548
502
|
}
|
|
549
503
|
}
|
|
@@ -553,29 +507,34 @@ function longform(input, args) {
|
|
|
553
507
|
// server specific process.
|
|
554
508
|
if (element.mount != null) {
|
|
555
509
|
const id = element.mount;
|
|
556
|
-
applyIndent(indent + 1);
|
|
510
|
+
[element, fragment] = applyIndent(indent + 1, key, element, fragment, doc, parsed, output, args);
|
|
511
|
+
if (fragment.mountPoints == null)
|
|
512
|
+
fragment.mountPoints = [];
|
|
557
513
|
fragment.mountPoints.push({
|
|
558
514
|
id,
|
|
559
515
|
part: fragment.html,
|
|
560
516
|
});
|
|
561
517
|
fragment.html = '';
|
|
562
|
-
applyIndent(indent);
|
|
518
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
563
519
|
break;
|
|
564
520
|
}
|
|
565
521
|
if (preformattedType !== undefined) {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
522
|
+
if (element.tag !== undefined)
|
|
523
|
+
[element, fragment] = applyIndent(indent + 1, key, element, fragment, doc, parsed, output, args);
|
|
524
|
+
verbatimIndent = indent + 1;
|
|
525
|
+
verbatimType = preformattedType === '{{' ? 3 : 2;
|
|
569
526
|
}
|
|
570
527
|
else if (inlineText !== undefined) {
|
|
571
528
|
element.text = inlineText;
|
|
572
529
|
}
|
|
573
530
|
break;
|
|
574
531
|
}
|
|
575
|
-
|
|
532
|
+
case '[':
|
|
533
|
+
// TODO: Add attr directive support.
|
|
534
|
+
attributeRe.lastIndex = 0;
|
|
576
535
|
m2 = m1[ATTR] !== undefined
|
|
577
|
-
?
|
|
578
|
-
:
|
|
536
|
+
? attributeRe.exec(m1[LINE])
|
|
537
|
+
: null;
|
|
579
538
|
if (m2 != null && element.tag != null) {
|
|
580
539
|
if (m2[2] === 'id') {
|
|
581
540
|
if (element.id == null) {
|
|
@@ -598,69 +557,24 @@ function longform(input, args) {
|
|
|
598
557
|
}
|
|
599
558
|
break;
|
|
600
559
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
const indent = m2[1].length / 2;
|
|
608
|
-
const tx = m2[2].trim();
|
|
609
|
-
if (element.tag != null) {
|
|
610
|
-
applyIndent(indent);
|
|
611
|
-
fragment.html += tx;
|
|
612
|
-
}
|
|
613
|
-
else if (fragment.type === 'text' && fragment.html === '') {
|
|
614
|
-
fragment.html += tx;
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
fragment.html += ' ' + tx;
|
|
618
|
-
}
|
|
619
|
-
textIndent = indent;
|
|
620
|
-
while ((m2 = refRe.exec(tx))) {
|
|
621
|
-
const start = fragment.html.length + m2.index - tx.length;
|
|
622
|
-
fragment.refs.push({
|
|
623
|
-
id: m2[1],
|
|
624
|
-
start,
|
|
625
|
-
end: start + m2[0].length,
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
break;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
applyIndent(0);
|
|
633
|
-
const arr = Array.from(parsed.values());
|
|
634
|
-
function flatten(fragment) {
|
|
635
|
-
if (fragment.refs == null)
|
|
636
|
-
fragment.refs = [];
|
|
637
|
-
// work backwards so we don't change the html string length
|
|
638
|
-
// for the later replacements
|
|
639
|
-
for (let j = fragment.refs.length - 1; j >= 0; j--) {
|
|
640
|
-
const ref = fragment.refs[j];
|
|
641
|
-
if (claimed.has(ref.id) || !parsed.has(ref.id)) {
|
|
642
|
-
fragment.html = fragment.html.slice(0, ref.start)
|
|
643
|
-
+ fragment.html.slice(ref.end);
|
|
644
|
-
}
|
|
645
|
-
else {
|
|
646
|
-
const child = flatten(parsed.get(ref.id));
|
|
647
|
-
fragment.html = fragment.html.slice(0, ref.start)
|
|
648
|
-
+ child.html
|
|
649
|
-
+ fragment.html.slice(ref.end);
|
|
650
|
-
if (child.type === 'embed') {
|
|
651
|
-
claimed.add(child.id);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
560
|
+
default:
|
|
561
|
+
if (element.tag !== undefined)
|
|
562
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
563
|
+
verbatimText = m1[0].trim();
|
|
564
|
+
verbatimIndent = indent;
|
|
565
|
+
verbatimType = 1;
|
|
654
566
|
}
|
|
655
|
-
fragment.refs = [];
|
|
656
|
-
return fragment;
|
|
657
567
|
}
|
|
568
|
+
applyIndent(0, key, element, fragment, doc, parsed, output, args);
|
|
569
|
+
if (promises.length > 0)
|
|
570
|
+
await Promise.all(promises);
|
|
658
571
|
if (root?.mountable) {
|
|
659
572
|
output.mountable = true;
|
|
660
573
|
output.tail = root.html;
|
|
661
|
-
output.mountPoints = root.mountPoints;
|
|
574
|
+
output.mountPoints = root.mountPoints ?? [];
|
|
662
575
|
return output;
|
|
663
576
|
}
|
|
577
|
+
const arr = Array.from(parsed.values());
|
|
664
578
|
for (let i = 0; i < parsed.size + 1; i++) {
|
|
665
579
|
let fragment;
|
|
666
580
|
if (i === 0 && root == null) {
|
|
@@ -675,33 +589,31 @@ function longform(input, args) {
|
|
|
675
589
|
if (fragment.refs == null || fragment.refs.length === 0) {
|
|
676
590
|
continue;
|
|
677
591
|
}
|
|
678
|
-
flatten(fragment);
|
|
592
|
+
flatten(fragment, claimed, parsed);
|
|
679
593
|
}
|
|
680
594
|
if (root?.html != null) {
|
|
681
595
|
output.root = root.html;
|
|
682
596
|
output.selector = `[data-${key}-root]`;
|
|
683
597
|
}
|
|
684
|
-
for (let i = 0
|
|
598
|
+
for (let i = 0, l = arr.length, f = arr[i]; i < l; i++) {
|
|
685
599
|
let selector;
|
|
686
|
-
|
|
687
|
-
if (fragment == null || claimed.has(fragment.id)) {
|
|
600
|
+
if (f == null || claimed.has(f.id)) {
|
|
688
601
|
continue;
|
|
689
602
|
}
|
|
690
|
-
switch (
|
|
691
|
-
case 'embed':
|
|
603
|
+
switch (f.type) {
|
|
604
|
+
case 'embed':
|
|
692
605
|
if (args?.predictable) {
|
|
693
|
-
selector = `[id=${
|
|
606
|
+
selector = `[id=${f.id}]`;
|
|
694
607
|
break;
|
|
695
608
|
}
|
|
696
|
-
}
|
|
697
609
|
case 'bare':
|
|
698
|
-
case 'range': selector = `[data-${key}=${
|
|
610
|
+
case 'range': selector = `[data-${key}=${f.id}]`;
|
|
699
611
|
}
|
|
700
|
-
output.fragments[
|
|
701
|
-
id:
|
|
612
|
+
output.fragments[f.id] = {
|
|
613
|
+
id: f.id,
|
|
702
614
|
selector,
|
|
703
|
-
type:
|
|
704
|
-
html:
|
|
615
|
+
type: f.type,
|
|
616
|
+
html: f.html,
|
|
705
617
|
};
|
|
706
618
|
}
|
|
707
619
|
output.key = key;
|
|
@@ -719,9 +631,9 @@ function longform(input, args) {
|
|
|
719
631
|
* @param getFragment - A function which returns an already processed fragment's HTML string.
|
|
720
632
|
* @returns The processed template.
|
|
721
633
|
*/
|
|
722
|
-
function processTemplate(template, args, getFragment) {
|
|
723
|
-
const lf = template.replace(templateRe, (
|
|
724
|
-
if (ref
|
|
634
|
+
async function processTemplate(template, args, longformArgs, getFragment) {
|
|
635
|
+
const lf = template.replace(templateRe, (_line, _structured, param, ref) => {
|
|
636
|
+
if (ref !== undefined && typeof getFragment === 'function') {
|
|
725
637
|
const fragment = getFragment(ref);
|
|
726
638
|
if (fragment == null)
|
|
727
639
|
return '';
|
|
@@ -729,7 +641,156 @@ function processTemplate(template, args, getFragment) {
|
|
|
729
641
|
}
|
|
730
642
|
return args[param] != null ? escape(args[param].toString()) : '';
|
|
731
643
|
});
|
|
732
|
-
return Object.values(longform(lf).fragments)[0]?.html ?? null;
|
|
644
|
+
return Object.values((await longform(lf, longformArgs)).fragments)[0]?.html ?? null;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Closes any current in progress element definition
|
|
648
|
+
* and creates a new working element.
|
|
649
|
+
*/
|
|
650
|
+
function applyIndent(targetIndent, key, element, fragment, doc, parsed, output, args) {
|
|
651
|
+
if (element.tag !== undefined) {
|
|
652
|
+
if (Array.isArray(element.beforeRender)) {
|
|
653
|
+
const el = {
|
|
654
|
+
id: element.id,
|
|
655
|
+
tag: element.tag,
|
|
656
|
+
class: element.class,
|
|
657
|
+
attrs: element.attrs,
|
|
658
|
+
};
|
|
659
|
+
const chain = [];
|
|
660
|
+
for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
|
|
661
|
+
chain.push({
|
|
662
|
+
id: el.id,
|
|
663
|
+
tag: el.tag,
|
|
664
|
+
class: el.class,
|
|
665
|
+
attrs: el.attrs,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
for (let i = 0, l = element.beforeRender.length, def = element.beforeRender[i]; i < l; i++) {
|
|
669
|
+
def.element({
|
|
670
|
+
el,
|
|
671
|
+
chain,
|
|
672
|
+
doc,
|
|
673
|
+
inlineArgs: def.inlineArgs,
|
|
674
|
+
blockArgs: def.blockArgs,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const root = fragment.type === 'range'
|
|
679
|
+
? targetIndent < 2
|
|
680
|
+
: fragment.html === '';
|
|
681
|
+
fragment.html += `<${element.tag}`;
|
|
682
|
+
if (element.id !== undefined) {
|
|
683
|
+
fragment.html += ' id="' + element.id + '"';
|
|
684
|
+
}
|
|
685
|
+
if (element.class !== undefined) {
|
|
686
|
+
fragment.html += ' class="' + element.class + '"';
|
|
687
|
+
}
|
|
688
|
+
for (const attr of Object.entries(element.attrs)) {
|
|
689
|
+
if (attr[0] === 'id' || attr[0] === 'class')
|
|
690
|
+
continue;
|
|
691
|
+
if (attr[1] == null) {
|
|
692
|
+
fragment.html += ' ' + attr[0];
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
fragment.html += ` ${attr[0]}="${attr[1]}"`;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (root) {
|
|
699
|
+
if (fragment.type === 'root') {
|
|
700
|
+
fragment.html += ` data-${key}-root`;
|
|
701
|
+
}
|
|
702
|
+
else if (fragment.type === 'bare' || fragment.type === 'range') {
|
|
703
|
+
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
704
|
+
}
|
|
705
|
+
else if (fragment.type === 'embed' && !args?.predictable) {
|
|
706
|
+
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (element.mount !== undefined) {
|
|
710
|
+
fragment.html += ` data-${key}-mount="${element.mount}"`;
|
|
711
|
+
}
|
|
712
|
+
fragment.html += '>';
|
|
713
|
+
if (Array.isArray(element.chain)) {
|
|
714
|
+
let chained;
|
|
715
|
+
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
716
|
+
chained = element.chain[i];
|
|
717
|
+
fragment.html += '<' + chained.tag;
|
|
718
|
+
if (chained.id !== undefined) {
|
|
719
|
+
fragment.html += ' id="' + chained.id + '"';
|
|
720
|
+
}
|
|
721
|
+
if (chained.class != undefined) {
|
|
722
|
+
fragment.html += ' class="' + chained.class + '"';
|
|
723
|
+
}
|
|
724
|
+
for (const attr of Object.entries(chained.attrs)) {
|
|
725
|
+
if (attr[1] === undefined) {
|
|
726
|
+
fragment.html += ' ' + attr[0];
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
fragment.html += ` ${attr[0]}="${attr[1]}"`;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
fragment.html += '>';
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (!voids.has(element.tag) && element.text != null) {
|
|
736
|
+
fragment.html += element.text;
|
|
737
|
+
}
|
|
738
|
+
if (!voids.has(element.tag)) {
|
|
739
|
+
fragment.els.push(element);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (targetIndent <= element.indent) {
|
|
743
|
+
element = makeElement(targetIndent);
|
|
744
|
+
while (fragment.els.length !== 0 && (targetIndent == null ||
|
|
745
|
+
fragment.els[fragment.els.length - 1].indent !== targetIndent - 1)) {
|
|
746
|
+
const element = fragment.els.pop();
|
|
747
|
+
if (Array.isArray(element.chain)) {
|
|
748
|
+
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
749
|
+
fragment.html += `</${element.chain[i].tag}>`;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
fragment.html += `</${element?.tag}>`;
|
|
753
|
+
}
|
|
754
|
+
if (targetIndent === 0) {
|
|
755
|
+
if (fragment.template) {
|
|
756
|
+
output.templates[fragment.id] = fragment.html;
|
|
757
|
+
}
|
|
758
|
+
else if (fragment.type !== 'root') {
|
|
759
|
+
parsed.set(fragment.id, fragment);
|
|
760
|
+
}
|
|
761
|
+
fragment = makeFragment();
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
element = makeElement(targetIndent);
|
|
766
|
+
}
|
|
767
|
+
return [element, fragment];
|
|
768
|
+
}
|
|
769
|
+
function flatten(fragment, claimed, parsed, locals = new Set()) {
|
|
770
|
+
let r;
|
|
771
|
+
if (Array.isArray(fragment.refs)) {
|
|
772
|
+
for (let i = fragment.refs.length - 1; i > -1; i--) {
|
|
773
|
+
r = fragment.refs[i];
|
|
774
|
+
if (locals.has(r.id) || claimed.has(r.id) || !parsed.has(r.id)) {
|
|
775
|
+
// cannot use fragment here. Clear the reference marker
|
|
776
|
+
fragment.html = fragment.html.slice(0, r.start)
|
|
777
|
+
+ fragment.html.slice(r.end);
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
locals.add(fragment.id);
|
|
781
|
+
const child = flatten(parsed.get(r.id), claimed, parsed, locals);
|
|
782
|
+
fragment.html = fragment.html.slice(0, r.start)
|
|
783
|
+
+ child.html
|
|
784
|
+
+ fragment.html.slice(r.end);
|
|
785
|
+
if (child.type === 'embed') {
|
|
786
|
+
claimed.add(child.id);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
// don't re-process the fragment
|
|
792
|
+
fragment.refs = undefined;
|
|
793
|
+
return fragment;
|
|
733
794
|
}
|
|
734
795
|
|
|
735
796
|
export { longform, processTemplate };
|