@longform/longform 0.0.18 → 0.0.19
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 +417 -366
- package/dist/longform.cjs.map +1 -1
- package/dist/longform.d.ts +2 -2
- package/dist/longform.js +417 -366
- 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 +517 -446
- 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,221 @@ 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
|
-
const root = fragment.type === 'range'
|
|
252
|
-
? targetIndent < 2
|
|
253
|
-
: fragment.html === ''
|
|
254
|
-
;
|
|
232
|
+
const indent = m1[INDENT].length / 2;
|
|
255
233
|
|
|
256
|
-
|
|
234
|
+
// only one root fragment is allowed. Skip until beginning of next fragment / directive.
|
|
235
|
+
if (skipping && indent !== 0) continue;
|
|
236
|
+
if (skipping) skipping = false;
|
|
257
237
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
fragment.html += ' ' + attr[0]
|
|
238
|
+
|
|
239
|
+
// verbatim blocks collect the string as is and do processing on it
|
|
240
|
+
// once the full block has been collected into one string
|
|
241
|
+
if (verbatimType !== 0) {
|
|
242
|
+
if (indent >= verbatimIndent && (
|
|
243
|
+
verbatimType !== 1 || // text verbatim type should exit when other symbols are parsed
|
|
244
|
+
(m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) === undefined
|
|
245
|
+
)) {
|
|
246
|
+
// still in verbatim block
|
|
247
|
+
if (verbatimType === 1) {
|
|
248
|
+
verbatimText += ' ' + m1[0].trim();
|
|
270
249
|
} 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}"`;
|
|
250
|
+
if (verbatimText !== '') verbatimText += '\n';
|
|
251
|
+
|
|
252
|
+
verbatimText += m1[0].replace(m1[INDENT], '');
|
|
282
253
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
fragment.html += '>';
|
|
290
|
-
|
|
291
|
-
if (Array.isArray(element.chain)) {
|
|
292
|
-
let chained: WorkingElement;
|
|
254
|
+
continue;
|
|
255
|
+
} else if (m1[0].trim() === '' &&
|
|
256
|
+
input.length !== m1.index + m1[0].length) {
|
|
257
|
+
// blank line in verbatim
|
|
258
|
+
if (verbatimType !== 1) verbatimText += '\n';
|
|
293
259
|
|
|
294
|
-
|
|
295
|
-
|
|
260
|
+
continue;
|
|
261
|
+
} else {
|
|
262
|
+
// verbatim block is finished
|
|
263
|
+
switch (verbatimType) {
|
|
264
|
+
case 1:
|
|
265
|
+
// text
|
|
266
|
+
fragment.html += verbatimText;
|
|
267
|
+
|
|
268
|
+
// locate reference points in text
|
|
269
|
+
while ((m2 = refRe.exec(verbatimText))) {
|
|
270
|
+
const start = fragment.html.length + m2.index - verbatimText.length;
|
|
271
|
+
|
|
272
|
+
fragment.refs.push({
|
|
273
|
+
id: m2[1],
|
|
274
|
+
start,
|
|
275
|
+
end: start + m2[0].length,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
296
278
|
|
|
297
|
-
|
|
279
|
+
applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
280
|
+
break
|
|
281
|
+
case 2:
|
|
282
|
+
// escaped preformatted text
|
|
283
|
+
fragment.html += escape(verbatimText);
|
|
298
284
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
285
|
+
break;
|
|
286
|
+
case 3:
|
|
287
|
+
// preformatted
|
|
288
|
+
fragment.html += verbatimText + '\n';
|
|
302
289
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
290
|
+
break;
|
|
291
|
+
case 4:
|
|
292
|
+
// directive block args
|
|
293
|
+
if (directive.def == null) break;
|
|
294
|
+
if (doc == null) {
|
|
295
|
+
if (typeof directive.def.meta === 'function') {
|
|
296
|
+
meta[m1[DIRECTIVE]] = directive.def.meta({
|
|
297
|
+
inlineArgs: directive.inlineArgs,
|
|
298
|
+
blockArgs: verbatimText,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
} else if (typeof directive.def.render === 'function') {
|
|
302
|
+
try {
|
|
303
|
+
element.html += directive.def.render({
|
|
304
|
+
doc,
|
|
305
|
+
inlineArgs: directive.inlineArgs,
|
|
306
|
+
blockArgs: verbatimText,
|
|
307
|
+
});
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.error(`Error in calling directive ${directive.name}`)
|
|
310
|
+
console.error(err)
|
|
311
|
+
}
|
|
312
|
+
} else if (typeof directive.def.asyncRender === 'function') {
|
|
313
|
+
asyncCount++;
|
|
314
|
+
|
|
315
|
+
// async rendering uses the #[ref] feature to insert the
|
|
316
|
+
// eventual response.
|
|
317
|
+
const directiveFragment = makeFragment('embed');
|
|
318
|
+
|
|
319
|
+
directiveFragment.id = `@${asyncCount}`;
|
|
320
|
+
parsed.set(directiveFragment.id, directiveFragment);
|
|
321
|
+
|
|
322
|
+
fragment.refs.push({
|
|
323
|
+
id: directiveFragment.id,
|
|
324
|
+
start: fragment.html.length,
|
|
325
|
+
end: fragment.html.length,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
promises.push(
|
|
329
|
+
directive.def.asyncRender({
|
|
330
|
+
doc,
|
|
331
|
+
inlineArgs: directive.inlineArgs,
|
|
332
|
+
blockArgs: verbatimText,
|
|
333
|
+
}).then(res => {
|
|
334
|
+
directiveFragment.html = res ?? '';
|
|
335
|
+
}).catch(err => {
|
|
336
|
+
console.error(`Error in calling directive ${directive.name}`)
|
|
337
|
+
console.error(err)
|
|
338
|
+
})
|
|
339
|
+
);
|
|
340
|
+
} else if (typeof directive.def.element === 'function') {
|
|
341
|
+
if (element.beforeRender == null) element.beforeRender = [];
|
|
306
342
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
343
|
+
element.beforeRender.push({
|
|
344
|
+
blockArgs: verbatimText,
|
|
345
|
+
inlineArgs: directive.inlineArgs,
|
|
346
|
+
element: directive.def.element,
|
|
347
|
+
});
|
|
310
348
|
} else {
|
|
311
|
-
|
|
349
|
+
console.warn(`Directive used in incorrect context ${directive.name}`);
|
|
312
350
|
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
fragment.html += '>';
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (!voids.has(element.tag as string) && element.text != null) {
|
|
320
|
-
fragment.html += element.text;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (
|
|
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
351
|
}
|
|
396
|
-
} else {
|
|
397
|
-
const line = m1[0].replace(' '.repeat(verbatimIndent + 1), '');
|
|
398
352
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
353
|
+
verbatimType = 0;
|
|
354
|
+
verbatimText = '';
|
|
402
355
|
|
|
403
|
-
if (
|
|
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;
|
|
356
|
+
if (preformatClose.has(m1[0].trim())) continue;
|
|
416
357
|
}
|
|
417
|
-
}
|
|
358
|
+
} // end verbatim
|
|
418
359
|
|
|
419
360
|
if (m1[LINE].trim() === '') {
|
|
361
|
+
// empty lines have no effect from here on
|
|
420
362
|
continue;
|
|
421
363
|
}
|
|
422
364
|
|
|
423
|
-
// The id and lang directives should proceed all other directives and
|
|
424
|
-
// fragment declarations.
|
|
425
365
|
if (doc === undefined) {
|
|
366
|
+
// The meta directives get special treatment.
|
|
367
|
+
let parseBlock = false;
|
|
426
368
|
const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? '';
|
|
427
|
-
|
|
369
|
+
|
|
428
370
|
switch (m1[DIRECTIVE]) {
|
|
429
|
-
case 'id':
|
|
371
|
+
case 'id':
|
|
430
372
|
const url = inlineArgs.trim();
|
|
373
|
+
|
|
374
|
+
parseBlock = true;
|
|
375
|
+
|
|
431
376
|
try {
|
|
432
377
|
id = id ?? new URL(url, args?.base).toString();
|
|
433
378
|
} catch (err) {
|
|
434
379
|
console.warn(
|
|
435
|
-
`Invalid URL given to @id directive: ${url} base=${args
|
|
380
|
+
`Invalid URL given to @id directive: ${url} base=${args?.base}`
|
|
436
381
|
);
|
|
437
382
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
383
|
+
break;
|
|
384
|
+
case 'lang':
|
|
385
|
+
parseBlock = true;
|
|
441
386
|
lang = lang ?? inlineArgs.trim();
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
387
|
+
break;
|
|
388
|
+
case 'dir':
|
|
389
|
+
parseBlock = true;
|
|
445
390
|
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
|
-
}
|
|
391
|
+
}
|
|
458
392
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
393
|
+
const def = directives[m1[DIRECTIVE]];
|
|
394
|
+
|
|
395
|
+
if (typeof def?.meta === 'function' || parseBlock) {
|
|
396
|
+
verbatimIndent = indent + 1;
|
|
397
|
+
verbatimType = 4;
|
|
398
|
+
directive = {
|
|
399
|
+
name: m1[DIRECTIVE],
|
|
400
|
+
inlineArgs,
|
|
401
|
+
def,
|
|
462
402
|
}
|
|
463
|
-
}
|
|
464
403
|
|
|
465
|
-
|
|
404
|
+
continue main;
|
|
405
|
+
}
|
|
466
406
|
|
|
467
|
-
if (args?.outputMode === '
|
|
407
|
+
if (args?.outputMode === 'head') {
|
|
468
408
|
return {
|
|
469
409
|
key,
|
|
470
410
|
id,
|
|
471
411
|
lang,
|
|
472
412
|
dir,
|
|
473
|
-
meta
|
|
474
|
-
|
|
413
|
+
meta,
|
|
414
|
+
data,
|
|
415
|
+
} as unknown as ParsedResult;
|
|
475
416
|
}
|
|
417
|
+
|
|
418
|
+
doc = new Doc(id, lang, dir, meta, args?.allowAll, args?.allowedAttributes, args?.allowedElements);
|
|
476
419
|
}
|
|
477
420
|
|
|
478
421
|
switch (m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) {
|
|
479
422
|
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
|
-
}
|
|
423
|
+
case '##':
|
|
424
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
489
425
|
|
|
490
426
|
fragment.id = m1[ID];
|
|
491
427
|
|
|
@@ -505,90 +441,76 @@ export function longform(
|
|
|
505
441
|
}
|
|
506
442
|
|
|
507
443
|
break;
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (element.tag != null || textIndent != null) {
|
|
513
|
-
applyIndent(indent);
|
|
444
|
+
case '@':
|
|
445
|
+
if (element.tag !== undefined) {
|
|
446
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
514
447
|
}
|
|
515
448
|
|
|
449
|
+
const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? ''
|
|
450
|
+
|
|
516
451
|
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;
|
|
452
|
+
case 'template':
|
|
453
|
+
if (indent === 0) {
|
|
454
|
+
let indented = false;
|
|
455
|
+
fragment.template = true;
|
|
456
|
+
|
|
457
|
+
templateLinesRe.lastIndex = sniffTestRe.lastIndex;
|
|
458
|
+
while ((m2 = templateLinesRe.exec(input))) {
|
|
459
|
+
if (m2[1] == null && !indented && fragment.id == null) {
|
|
460
|
+
m3 = idRe.exec(m2[0]);
|
|
461
|
+
|
|
462
|
+
if (m3 != null) fragment.id = m3[3];
|
|
463
|
+
|
|
464
|
+
fragment.html += m2[0];
|
|
465
|
+
} else if (m2[1] == null && indented) {
|
|
466
|
+
sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
|
|
467
|
+
break;
|
|
468
|
+
} else {
|
|
469
|
+
fragment.html += '\n' + m2[0];
|
|
470
|
+
if (m2[1] != null) indented = true;
|
|
471
|
+
}
|
|
546
472
|
}
|
|
473
|
+
|
|
474
|
+
[element, fragment] = applyIndent(0, key, element, fragment, doc, parsed, output, args);
|
|
547
475
|
}
|
|
548
476
|
|
|
549
|
-
|
|
477
|
+
continue main;
|
|
478
|
+
case 'doctype':
|
|
479
|
+
fragment.html += `<!doctype ${(inlineArgs.trim() || 'html').trim()}>`;
|
|
550
480
|
break;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
481
|
+
case 'xml':
|
|
482
|
+
fragment.html += `<?xml ${inlineArgs.trim() || 'version="1.0" encoding="UTF-8"'}?>`;
|
|
483
|
+
break;
|
|
484
|
+
case 'mount':
|
|
485
|
+
if (args?.outputMode !== 'mountable') break;
|
|
486
|
+
|
|
487
|
+
if (inlineArgs === '') {
|
|
488
|
+
console.warn('Mount points must have a name');
|
|
555
489
|
} else if (fragment.type !== 'root') {
|
|
556
|
-
|
|
490
|
+
console.warn('Mounting is only allowed on a root element');
|
|
557
491
|
}
|
|
558
492
|
|
|
559
493
|
fragment.mountable = true;
|
|
560
|
-
element.mount =
|
|
494
|
+
element.mount = inlineArgs.trim();
|
|
561
495
|
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
496
|
}
|
|
585
497
|
|
|
498
|
+
const def = directives[m1[DIRECTIVE]];
|
|
499
|
+
|
|
500
|
+
// A directive may not be defined but we want to process
|
|
501
|
+
// any block args to keep the output valid. Builtin directives
|
|
502
|
+
// will be ignored unless they require block args.
|
|
503
|
+
verbatimIndent = indent + 1;
|
|
504
|
+
verbatimType = 4;
|
|
505
|
+
directive = {
|
|
506
|
+
name: m1[DIRECTIVE],
|
|
507
|
+
inlineArgs: m1[DIRECTIVE_INLINE_ARGS],
|
|
508
|
+
def,
|
|
509
|
+
};
|
|
510
|
+
|
|
586
511
|
break;
|
|
587
|
-
|
|
588
|
-
case '[':
|
|
589
|
-
case '::': {
|
|
512
|
+
case '::':
|
|
590
513
|
if (m1[ELEMENT] !== undefined) {
|
|
591
|
-
const indent = (m1[INDENT]?.length ?? 0) / 2;
|
|
592
514
|
let preformattedType: string | undefined;
|
|
593
515
|
let inlineText: string | undefined;
|
|
594
516
|
|
|
@@ -596,12 +518,10 @@ export function longform(
|
|
|
596
518
|
element.tag !== undefined ||
|
|
597
519
|
element.indent > indent
|
|
598
520
|
) {
|
|
599
|
-
applyIndent(indent);
|
|
521
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
600
522
|
}
|
|
601
523
|
|
|
602
524
|
element.indent = indent;
|
|
603
|
-
|
|
604
|
-
textIndent = null;
|
|
605
525
|
|
|
606
526
|
if (indent === 0 && fragment.id == null) {
|
|
607
527
|
if (root != null) {
|
|
@@ -615,12 +535,11 @@ export function longform(
|
|
|
615
535
|
}
|
|
616
536
|
|
|
617
537
|
const parent = element;
|
|
618
|
-
|
|
619
|
-
|
|
538
|
+
outerRe.lastIndex = 0;
|
|
620
539
|
|
|
621
540
|
// Looping through chained element declarations
|
|
622
541
|
// foo#x.y::bar::free::
|
|
623
|
-
while ((m2 =
|
|
542
|
+
while ((m2 = outerRe.exec(m1[LINE]))) {
|
|
624
543
|
let working: WorkingElement;
|
|
625
544
|
|
|
626
545
|
preformattedType = m2[3];
|
|
@@ -636,9 +555,9 @@ export function longform(
|
|
|
636
555
|
parent.chain.push(working);
|
|
637
556
|
}
|
|
638
557
|
|
|
639
|
-
|
|
558
|
+
innerRe.lastIndex = 0;
|
|
640
559
|
// Looping through ids, classes and attrs
|
|
641
|
-
while((m3 =
|
|
560
|
+
while((m3 = innerRe.exec(m2[2]))) {
|
|
642
561
|
if (m3[2] !== undefined) {
|
|
643
562
|
working.id = m3[2];
|
|
644
563
|
} else if (m3[1] !== undefined) {
|
|
@@ -649,12 +568,12 @@ export function longform(
|
|
|
649
568
|
}
|
|
650
569
|
} else {
|
|
651
570
|
// TODO: Preserve quoting style around attribute values
|
|
652
|
-
let value = m3[4] ?? m3[5] ?? m3[6];
|
|
571
|
+
let value: string | boolean | undefined | null = m3[4] ?? m3[5] ?? m3[6];
|
|
653
572
|
|
|
654
573
|
// attribute directives
|
|
655
574
|
if (value[0] === '@') {
|
|
656
|
-
|
|
657
|
-
m4 =
|
|
575
|
+
attributeDirectiveRe.lastIndex = 0;
|
|
576
|
+
m4 = attributeDirectiveRe.exec(value);
|
|
658
577
|
|
|
659
578
|
if (m4 != null) {
|
|
660
579
|
const def = directives[m4[1]];
|
|
@@ -671,19 +590,19 @@ export function longform(
|
|
|
671
590
|
|
|
672
591
|
switch (m3[3]) {
|
|
673
592
|
case 'id':
|
|
674
|
-
if (!working.id) {
|
|
593
|
+
if (!working.id && typeof value === 'string') {
|
|
675
594
|
working.id = value;
|
|
676
595
|
}
|
|
677
596
|
break;
|
|
678
597
|
case 'class':
|
|
679
|
-
if (!working.class) {
|
|
598
|
+
if (!working.class && typeof value === 'string') {
|
|
680
599
|
working.class = value;
|
|
681
|
-
} else {
|
|
600
|
+
} else if (typeof value === 'string') {
|
|
682
601
|
working.class += ' ' + value;
|
|
683
602
|
}
|
|
684
603
|
break;
|
|
685
604
|
default:
|
|
686
|
-
working.attrs[m3[3]] = value;
|
|
605
|
+
if (value !== false) working.attrs[m3[3]] = value;
|
|
687
606
|
}
|
|
688
607
|
}
|
|
689
608
|
}
|
|
@@ -694,31 +613,38 @@ export function longform(
|
|
|
694
613
|
// server specific process.
|
|
695
614
|
if (element.mount != null) {
|
|
696
615
|
const id = element.mount;
|
|
697
|
-
|
|
616
|
+
|
|
617
|
+
[element, fragment] = applyIndent(indent + 1, key, element, fragment, doc, parsed, output, args);
|
|
618
|
+
|
|
619
|
+
if (fragment.mountPoints == null) fragment.mountPoints = [];
|
|
620
|
+
|
|
698
621
|
fragment.mountPoints.push({
|
|
699
622
|
id,
|
|
700
623
|
part: fragment.html,
|
|
701
624
|
});
|
|
625
|
+
|
|
702
626
|
fragment.html = '';
|
|
703
|
-
applyIndent(indent);
|
|
627
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
704
628
|
break;
|
|
705
629
|
}
|
|
706
630
|
|
|
707
631
|
if (preformattedType !== undefined) {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
632
|
+
if (element.tag !== undefined)
|
|
633
|
+
[element, fragment] = applyIndent(indent + 1, key, element, fragment, doc, parsed, output, args);
|
|
634
|
+
|
|
635
|
+
verbatimIndent = indent + 1;
|
|
636
|
+
verbatimType = preformattedType === '{{' ? 3 : 2
|
|
711
637
|
} else if (inlineText !== undefined) {
|
|
712
638
|
element.text = inlineText;
|
|
713
639
|
}
|
|
714
640
|
|
|
715
641
|
break;
|
|
716
642
|
}
|
|
717
|
-
|
|
718
|
-
|
|
643
|
+
case '[':
|
|
644
|
+
attributeRe.lastIndex = 0;
|
|
719
645
|
m2 = m1[ATTR] !== undefined
|
|
720
|
-
?
|
|
721
|
-
:
|
|
646
|
+
? attributeRe.exec(m1[LINE])
|
|
647
|
+
: null;
|
|
722
648
|
|
|
723
649
|
if (m2 != null && element.tag != null) {
|
|
724
650
|
if (m2[2] === 'id') {
|
|
@@ -739,84 +665,30 @@ export function longform(
|
|
|
739
665
|
|
|
740
666
|
break;
|
|
741
667
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
668
|
+
default:
|
|
669
|
+
if (element.tag !== undefined)
|
|
670
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
745
671
|
|
|
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
|
-
}
|
|
672
|
+
verbatimText = m1[0].trim();
|
|
673
|
+
verbatimIndent = indent;
|
|
674
|
+
verbatimType = 1;
|
|
776
675
|
}
|
|
777
676
|
}
|
|
778
677
|
|
|
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
|
-
}
|
|
678
|
+
applyIndent(0, key, element, fragment, doc, parsed, output, args);
|
|
806
679
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
return fragment;
|
|
810
|
-
}
|
|
680
|
+
if (promises.length > 0) await Promise.all(promises);
|
|
811
681
|
|
|
812
682
|
if (root?.mountable) {
|
|
813
683
|
output.mountable = true;
|
|
814
684
|
output.tail = root.html;
|
|
815
|
-
output.mountPoints = root.mountPoints;
|
|
685
|
+
output.mountPoints = root.mountPoints ?? [];
|
|
816
686
|
|
|
817
687
|
return output;
|
|
818
688
|
}
|
|
819
689
|
|
|
690
|
+
const arr = Array.from(parsed.values());
|
|
691
|
+
|
|
820
692
|
for (let i = 0; i < parsed.size + 1; i++) {
|
|
821
693
|
let fragment: WorkingFragment;
|
|
822
694
|
|
|
@@ -832,38 +704,36 @@ export function longform(
|
|
|
832
704
|
continue;
|
|
833
705
|
}
|
|
834
706
|
|
|
835
|
-
flatten(fragment)
|
|
707
|
+
flatten(fragment, claimed, parsed);
|
|
836
708
|
}
|
|
837
|
-
|
|
709
|
+
|
|
838
710
|
if (root?.html != null) {
|
|
839
711
|
output.root = root.html;
|
|
840
712
|
output.selector = `[data-${key}-root]`;
|
|
841
713
|
}
|
|
842
714
|
|
|
843
|
-
for (let i = 0
|
|
715
|
+
for (let i = 0, l = arr.length, f = arr[i]; i < l; i++) {
|
|
844
716
|
let selector!: string;
|
|
845
|
-
const fragment: WorkingFragment = arr[i];
|
|
846
717
|
|
|
847
|
-
if (
|
|
718
|
+
if (f == null || claimed.has(f.id as string)) {
|
|
848
719
|
continue;
|
|
849
720
|
}
|
|
850
721
|
|
|
851
|
-
switch (
|
|
852
|
-
case 'embed':
|
|
722
|
+
switch (f.type) {
|
|
723
|
+
case 'embed':
|
|
853
724
|
if (args?.predictable) {
|
|
854
|
-
selector = `[id=${
|
|
725
|
+
selector = `[id=${f.id}]`;
|
|
855
726
|
break;
|
|
856
727
|
}
|
|
857
|
-
}
|
|
858
728
|
case 'bare':
|
|
859
|
-
case 'range': selector = `[data-${key}=${
|
|
729
|
+
case 'range': selector = `[data-${key}=${f.id}]`;
|
|
860
730
|
}
|
|
861
731
|
|
|
862
|
-
output.fragments[
|
|
863
|
-
id:
|
|
732
|
+
output.fragments[f.id as string] = {
|
|
733
|
+
id: f.id as string,
|
|
864
734
|
selector,
|
|
865
|
-
type:
|
|
866
|
-
html:
|
|
735
|
+
type: f.type as 'embed' | 'bare' | 'range',
|
|
736
|
+
html: f.html,
|
|
867
737
|
};
|
|
868
738
|
}
|
|
869
739
|
|
|
@@ -884,13 +754,14 @@ export function longform(
|
|
|
884
754
|
* @param getFragment - A function which returns an already processed fragment's HTML string.
|
|
885
755
|
* @returns The processed template.
|
|
886
756
|
*/
|
|
887
|
-
export function processTemplate(
|
|
757
|
+
export async function processTemplate(
|
|
888
758
|
template: string,
|
|
889
759
|
args: Record<string, string | number>,
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
760
|
+
longformArgs?: LongformArgs,
|
|
761
|
+
getFragment?: (fragment: string) => string | undefined,
|
|
762
|
+
): Promise<string | undefined> {
|
|
763
|
+
const lf = template.replace(templateRe, (_line, _structured, param, ref) => {
|
|
764
|
+
if (ref !== undefined && typeof getFragment === 'function') {
|
|
894
765
|
const fragment = getFragment(ref);
|
|
895
766
|
|
|
896
767
|
if (fragment == null) return '';
|
|
@@ -901,6 +772,206 @@ export function processTemplate(
|
|
|
901
772
|
return args[param] != null ? escape(args[param].toString()) : '';
|
|
902
773
|
});
|
|
903
774
|
|
|
904
|
-
return Object.values(longform(lf).fragments)[0]?.html ?? null;
|
|
775
|
+
return Object.values((await longform(lf, longformArgs)).fragments)[0]?.html ?? null;
|
|
905
776
|
}
|
|
906
777
|
|
|
778
|
+
/**
|
|
779
|
+
* Closes any current in progress element definition
|
|
780
|
+
* and creates a new working element.
|
|
781
|
+
*/
|
|
782
|
+
function applyIndent(
|
|
783
|
+
targetIndent: number,
|
|
784
|
+
key: string,
|
|
785
|
+
element: WorkingElement,
|
|
786
|
+
fragment: WorkingFragment,
|
|
787
|
+
doc: Doc,
|
|
788
|
+
parsed: Map<string, WorkingFragment>,
|
|
789
|
+
output: ParsedResult,
|
|
790
|
+
args?: LongformArgs,
|
|
791
|
+
): [element: WorkingElement, fragment: WorkingFragment] {
|
|
792
|
+
if (element.tag !== undefined) {
|
|
793
|
+
if (Array.isArray(element.beforeRender)) {
|
|
794
|
+
const el: Element = {
|
|
795
|
+
id: element.id,
|
|
796
|
+
tag: element.tag,
|
|
797
|
+
class: element.class,
|
|
798
|
+
attrs: element.attrs,
|
|
799
|
+
};
|
|
800
|
+
const chain: Element[] = [];
|
|
801
|
+
|
|
802
|
+
for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
|
|
803
|
+
chain.push({
|
|
804
|
+
id: el.id,
|
|
805
|
+
tag: el.tag,
|
|
806
|
+
class: el.class,
|
|
807
|
+
attrs: el.attrs,
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
for (let i = 0, l = element.beforeRender.length, def = element.beforeRender[i]; i < l; i++) {
|
|
812
|
+
(def.element as (ctx: ElementCtx) => void)({
|
|
813
|
+
el,
|
|
814
|
+
chain,
|
|
815
|
+
doc,
|
|
816
|
+
inlineArgs: def.inlineArgs,
|
|
817
|
+
blockArgs: def.blockArgs,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const root = fragment.type === 'range'
|
|
823
|
+
? targetIndent < 2
|
|
824
|
+
: fragment.html === ''
|
|
825
|
+
;
|
|
826
|
+
|
|
827
|
+
fragment.html += `<${element.tag}`;
|
|
828
|
+
|
|
829
|
+
if (element.id !== undefined) {
|
|
830
|
+
fragment.html += ' id="' + element.id + '"';
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (element.class !== undefined) {
|
|
834
|
+
fragment.html += ' class="' + element.class + '"';
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
for (const attr of Object.entries(element.attrs)) {
|
|
838
|
+
if (attr[0] === 'id' || attr[0] === 'class') continue;
|
|
839
|
+
if (attr[1] == null) {
|
|
840
|
+
fragment.html += ' ' + attr[0]
|
|
841
|
+
} else {
|
|
842
|
+
fragment.html += ` ${attr[0]}="${attr[1]}"`;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (root) {
|
|
847
|
+
if (fragment.type === 'root') {
|
|
848
|
+
fragment.html += ` data-${key}-root`;
|
|
849
|
+
} else if (fragment.type === 'bare' || fragment.type === 'range') {
|
|
850
|
+
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
851
|
+
} else if (fragment.type === 'embed' && !args?.predictable) {
|
|
852
|
+
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (element.mount !== undefined) {
|
|
857
|
+
fragment.html += ` data-${key}-mount="${element.mount}"`;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
fragment.html += '>';
|
|
861
|
+
|
|
862
|
+
if (Array.isArray(element.chain)) {
|
|
863
|
+
let chained: WorkingElement;
|
|
864
|
+
|
|
865
|
+
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
866
|
+
chained = element.chain[i];
|
|
867
|
+
|
|
868
|
+
fragment.html += '<' + chained.tag;
|
|
869
|
+
|
|
870
|
+
if (chained.id !== undefined) {
|
|
871
|
+
fragment.html += ' id="' + chained.id + '"';
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (chained.class != undefined) {
|
|
875
|
+
fragment.html += ' class="' + chained.class + '"';
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
for (const attr of Object.entries(chained.attrs)) {
|
|
879
|
+
if (attr[1] === undefined) {
|
|
880
|
+
fragment.html += ' ' + attr[0]
|
|
881
|
+
} else {
|
|
882
|
+
fragment.html += ` ${attr[0]}="${attr[1]}"`;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
fragment.html += '>';
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (!voids.has(element.tag as string) && element.text != null) {
|
|
891
|
+
fragment.html += element.text;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (
|
|
895
|
+
!voids.has(element.tag as string)
|
|
896
|
+
) {
|
|
897
|
+
fragment.els.push(element);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (targetIndent <= element.indent) {
|
|
902
|
+
element = makeElement(targetIndent);
|
|
903
|
+
|
|
904
|
+
while (
|
|
905
|
+
fragment.els.length !== 0 && (
|
|
906
|
+
targetIndent == null ||
|
|
907
|
+
fragment.els[fragment.els.length - 1].indent !== targetIndent - 1
|
|
908
|
+
)
|
|
909
|
+
) {
|
|
910
|
+
const element = fragment.els.pop() as WorkingElement;
|
|
911
|
+
|
|
912
|
+
if (Array.isArray(element.chain)) {
|
|
913
|
+
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
914
|
+
fragment.html += `</${element.chain[i].tag}>`;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
fragment.html += `</${element?.tag}>`;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (targetIndent === 0) {
|
|
922
|
+
if (fragment.template) {
|
|
923
|
+
output.templates[fragment.id] = fragment.html;
|
|
924
|
+
} else if (fragment.type !== 'root') {
|
|
925
|
+
parsed.set(fragment.id, fragment);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
fragment = makeFragment();
|
|
929
|
+
}
|
|
930
|
+
} else {
|
|
931
|
+
element = makeElement(targetIndent)
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
return [element, fragment];
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function flatten(
|
|
938
|
+
fragment: WorkingFragment,
|
|
939
|
+
claimed: Set<string>,
|
|
940
|
+
parsed: Map<string, WorkingFragment>,
|
|
941
|
+
locals: Set<string> = new Set(),
|
|
942
|
+
): WorkingFragment {
|
|
943
|
+
let r: FragmentRef;
|
|
944
|
+
if (Array.isArray(fragment.refs)) {
|
|
945
|
+
for (let i = fragment.refs.length - 1; i > -1; i--) {
|
|
946
|
+
r = fragment.refs[i];
|
|
947
|
+
|
|
948
|
+
if (locals.has(r.id) || claimed.has(r.id) || !parsed.has(r.id)) {
|
|
949
|
+
// cannot use fragment here. Clear the reference marker
|
|
950
|
+
fragment.html = fragment.html.slice(0, r.start)
|
|
951
|
+
+ fragment.html.slice(r.end)
|
|
952
|
+
} else {
|
|
953
|
+
locals.add(fragment.id);
|
|
954
|
+
|
|
955
|
+
const child = flatten(
|
|
956
|
+
parsed.get(r.id) as WorkingFragment,
|
|
957
|
+
claimed,
|
|
958
|
+
parsed,
|
|
959
|
+
locals,
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
fragment.html = fragment.html.slice(0, r.start)
|
|
963
|
+
+ child.html
|
|
964
|
+
+ fragment.html.slice(r.end);
|
|
965
|
+
|
|
966
|
+
if (child.type === 'embed') {
|
|
967
|
+
claimed.add(child.id)
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// don't re-process the fragment
|
|
974
|
+
fragment.refs = undefined as unknown as FragmentRef[];
|
|
975
|
+
|
|
976
|
+
return fragment;
|
|
977
|
+
}
|