@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/lib/longform.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DirectiveDef, Element, ElementCtx, FragmentRef, FragmentType, LongformArgs, ParsedResult, SerializationElement, SerializerConfig, WorkingElement, WorkingFragment } from "./types.ts";
|
|
2
2
|
|
|
3
3
|
const LINE = 0
|
|
4
4
|
, INDENT = 1
|
|
@@ -10,35 +10,32 @@ const LINE = 0
|
|
|
10
10
|
, FRAGMENT_TYPE = 7
|
|
11
11
|
, ELEMENT = 8
|
|
12
12
|
, ATTR = 9
|
|
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
|
-
, m2: RegExpExecArray | null
|
|
40
|
-
, m3: RegExpExecArray | null
|
|
41
|
-
, m4: RegExpExecArray | null
|
|
13
|
+
, COMMENT = 10
|
|
14
|
+
, 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
|
|
15
|
+
|
|
16
|
+
// captures a single element definition which could be in a chain.
|
|
17
|
+
// id, class and attributes are matched as a single block for a later loop
|
|
18
|
+
// if in chained situation the regexp will need to be looped over for each element
|
|
19
|
+
// we know the element is the last if the final capture group has a positive match
|
|
20
|
+
// or if the line is consumed by the regexp.
|
|
21
|
+
, outerRe = /([a-z][\w\-]*(?::[a-z][\w\-]*)?)((?:(?:[^:])|(?::(?!:)))*)::(?: (?:({{?)|(.*)))?/gi
|
|
22
|
+
|
|
23
|
+
// captures each id, class and attribute declaration in an element
|
|
24
|
+
, innerRe = /(?:\.([a-z][\w\-]+)|#([a-z][\w\-]+)|\[([a-z][a-z\-]+(?::[a-z][a-z|\-]*)?)(?:=(?:"([^"]+)"|'([^']+)'|([^\]]+)))?\])/gi
|
|
25
|
+
, attributeRe = /((?:\ \ )+)\[(\w[\w-]*(?::\w[\w-]*)?)(?:=([^\n]+))?\]/
|
|
26
|
+
, attributeDirectiveRe = /^@([a-z][\w\-]*(?::[a-z][\w\-]*)?)$/i
|
|
27
|
+
, idRe = /((?:\ \ )+)?#(#)?([\w\-]+)(?: ([\["]))?/i
|
|
28
|
+
, refRe = /#\[([\w\-]+)\]/g
|
|
29
|
+
, escapeRe = /([&<>"'#\[\]{}])/g
|
|
30
|
+
, templateLinesRe = /^(\ \ )?([^\n]+)$/gm
|
|
31
|
+
, templateRe = /#(#)?{([a-z][\w\-]*(?::[a-z][\w\-]*)?)}|#\[([a-z][\w\-]*(?::[a-z][\w\-]*)?)\]/g
|
|
32
|
+
, preformatClose = new Set(['}', '}}'])
|
|
33
|
+
, voids = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wrb']);
|
|
34
|
+
|
|
35
|
+
let m1: RegExpExecArray | null = null
|
|
36
|
+
, m2: RegExpExecArray | null = null
|
|
37
|
+
, m3: RegExpExecArray | null = null
|
|
38
|
+
, m4: RegExpExecArray | null = null
|
|
42
39
|
|
|
43
40
|
const entities = {
|
|
44
41
|
'&': '&',
|
|
@@ -55,7 +52,7 @@ const entities = {
|
|
|
55
52
|
|
|
56
53
|
function escape(value: string): string {
|
|
57
54
|
return value.replace(escapeRe, (match) => {
|
|
58
|
-
return entities[match] ?? match;
|
|
55
|
+
return entities[match as keyof typeof entities] ?? match;
|
|
59
56
|
});
|
|
60
57
|
}
|
|
61
58
|
|
|
@@ -64,7 +61,7 @@ function makeElement(indent: number = 0): WorkingElement {
|
|
|
64
61
|
indent,
|
|
65
62
|
key: undefined,
|
|
66
63
|
id: undefined,
|
|
67
|
-
tag: undefined,
|
|
64
|
+
tag: undefined as unknown as string,
|
|
68
65
|
class: undefined,
|
|
69
66
|
text: undefined,
|
|
70
67
|
attrs: {},
|
|
@@ -78,6 +75,7 @@ function makeElement(indent: number = 0): WorkingElement {
|
|
|
78
75
|
|
|
79
76
|
function makeFragment(type: FragmentType = 'bare'): WorkingFragment {
|
|
80
77
|
return {
|
|
78
|
+
id: undefined as unknown as string,
|
|
81
79
|
type,
|
|
82
80
|
html: '',
|
|
83
81
|
template: false,
|
|
@@ -153,24 +151,30 @@ const directiveValidator = /^[a-z][a-z\-]*\:[a-z][a-z\-]*$/i;
|
|
|
153
151
|
* @param input - The Longform document to parse.
|
|
154
152
|
* @param args - Arguments for the Longform parser.
|
|
155
153
|
*/
|
|
156
|
-
export function longform(
|
|
154
|
+
export async function longform(
|
|
157
155
|
input: string,
|
|
158
156
|
args?: LongformArgs,
|
|
159
|
-
): ParsedResult {
|
|
157
|
+
): Promise<ParsedResult> {
|
|
160
158
|
let skipping: boolean = false
|
|
161
|
-
,
|
|
162
|
-
,
|
|
163
|
-
,
|
|
164
|
-
, verbatimFirst: boolean = false
|
|
159
|
+
, verbatimText: string = ''
|
|
160
|
+
, verbatimIndent: number = 0
|
|
161
|
+
, verbatimType: number = 0
|
|
165
162
|
, element: WorkingElement = makeElement()
|
|
166
163
|
, fragment: WorkingFragment = makeFragment()
|
|
164
|
+
, directive!: {
|
|
165
|
+
name: string;
|
|
166
|
+
inlineArgs: string;
|
|
167
|
+
def?: DirectiveDef,
|
|
168
|
+
}
|
|
167
169
|
// the root fragment
|
|
168
170
|
, root: WorkingFragment | null = null
|
|
169
171
|
, id: string | undefined = args?.id
|
|
170
172
|
, lang: string | undefined = args?.lang
|
|
171
173
|
, dir: string | undefined = args?.dir
|
|
172
174
|
, meta: Record<string, unknown> = {}
|
|
173
|
-
,
|
|
175
|
+
, data: Record<string, unknown> = {}
|
|
176
|
+
, doc!: Doc
|
|
177
|
+
, asyncCount: number = 0;
|
|
174
178
|
// ids of claimed fragments
|
|
175
179
|
const claimed: Set<string> = new Set()
|
|
176
180
|
// parsed fragments
|
|
@@ -183,9 +187,10 @@ export function longform(
|
|
|
183
187
|
dir: { attr: ctx => ctx.doc.dir },
|
|
184
188
|
lang: { attr: ctx => ctx.doc.lang },
|
|
185
189
|
}
|
|
190
|
+
, promises: Array<Promise<void>> = []
|
|
186
191
|
|
|
187
192
|
let key: string = args?.key as string;
|
|
188
|
-
|
|
193
|
+
|
|
189
194
|
if (!args?.predictable && key == null) {
|
|
190
195
|
const arr = new Uint8Array(10);
|
|
191
196
|
|
|
@@ -202,290 +207,230 @@ export function longform(
|
|
|
202
207
|
if (args?.directives != null) {
|
|
203
208
|
const entries = Object.entries(args.directives);
|
|
204
209
|
|
|
205
|
-
for (let i = 0, l = entries.length; i < l; i++) {
|
|
206
|
-
if (!directiveValidator.test(
|
|
207
|
-
console.warn(`Invalid custom directive name '${
|
|
210
|
+
for (let i = 0, l = entries.length, e = entries[i]; i < l; i++) {
|
|
211
|
+
if (!directiveValidator.test(e[0])) {
|
|
212
|
+
console.warn(`Invalid custom directive name '$e{[0]}'`);
|
|
208
213
|
|
|
209
214
|
continue;
|
|
210
215
|
}
|
|
211
216
|
|
|
212
|
-
directives[
|
|
217
|
+
directives[e[0]] = e[1];
|
|
213
218
|
}
|
|
214
219
|
}
|
|
215
220
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
class: element.class,
|
|
226
|
-
attrs: element.attrs,
|
|
227
|
-
};
|
|
228
|
-
const chain: SimplifiedElement[] = [];
|
|
229
|
-
|
|
230
|
-
for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
|
|
231
|
-
chain.push({
|
|
232
|
-
id: el.id,
|
|
233
|
-
tag: el.tag as string,
|
|
234
|
-
class: el.class,
|
|
235
|
-
attrs: el.attrs,
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
for (let i = 0, l = element.beforeRender.length, def = element.beforeRender[i]; i < l; i++) {
|
|
240
|
-
def.beforeRender({
|
|
241
|
-
el,
|
|
242
|
-
chain,
|
|
243
|
-
doc: doc as Doc,
|
|
244
|
-
inlineArg: def.inlineArg,
|
|
245
|
-
blockArg: def.blockArg,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
221
|
+
// This is a hack to allow open verbatim blocks to detect
|
|
222
|
+
// when they close via an extra step in the loop.
|
|
223
|
+
input += '\n ';
|
|
224
|
+
sniffTestRe.lastIndex = 0;
|
|
225
|
+
main: while ((m1 = sniffTestRe.exec(input))) {
|
|
226
|
+
if (m1[COMMENT] === '--') {
|
|
227
|
+
continue;
|
|
228
|
+
} else if (fragment.template) {
|
|
229
|
+
fragment.html += m1[0];
|
|
248
230
|
}
|
|
249
231
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (attr[1] == null) {
|
|
269
|
-
fragment.html += ' ' + attr[0]
|
|
232
|
+
const indent = m1[INDENT].length / 2;
|
|
233
|
+
|
|
234
|
+
// only one root fragment is allowed. Skip until beginning of next fragment / directive.
|
|
235
|
+
if (skipping && indent !== 0) continue;
|
|
236
|
+
// I hear avoiding branching is faster than avoiding assignments...
|
|
237
|
+
skipping = false;
|
|
238
|
+
|
|
239
|
+
// verbatim blocks collect the string as is as a continuous block
|
|
240
|
+
// and do processing on it once the full block has been collected.
|
|
241
|
+
if (verbatimType !== 0) {
|
|
242
|
+
if (indent >= verbatimIndent && (
|
|
243
|
+
// text verbatim type should exit when other Longform constructs begin.
|
|
244
|
+
verbatimType !== 1 ||
|
|
245
|
+
(m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) === undefined
|
|
246
|
+
)) {
|
|
247
|
+
if (verbatimType === 1) {
|
|
248
|
+
// Text verbatim blocks join with blank spaces
|
|
249
|
+
verbatimText += ' ' + m1[0].trim();
|
|
270
250
|
} else {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (root) {
|
|
276
|
-
if (fragment.type === 'root') {
|
|
277
|
-
fragment.html += ` data-${key}-root`;
|
|
278
|
-
} else if (fragment.type === 'bare' || fragment.type === 'range') {
|
|
279
|
-
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
280
|
-
} else if (fragment.type === 'embed' && !args?.predictable) {
|
|
281
|
-
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
251
|
+
if (verbatimText !== '') verbatimText += '\n';
|
|
252
|
+
|
|
253
|
+
// other verbatim blocks normalize the Longform indent of the block away
|
|
254
|
+
verbatimText += m1[0].replace(' '.repeat(verbatimIndent), '');
|
|
282
255
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
fragment.html += '>';
|
|
290
|
-
|
|
291
|
-
if (Array.isArray(element.chain)) {
|
|
292
|
-
let chained: WorkingElement;
|
|
256
|
+
continue;
|
|
257
|
+
} else if (m1[0].trim() === '' &&
|
|
258
|
+
input.length !== m1.index + m1[0].length) {
|
|
259
|
+
// blank line in verbatim are ignored by text types
|
|
260
|
+
if (verbatimType !== 1) verbatimText += '\n';
|
|
293
261
|
|
|
294
|
-
|
|
295
|
-
|
|
262
|
+
continue;
|
|
263
|
+
} else {
|
|
264
|
+
// verbatim block is finished
|
|
265
|
+
switch (verbatimType) {
|
|
266
|
+
case 1:
|
|
267
|
+
// text
|
|
268
|
+
fragment.html += verbatimText;
|
|
269
|
+
|
|
270
|
+
// locate reference points in text
|
|
271
|
+
while ((m2 = refRe.exec(verbatimText))) {
|
|
272
|
+
const start = fragment.html.length + m2.index - verbatimText.length;
|
|
273
|
+
|
|
274
|
+
fragment.refs.push({
|
|
275
|
+
id: m2[1],
|
|
276
|
+
start,
|
|
277
|
+
end: start + m2[0].length,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
296
280
|
|
|
297
|
-
|
|
281
|
+
applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
282
|
+
break
|
|
283
|
+
case 2:
|
|
284
|
+
// escaped preformatted text
|
|
285
|
+
fragment.html += escape(verbatimText);
|
|
298
286
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
287
|
+
break;
|
|
288
|
+
case 3:
|
|
289
|
+
// preformatted
|
|
290
|
+
fragment.html += verbatimText + '\n';
|
|
302
291
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
292
|
+
break;
|
|
293
|
+
case 4:
|
|
294
|
+
// directive block args
|
|
295
|
+
if (directive.def == null) break;
|
|
296
|
+
if (doc == null) {
|
|
297
|
+
if (typeof directive.def.meta === 'function') {
|
|
298
|
+
meta[m1[DIRECTIVE]] = directive.def.meta({
|
|
299
|
+
inlineArgs: directive.inlineArgs,
|
|
300
|
+
blockArgs: verbatimText,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
} else if (typeof directive.def.render === 'function') {
|
|
304
|
+
try {
|
|
305
|
+
element.html += directive.def.render({
|
|
306
|
+
doc,
|
|
307
|
+
inlineArgs: directive.inlineArgs,
|
|
308
|
+
blockArgs: verbatimText,
|
|
309
|
+
}) + ' ';
|
|
310
|
+
} catch (err) {
|
|
311
|
+
console.error(`Error in calling directive ${directive.name}`)
|
|
312
|
+
console.error(err)
|
|
313
|
+
}
|
|
314
|
+
} else if (typeof directive.def.asyncRender === 'function') {
|
|
315
|
+
asyncCount++;
|
|
316
|
+
|
|
317
|
+
// async rendering uses the #[ref] feature to insert the
|
|
318
|
+
// eventual response.
|
|
319
|
+
const directiveFragment = makeFragment('embed');
|
|
320
|
+
|
|
321
|
+
directiveFragment.id = `@${asyncCount}`;
|
|
322
|
+
parsed.set(directiveFragment.id, directiveFragment);
|
|
323
|
+
|
|
324
|
+
fragment.refs.push({
|
|
325
|
+
id: directiveFragment.id,
|
|
326
|
+
start: fragment.html.length,
|
|
327
|
+
end: fragment.html.length,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// in case text follows we want a space separating the render output
|
|
331
|
+
fragment.html += ' ';
|
|
332
|
+
|
|
333
|
+
promises.push(
|
|
334
|
+
directive.def.asyncRender({
|
|
335
|
+
doc,
|
|
336
|
+
inlineArgs: directive.inlineArgs,
|
|
337
|
+
blockArgs: verbatimText,
|
|
338
|
+
}).then(res => {
|
|
339
|
+
directiveFragment.html = res ?? '';
|
|
340
|
+
}).catch(err => {
|
|
341
|
+
console.error(`Error in calling directive ${directive.name}`)
|
|
342
|
+
console.error(err)
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
} else if (typeof directive.def.element === 'function') {
|
|
346
|
+
if (element.beforeRender == null) element.beforeRender = [];
|
|
306
347
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
348
|
+
element.beforeRender.push({
|
|
349
|
+
blockArgs: verbatimText,
|
|
350
|
+
inlineArgs: directive.inlineArgs,
|
|
351
|
+
element: directive.def.element,
|
|
352
|
+
});
|
|
310
353
|
} else {
|
|
311
|
-
|
|
354
|
+
console.warn(`Directive used in incorrect context ${directive.name}`);
|
|
312
355
|
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
fragment.html += '>';
|
|
316
356
|
}
|
|
317
|
-
}
|
|
318
357
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
358
|
+
verbatimType = 0;
|
|
359
|
+
verbatimText = '';
|
|
322
360
|
|
|
323
|
-
|
|
324
|
-
!voids.has(element.tag as string)
|
|
325
|
-
) {
|
|
326
|
-
fragment.els.push(element);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (targetIndent <= element.indent) {
|
|
331
|
-
element = makeElement(targetIndent);
|
|
332
|
-
|
|
333
|
-
while (
|
|
334
|
-
fragment.els.length !== 0 && (
|
|
335
|
-
targetIndent == null ||
|
|
336
|
-
fragment.els[fragment.els.length - 1].indent !== targetIndent - 1
|
|
337
|
-
)
|
|
338
|
-
) {
|
|
339
|
-
const element = fragment.els.pop() as WorkingElement;
|
|
340
|
-
|
|
341
|
-
if (Array.isArray(element.chain)) {
|
|
342
|
-
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
343
|
-
fragment.html += `</${element.chain[i].tag}>`;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
fragment.html += `</${element?.tag}>`;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (targetIndent === 0) {
|
|
351
|
-
if (fragment.template) {
|
|
352
|
-
output.templates[fragment.id] = fragment.html;
|
|
353
|
-
} else if (fragment.type === 'root') {
|
|
354
|
-
root = fragment;
|
|
355
|
-
} else {
|
|
356
|
-
parsed.set(fragment.id, fragment);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
fragment = makeFragment();
|
|
360
|
-
}
|
|
361
|
-
} else {
|
|
362
|
-
element = makeElement(targetIndent)
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
main: while ((m1 = sniffTestRe.exec(input))) {
|
|
367
|
-
if (m1[1] === '--') {
|
|
368
|
-
continue;
|
|
369
|
-
} else if (fragment.template) {
|
|
370
|
-
fragment.html += m1[0];
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// If this is a script tag or preformatted block
|
|
374
|
-
// we want to retain the intended formatting less
|
|
375
|
-
// the indent. Pre-formatting can apply to any element
|
|
376
|
-
// by ending the declaration with `:: {`.
|
|
377
|
-
if (verbatimIndent != null) {
|
|
378
|
-
// inside a script or preformatted block
|
|
379
|
-
identRe.lastIndex = 0;
|
|
380
|
-
m2 = identRe.exec(m1[0]);
|
|
381
|
-
const indent = m2 == null
|
|
382
|
-
? null
|
|
383
|
-
: m2[0].length / 2;
|
|
384
|
-
|
|
385
|
-
if (m2 == null || indent as number <= verbatimIndent) {
|
|
386
|
-
fragment.html += '\n';
|
|
387
|
-
|
|
388
|
-
applyIndent(indent);
|
|
389
|
-
verbatimIndent = null;
|
|
390
|
-
verbatimFirst = false;
|
|
391
|
-
textIndent = indent;
|
|
392
|
-
|
|
393
|
-
if (preformattedClose.test(m1[0])) {
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
} else {
|
|
397
|
-
const line = m1[0].replace(' '.repeat(verbatimIndent + 1), '');
|
|
398
|
-
|
|
399
|
-
if (element.tag != null) {
|
|
400
|
-
applyIndent(indent as number);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (verbatimFirst) {
|
|
404
|
-
verbatimFirst = false;
|
|
405
|
-
} else {
|
|
406
|
-
fragment.html += '\n';
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (verbatimSerialize) {
|
|
410
|
-
fragment.html += line;
|
|
411
|
-
} else {
|
|
412
|
-
fragment.html += escape(line);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
continue;
|
|
361
|
+
if (preformatClose.has(m1[0].trim())) continue;
|
|
416
362
|
}
|
|
417
363
|
}
|
|
418
364
|
|
|
419
365
|
if (m1[LINE].trim() === '') {
|
|
366
|
+
// empty lines have no effect from here on
|
|
420
367
|
continue;
|
|
421
368
|
}
|
|
422
369
|
|
|
423
|
-
// The id and lang directives should proceed all other directives and
|
|
424
|
-
// fragment declarations.
|
|
425
370
|
if (doc === undefined) {
|
|
371
|
+
// The meta directives get special treatment.
|
|
372
|
+
// Parsers can use the head output type to output
|
|
373
|
+
// the meta results only.
|
|
374
|
+
let parseBlock = false;
|
|
426
375
|
const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? '';
|
|
427
|
-
|
|
376
|
+
|
|
428
377
|
switch (m1[DIRECTIVE]) {
|
|
429
|
-
case 'id':
|
|
378
|
+
case 'id':
|
|
430
379
|
const url = inlineArgs.trim();
|
|
380
|
+
|
|
381
|
+
parseBlock = true;
|
|
382
|
+
|
|
431
383
|
try {
|
|
432
384
|
id = id ?? new URL(url, args?.base).toString();
|
|
433
385
|
} catch (err) {
|
|
434
386
|
console.warn(
|
|
435
|
-
`Invalid URL given to @id directive: ${url} base=${args
|
|
387
|
+
`Invalid URL given to @id directive: ${url} base=${args?.base}`
|
|
436
388
|
);
|
|
437
389
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
390
|
+
break;
|
|
391
|
+
case 'lang':
|
|
392
|
+
parseBlock = true;
|
|
441
393
|
lang = lang ?? inlineArgs.trim();
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
394
|
+
break;
|
|
395
|
+
case 'dir':
|
|
396
|
+
parseBlock = true;
|
|
445
397
|
dir = dir ?? inlineArgs.trim();
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
default: {
|
|
449
|
-
const def = directives[m1[DIRECTIVE]];
|
|
450
|
-
|
|
451
|
-
if (typeof def?.meta === 'function') {
|
|
452
|
-
if (Object.keys(def).length > 1) {
|
|
453
|
-
throw new Error(
|
|
454
|
-
`A custom directive performing the meta role cannot be used for other purposes. ` +
|
|
455
|
-
`See @${m1[DIRECTIVE]}`,
|
|
456
|
-
);
|
|
457
|
-
}
|
|
398
|
+
}
|
|
458
399
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
400
|
+
// Even though the builtin meta directives do not support block args
|
|
401
|
+
// the args need to be parsed so they do not end up in the output.
|
|
402
|
+
const def = directives[m1[DIRECTIVE]];
|
|
403
|
+
|
|
404
|
+
if (typeof def?.meta === 'function'|| parseBlock) {
|
|
405
|
+
verbatimIndent = indent + 1;
|
|
406
|
+
verbatimType = 4;
|
|
407
|
+
directive = {
|
|
408
|
+
name: m1[DIRECTIVE],
|
|
409
|
+
inlineArgs,
|
|
410
|
+
def,
|
|
462
411
|
}
|
|
463
|
-
}
|
|
464
412
|
|
|
465
|
-
|
|
413
|
+
continue main;
|
|
414
|
+
}
|
|
466
415
|
|
|
467
|
-
if (args?.outputMode === '
|
|
416
|
+
if (args?.outputMode === 'head') {
|
|
468
417
|
return {
|
|
469
418
|
key,
|
|
470
419
|
id,
|
|
471
420
|
lang,
|
|
472
421
|
dir,
|
|
473
|
-
meta
|
|
474
|
-
|
|
422
|
+
meta,
|
|
423
|
+
data,
|
|
424
|
+
} as unknown as ParsedResult;
|
|
475
425
|
}
|
|
426
|
+
|
|
427
|
+
doc = new Doc(id, lang, dir, meta, args?.allowAll, args?.allowedAttributes, args?.allowedElements);
|
|
476
428
|
}
|
|
477
429
|
|
|
478
430
|
switch (m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) {
|
|
479
431
|
case '#':
|
|
480
|
-
case '##':
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const indent = (m1[INDENT].length ?? 0) / 2;
|
|
484
|
-
|
|
485
|
-
if (element.tag != null || textIndent != null) {
|
|
486
|
-
applyIndent(indent);
|
|
487
|
-
textIndent = null;
|
|
488
|
-
}
|
|
432
|
+
case '##':
|
|
433
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
489
434
|
|
|
490
435
|
fragment.id = m1[ID];
|
|
491
436
|
|
|
@@ -505,90 +450,76 @@ export function longform(
|
|
|
505
450
|
}
|
|
506
451
|
|
|
507
452
|
break;
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (element.tag != null || textIndent != null) {
|
|
513
|
-
applyIndent(indent);
|
|
453
|
+
case '@':
|
|
454
|
+
if (element.tag !== undefined) {
|
|
455
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
514
456
|
}
|
|
515
457
|
|
|
458
|
+
const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? ''
|
|
459
|
+
|
|
516
460
|
switch (m1[DIRECTIVE]) {
|
|
517
|
-
case '
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if (m3 != null) fragment.id = m3[3];
|
|
538
|
-
|
|
539
|
-
fragment.html += m2[0];
|
|
540
|
-
} else if (m2[1] == null && indented) {
|
|
541
|
-
sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
|
|
542
|
-
break;
|
|
543
|
-
} else {
|
|
544
|
-
fragment.html += '\n' + m2[0];
|
|
545
|
-
if (m2[1] != null) indented = true;
|
|
461
|
+
case 'template':
|
|
462
|
+
if (indent === 0) {
|
|
463
|
+
let indented = false;
|
|
464
|
+
fragment.template = true;
|
|
465
|
+
|
|
466
|
+
templateLinesRe.lastIndex = sniffTestRe.lastIndex;
|
|
467
|
+
while ((m2 = templateLinesRe.exec(input))) {
|
|
468
|
+
if (m2[1] == null && !indented && fragment.id == null) {
|
|
469
|
+
m3 = idRe.exec(m2[0]);
|
|
470
|
+
|
|
471
|
+
if (m3 != null) fragment.id = m3[3];
|
|
472
|
+
|
|
473
|
+
fragment.html += m2[0];
|
|
474
|
+
} else if (m2[1] == null && indented) {
|
|
475
|
+
sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
|
|
476
|
+
break;
|
|
477
|
+
} else {
|
|
478
|
+
fragment.html += '\n' + m2[0];
|
|
479
|
+
if (m2[1] != null) indented = true;
|
|
480
|
+
}
|
|
546
481
|
}
|
|
482
|
+
|
|
483
|
+
[element, fragment] = applyIndent(0, key, element, fragment, doc, parsed, output, args);
|
|
547
484
|
}
|
|
548
485
|
|
|
549
|
-
|
|
486
|
+
continue main;
|
|
487
|
+
case 'doctype':
|
|
488
|
+
fragment.html += `<!doctype ${(inlineArgs.trim() || 'html').trim()}>`;
|
|
550
489
|
break;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
490
|
+
case 'xml':
|
|
491
|
+
fragment.html += `<?xml ${inlineArgs.trim() || 'version="1.0" encoding="UTF-8"'}?>`;
|
|
492
|
+
break;
|
|
493
|
+
case 'mount':
|
|
494
|
+
if (args?.outputMode !== 'mountable') break;
|
|
495
|
+
|
|
496
|
+
if (inlineArgs === '') {
|
|
497
|
+
console.warn('Mount points must have a name');
|
|
555
498
|
} else if (fragment.type !== 'root') {
|
|
556
|
-
|
|
499
|
+
console.warn('Mounting is only allowed on a root element');
|
|
557
500
|
}
|
|
558
501
|
|
|
559
502
|
fragment.mountable = true;
|
|
560
|
-
element.mount =
|
|
503
|
+
element.mount = inlineArgs.trim();
|
|
561
504
|
break;
|
|
562
|
-
}
|
|
563
|
-
default: {
|
|
564
|
-
const def = directives[m1[DIRECTIVE]];
|
|
565
|
-
|
|
566
|
-
if (def == null) break;
|
|
567
|
-
|
|
568
|
-
if (typeof def.beforeRender === 'function') {
|
|
569
|
-
if (element.id != null) {
|
|
570
|
-
applyIndent(indent);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
if (element.beforeRender == null) element.beforeRender = [];
|
|
574
|
-
|
|
575
|
-
// TODO: Parse block args
|
|
576
|
-
const applied: AppliedDirective = {
|
|
577
|
-
...def,
|
|
578
|
-
inlineArg: m1[DIRECTIVE_INLINE_ARGS],
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
element.beforeRender.push(applied);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
505
|
}
|
|
585
506
|
|
|
507
|
+
const def = directives[m1[DIRECTIVE]];
|
|
508
|
+
|
|
509
|
+
// A directive may not be defined but we want to process
|
|
510
|
+
// any block args to keep the output valid. Builtin directives
|
|
511
|
+
// will be ignored unless they require block args.
|
|
512
|
+
verbatimIndent = indent + 1;
|
|
513
|
+
verbatimType = 4;
|
|
514
|
+
directive = {
|
|
515
|
+
name: m1[DIRECTIVE],
|
|
516
|
+
inlineArgs: m1[DIRECTIVE_INLINE_ARGS],
|
|
517
|
+
def,
|
|
518
|
+
};
|
|
519
|
+
|
|
586
520
|
break;
|
|
587
|
-
|
|
588
|
-
case '[':
|
|
589
|
-
case '::': {
|
|
521
|
+
case '::':
|
|
590
522
|
if (m1[ELEMENT] !== undefined) {
|
|
591
|
-
const indent = (m1[INDENT]?.length ?? 0) / 2;
|
|
592
523
|
let preformattedType: string | undefined;
|
|
593
524
|
let inlineText: string | undefined;
|
|
594
525
|
|
|
@@ -596,12 +527,10 @@ export function longform(
|
|
|
596
527
|
element.tag !== undefined ||
|
|
597
528
|
element.indent > indent
|
|
598
529
|
) {
|
|
599
|
-
applyIndent(indent);
|
|
530
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
600
531
|
}
|
|
601
532
|
|
|
602
533
|
element.indent = indent;
|
|
603
|
-
|
|
604
|
-
textIndent = null;
|
|
605
534
|
|
|
606
535
|
if (indent === 0 && fragment.id == null) {
|
|
607
536
|
if (root != null) {
|
|
@@ -615,12 +544,11 @@ export function longform(
|
|
|
615
544
|
}
|
|
616
545
|
|
|
617
546
|
const parent = element;
|
|
618
|
-
|
|
619
|
-
|
|
547
|
+
outerRe.lastIndex = 0;
|
|
620
548
|
|
|
621
549
|
// Looping through chained element declarations
|
|
622
550
|
// foo#x.y::bar::free::
|
|
623
|
-
while ((m2 =
|
|
551
|
+
while ((m2 = outerRe.exec(m1[LINE]))) {
|
|
624
552
|
let working: WorkingElement;
|
|
625
553
|
|
|
626
554
|
preformattedType = m2[3];
|
|
@@ -636,9 +564,9 @@ export function longform(
|
|
|
636
564
|
parent.chain.push(working);
|
|
637
565
|
}
|
|
638
566
|
|
|
639
|
-
|
|
567
|
+
innerRe.lastIndex = 0;
|
|
640
568
|
// Looping through ids, classes and attrs
|
|
641
|
-
while((m3 =
|
|
569
|
+
while((m3 = innerRe.exec(m2[2]))) {
|
|
642
570
|
if (m3[2] !== undefined) {
|
|
643
571
|
working.id = m3[2];
|
|
644
572
|
} else if (m3[1] !== undefined) {
|
|
@@ -649,12 +577,12 @@ export function longform(
|
|
|
649
577
|
}
|
|
650
578
|
} else {
|
|
651
579
|
// TODO: Preserve quoting style around attribute values
|
|
652
|
-
let value = m3[4] ?? m3[5] ?? m3[6];
|
|
580
|
+
let value: string | boolean | undefined | null = m3[4] ?? m3[5] ?? m3[6];
|
|
653
581
|
|
|
654
582
|
// attribute directives
|
|
655
583
|
if (value[0] === '@') {
|
|
656
|
-
|
|
657
|
-
m4 =
|
|
584
|
+
attributeDirectiveRe.lastIndex = 0;
|
|
585
|
+
m4 = attributeDirectiveRe.exec(value);
|
|
658
586
|
|
|
659
587
|
if (m4 != null) {
|
|
660
588
|
const def = directives[m4[1]];
|
|
@@ -671,19 +599,19 @@ export function longform(
|
|
|
671
599
|
|
|
672
600
|
switch (m3[3]) {
|
|
673
601
|
case 'id':
|
|
674
|
-
if (!working.id) {
|
|
602
|
+
if (!working.id && typeof value === 'string') {
|
|
675
603
|
working.id = value;
|
|
676
604
|
}
|
|
677
605
|
break;
|
|
678
606
|
case 'class':
|
|
679
|
-
if (!working.class) {
|
|
607
|
+
if (!working.class && typeof value === 'string') {
|
|
680
608
|
working.class = value;
|
|
681
|
-
} else {
|
|
609
|
+
} else if (typeof value === 'string') {
|
|
682
610
|
working.class += ' ' + value;
|
|
683
611
|
}
|
|
684
612
|
break;
|
|
685
613
|
default:
|
|
686
|
-
working.attrs[m3[3]] = value;
|
|
614
|
+
if (value !== false) working.attrs[m3[3]] = value;
|
|
687
615
|
}
|
|
688
616
|
}
|
|
689
617
|
}
|
|
@@ -694,31 +622,39 @@ export function longform(
|
|
|
694
622
|
// server specific process.
|
|
695
623
|
if (element.mount != null) {
|
|
696
624
|
const id = element.mount;
|
|
697
|
-
|
|
625
|
+
|
|
626
|
+
[element, fragment] = applyIndent(indent + 1, key, element, fragment, doc, parsed, output, args);
|
|
627
|
+
|
|
628
|
+
if (fragment.mountPoints == null) fragment.mountPoints = [];
|
|
629
|
+
|
|
698
630
|
fragment.mountPoints.push({
|
|
699
631
|
id,
|
|
700
632
|
part: fragment.html,
|
|
701
633
|
});
|
|
634
|
+
|
|
702
635
|
fragment.html = '';
|
|
703
|
-
applyIndent(indent);
|
|
636
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
704
637
|
break;
|
|
705
638
|
}
|
|
706
639
|
|
|
707
640
|
if (preformattedType !== undefined) {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
641
|
+
if (element.tag !== undefined)
|
|
642
|
+
[element, fragment] = applyIndent(indent + 1, key, element, fragment, doc, parsed, output, args);
|
|
643
|
+
|
|
644
|
+
verbatimIndent = indent + 1;
|
|
645
|
+
verbatimType = preformattedType === '{{' ? 3 : 2
|
|
711
646
|
} else if (inlineText !== undefined) {
|
|
712
647
|
element.text = inlineText;
|
|
713
648
|
}
|
|
714
649
|
|
|
715
650
|
break;
|
|
716
651
|
}
|
|
717
|
-
|
|
718
|
-
|
|
652
|
+
case '[':
|
|
653
|
+
// TODO: Add attr directive support.
|
|
654
|
+
attributeRe.lastIndex = 0;
|
|
719
655
|
m2 = m1[ATTR] !== undefined
|
|
720
|
-
?
|
|
721
|
-
:
|
|
656
|
+
? attributeRe.exec(m1[LINE])
|
|
657
|
+
: null;
|
|
722
658
|
|
|
723
659
|
if (m2 != null && element.tag != null) {
|
|
724
660
|
if (m2[2] === 'id') {
|
|
@@ -739,84 +675,30 @@ export function longform(
|
|
|
739
675
|
|
|
740
676
|
break;
|
|
741
677
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
678
|
+
default:
|
|
679
|
+
if (element.tag !== undefined)
|
|
680
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
745
681
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const indent = m2[1].length / 2;
|
|
750
|
-
const tx = m2[2].trim();
|
|
751
|
-
|
|
752
|
-
if (element.tag != null) {
|
|
753
|
-
applyIndent(indent);
|
|
754
|
-
|
|
755
|
-
fragment.html += tx;
|
|
756
|
-
} else if (fragment.type === 'text' && fragment.html === '') {
|
|
757
|
-
fragment.html += tx;
|
|
758
|
-
} else {
|
|
759
|
-
fragment.html += ' ' + tx;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
textIndent = indent;
|
|
763
|
-
|
|
764
|
-
while ((m2 = refRe.exec(tx))) {
|
|
765
|
-
const start = fragment.html.length + m2.index - tx.length;
|
|
766
|
-
|
|
767
|
-
fragment.refs.push({
|
|
768
|
-
id: m2[1],
|
|
769
|
-
start,
|
|
770
|
-
end: start + m2[0].length,
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
break;
|
|
775
|
-
}
|
|
682
|
+
verbatimText = m1[0].trim();
|
|
683
|
+
verbatimIndent = indent;
|
|
684
|
+
verbatimType = 1;
|
|
776
685
|
}
|
|
777
686
|
}
|
|
778
687
|
|
|
779
|
-
applyIndent(0);
|
|
780
|
-
|
|
781
|
-
const arr = Array.from(parsed.values());
|
|
782
|
-
|
|
783
|
-
function flatten(fragment: WorkingFragment): WorkingFragment {
|
|
784
|
-
if (fragment.refs == null) fragment.refs = [];
|
|
785
|
-
|
|
786
|
-
// work backwards so we don't change the html string length
|
|
787
|
-
// for the later replacements
|
|
788
|
-
for (let j = fragment.refs.length - 1; j >= 0; j--) {
|
|
789
|
-
const ref = fragment.refs[j];
|
|
790
|
-
|
|
791
|
-
if (claimed.has(ref.id) || !parsed.has(ref.id)) {
|
|
792
|
-
fragment.html = fragment.html.slice(0, ref.start)
|
|
793
|
-
+ fragment.html.slice(ref.end)
|
|
794
|
-
} else {
|
|
795
|
-
const child = flatten(parsed.get(ref.id));
|
|
796
|
-
|
|
797
|
-
fragment.html = fragment.html.slice(0, ref.start)
|
|
798
|
-
+ child.html
|
|
799
|
-
+ fragment.html.slice(ref.end);
|
|
800
|
-
|
|
801
|
-
if (child.type === 'embed') {
|
|
802
|
-
claimed.add(child.id)
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
}
|
|
688
|
+
applyIndent(0, key, element, fragment, doc, parsed, output, args);
|
|
806
689
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
return fragment;
|
|
810
|
-
}
|
|
690
|
+
if (promises.length > 0) await Promise.all(promises);
|
|
811
691
|
|
|
812
692
|
if (root?.mountable) {
|
|
813
693
|
output.mountable = true;
|
|
814
694
|
output.tail = root.html;
|
|
815
|
-
output.mountPoints = root.mountPoints;
|
|
695
|
+
output.mountPoints = root.mountPoints ?? [];
|
|
816
696
|
|
|
817
697
|
return output;
|
|
818
698
|
}
|
|
819
699
|
|
|
700
|
+
const arr = Array.from(parsed.values());
|
|
701
|
+
|
|
820
702
|
for (let i = 0; i < parsed.size + 1; i++) {
|
|
821
703
|
let fragment: WorkingFragment;
|
|
822
704
|
|
|
@@ -832,38 +714,36 @@ export function longform(
|
|
|
832
714
|
continue;
|
|
833
715
|
}
|
|
834
716
|
|
|
835
|
-
flatten(fragment)
|
|
717
|
+
flatten(fragment, claimed, parsed);
|
|
836
718
|
}
|
|
837
|
-
|
|
719
|
+
|
|
838
720
|
if (root?.html != null) {
|
|
839
721
|
output.root = root.html;
|
|
840
722
|
output.selector = `[data-${key}-root]`;
|
|
841
723
|
}
|
|
842
724
|
|
|
843
|
-
for (let i = 0
|
|
725
|
+
for (let i = 0, l = arr.length, f = arr[i]; i < l; i++) {
|
|
844
726
|
let selector!: string;
|
|
845
|
-
const fragment: WorkingFragment = arr[i];
|
|
846
727
|
|
|
847
|
-
if (
|
|
728
|
+
if (f == null || claimed.has(f.id as string)) {
|
|
848
729
|
continue;
|
|
849
730
|
}
|
|
850
731
|
|
|
851
|
-
switch (
|
|
852
|
-
case 'embed':
|
|
732
|
+
switch (f.type) {
|
|
733
|
+
case 'embed':
|
|
853
734
|
if (args?.predictable) {
|
|
854
|
-
selector = `[id=${
|
|
735
|
+
selector = `[id=${f.id}]`;
|
|
855
736
|
break;
|
|
856
737
|
}
|
|
857
|
-
}
|
|
858
738
|
case 'bare':
|
|
859
|
-
case 'range': selector = `[data-${key}=${
|
|
739
|
+
case 'range': selector = `[data-${key}=${f.id}]`;
|
|
860
740
|
}
|
|
861
741
|
|
|
862
|
-
output.fragments[
|
|
863
|
-
id:
|
|
742
|
+
output.fragments[f.id as string] = {
|
|
743
|
+
id: f.id as string,
|
|
864
744
|
selector,
|
|
865
|
-
type:
|
|
866
|
-
html:
|
|
745
|
+
type: f.type as 'embed' | 'bare' | 'range',
|
|
746
|
+
html: f.html,
|
|
867
747
|
};
|
|
868
748
|
}
|
|
869
749
|
|
|
@@ -884,13 +764,14 @@ export function longform(
|
|
|
884
764
|
* @param getFragment - A function which returns an already processed fragment's HTML string.
|
|
885
765
|
* @returns The processed template.
|
|
886
766
|
*/
|
|
887
|
-
export function processTemplate(
|
|
767
|
+
export async function processTemplate(
|
|
888
768
|
template: string,
|
|
889
769
|
args: Record<string, string | number>,
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
770
|
+
longformArgs?: LongformArgs,
|
|
771
|
+
getFragment?: (fragment: string) => string | undefined,
|
|
772
|
+
): Promise<string | undefined> {
|
|
773
|
+
const lf = template.replace(templateRe, (_line, _structured, param, ref) => {
|
|
774
|
+
if (ref !== undefined && typeof getFragment === 'function') {
|
|
894
775
|
const fragment = getFragment(ref);
|
|
895
776
|
|
|
896
777
|
if (fragment == null) return '';
|
|
@@ -901,6 +782,206 @@ export function processTemplate(
|
|
|
901
782
|
return args[param] != null ? escape(args[param].toString()) : '';
|
|
902
783
|
});
|
|
903
784
|
|
|
904
|
-
return Object.values(longform(lf).fragments)[0]?.html ?? null;
|
|
785
|
+
return Object.values((await longform(lf, longformArgs)).fragments)[0]?.html ?? null;
|
|
905
786
|
}
|
|
906
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Closes any current in progress element definition
|
|
790
|
+
* and creates a new working element.
|
|
791
|
+
*/
|
|
792
|
+
function applyIndent(
|
|
793
|
+
targetIndent: number,
|
|
794
|
+
key: string,
|
|
795
|
+
element: WorkingElement,
|
|
796
|
+
fragment: WorkingFragment,
|
|
797
|
+
doc: Doc,
|
|
798
|
+
parsed: Map<string, WorkingFragment>,
|
|
799
|
+
output: ParsedResult,
|
|
800
|
+
args?: LongformArgs,
|
|
801
|
+
): [element: WorkingElement, fragment: WorkingFragment] {
|
|
802
|
+
if (element.tag !== undefined) {
|
|
803
|
+
if (Array.isArray(element.beforeRender)) {
|
|
804
|
+
const el: Element = {
|
|
805
|
+
id: element.id,
|
|
806
|
+
tag: element.tag,
|
|
807
|
+
class: element.class,
|
|
808
|
+
attrs: element.attrs,
|
|
809
|
+
};
|
|
810
|
+
const chain: Element[] = [];
|
|
811
|
+
|
|
812
|
+
for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
|
|
813
|
+
chain.push({
|
|
814
|
+
id: el.id,
|
|
815
|
+
tag: el.tag,
|
|
816
|
+
class: el.class,
|
|
817
|
+
attrs: el.attrs,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
for (let i = 0, l = element.beforeRender.length, def = element.beforeRender[i]; i < l; i++) {
|
|
822
|
+
(def.element as (ctx: ElementCtx) => void)({
|
|
823
|
+
el,
|
|
824
|
+
chain,
|
|
825
|
+
doc,
|
|
826
|
+
inlineArgs: def.inlineArgs,
|
|
827
|
+
blockArgs: def.blockArgs,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const root = fragment.type === 'range'
|
|
833
|
+
? targetIndent < 2
|
|
834
|
+
: fragment.html === ''
|
|
835
|
+
;
|
|
836
|
+
|
|
837
|
+
fragment.html += `<${element.tag}`;
|
|
838
|
+
|
|
839
|
+
if (element.id !== undefined) {
|
|
840
|
+
fragment.html += ' id="' + element.id + '"';
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (element.class !== undefined) {
|
|
844
|
+
fragment.html += ' class="' + element.class + '"';
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
for (const attr of Object.entries(element.attrs)) {
|
|
848
|
+
if (attr[0] === 'id' || attr[0] === 'class') continue;
|
|
849
|
+
if (attr[1] == null) {
|
|
850
|
+
fragment.html += ' ' + attr[0]
|
|
851
|
+
} else {
|
|
852
|
+
fragment.html += ` ${attr[0]}="${attr[1]}"`;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (root) {
|
|
857
|
+
if (fragment.type === 'root') {
|
|
858
|
+
fragment.html += ` data-${key}-root`;
|
|
859
|
+
} else if (fragment.type === 'bare' || fragment.type === 'range') {
|
|
860
|
+
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
861
|
+
} else if (fragment.type === 'embed' && !args?.predictable) {
|
|
862
|
+
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (element.mount !== undefined) {
|
|
867
|
+
fragment.html += ` data-${key}-mount="${element.mount}"`;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
fragment.html += '>';
|
|
871
|
+
|
|
872
|
+
if (Array.isArray(element.chain)) {
|
|
873
|
+
let chained: WorkingElement;
|
|
874
|
+
|
|
875
|
+
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
876
|
+
chained = element.chain[i];
|
|
877
|
+
|
|
878
|
+
fragment.html += '<' + chained.tag;
|
|
879
|
+
|
|
880
|
+
if (chained.id !== undefined) {
|
|
881
|
+
fragment.html += ' id="' + chained.id + '"';
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (chained.class != undefined) {
|
|
885
|
+
fragment.html += ' class="' + chained.class + '"';
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
for (const attr of Object.entries(chained.attrs)) {
|
|
889
|
+
if (attr[1] === undefined) {
|
|
890
|
+
fragment.html += ' ' + attr[0]
|
|
891
|
+
} else {
|
|
892
|
+
fragment.html += ` ${attr[0]}="${attr[1]}"`;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
fragment.html += '>';
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (!voids.has(element.tag as string) && element.text != null) {
|
|
901
|
+
fragment.html += element.text;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (
|
|
905
|
+
!voids.has(element.tag as string)
|
|
906
|
+
) {
|
|
907
|
+
fragment.els.push(element);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (targetIndent <= element.indent) {
|
|
912
|
+
element = makeElement(targetIndent);
|
|
913
|
+
|
|
914
|
+
while (
|
|
915
|
+
fragment.els.length !== 0 && (
|
|
916
|
+
targetIndent == null ||
|
|
917
|
+
fragment.els[fragment.els.length - 1].indent !== targetIndent - 1
|
|
918
|
+
)
|
|
919
|
+
) {
|
|
920
|
+
const element = fragment.els.pop() as WorkingElement;
|
|
921
|
+
|
|
922
|
+
if (Array.isArray(element.chain)) {
|
|
923
|
+
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
924
|
+
fragment.html += `</${element.chain[i].tag}>`;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
fragment.html += `</${element?.tag}>`;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (targetIndent === 0) {
|
|
932
|
+
if (fragment.template) {
|
|
933
|
+
output.templates[fragment.id] = fragment.html;
|
|
934
|
+
} else if (fragment.type !== 'root') {
|
|
935
|
+
parsed.set(fragment.id, fragment);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
fragment = makeFragment();
|
|
939
|
+
}
|
|
940
|
+
} else {
|
|
941
|
+
element = makeElement(targetIndent)
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return [element, fragment];
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function flatten(
|
|
948
|
+
fragment: WorkingFragment,
|
|
949
|
+
claimed: Set<string>,
|
|
950
|
+
parsed: Map<string, WorkingFragment>,
|
|
951
|
+
locals: Set<string> = new Set(),
|
|
952
|
+
): WorkingFragment {
|
|
953
|
+
let r: FragmentRef;
|
|
954
|
+
if (Array.isArray(fragment.refs)) {
|
|
955
|
+
for (let i = fragment.refs.length - 1; i > -1; i--) {
|
|
956
|
+
r = fragment.refs[i];
|
|
957
|
+
|
|
958
|
+
if (locals.has(r.id) || claimed.has(r.id) || !parsed.has(r.id)) {
|
|
959
|
+
// cannot use fragment here. Clear the reference marker
|
|
960
|
+
fragment.html = fragment.html.slice(0, r.start)
|
|
961
|
+
+ fragment.html.slice(r.end)
|
|
962
|
+
} else {
|
|
963
|
+
locals.add(fragment.id);
|
|
964
|
+
|
|
965
|
+
const child = flatten(
|
|
966
|
+
parsed.get(r.id) as WorkingFragment,
|
|
967
|
+
claimed,
|
|
968
|
+
parsed,
|
|
969
|
+
locals,
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
fragment.html = fragment.html.slice(0, r.start)
|
|
973
|
+
+ child.html
|
|
974
|
+
+ fragment.html.slice(r.end);
|
|
975
|
+
|
|
976
|
+
if (child.type === 'embed') {
|
|
977
|
+
claimed.add(child.id)
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// don't re-process the fragment
|
|
984
|
+
fragment.refs = undefined as unknown as FragmentRef[];
|
|
985
|
+
|
|
986
|
+
return fragment;
|
|
987
|
+
}
|