@longform/longform 0.0.17 → 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.
@@ -1,737 +1,2 @@
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
3
- // captures a single element definition which could be in a chain.
4
- // id, class and attributes are matched as a single block for a later loop
5
- // if in chained situation the regexp will need to be looped over for each element
6
- // we know the element is the last if the final capture group has a positive match
7
- // or if the line is consumed by the regexp.
8
- , outer = /([a-z][\w\-]*(?::[a-z][\w\-]*)?)((?:(?:[^:])|(?::(?!:)))*)::(?: (?:({{?)|(.*)))?/gi
9
- // captures each id, class and attribute declaration in an element
10
- , inner = /(?:\.([a-z][\w\-]+)|#([a-z][\w\-]+)|\[([a-z][a-z\-]+(?::[a-z][a-z|\-]*)?)(?:=(?:"([^"]+)"|'([^']+)'|([^\]]+)))?\])/gi, attribute1 = /((?:\ \ )+)\[(\w[\w-]*(?::\w[\w-]*)?)(?:=([^\n]+))?\]/, directiveAttr = /^@([a-z][\w\-]*(?::[a-z][\w\-]*)?)$/i, preformattedClose = /[ \t]*}}?[ \t]*/, id1 = /((?:\ \ )+)?#(#)?([\w\-]+)(?: ([\["]))?/gmi, identRe = /^(\ \ )+/, text1 = /^((?:\ \ )+)([^ \n][^\n]*)$/i, refRe = /#\[([\w\-]+)\]/g, escapeRe = /([&<>"'#\[\]{}])/g, templateLinesRe = /^(\ \ )?([^\n]+)$/gmi, templateRe = /\#\{(@)?([a-z][\w\-]*(?::[a-z][\w\-]*))}/g, voids = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wrb']);
11
- let m1, m2, m3, m4;
12
- const entities = {
13
- '&': '&amp;',
14
- '<': '&lt;',
15
- '>': '&gt;',
16
- '"': '&quot;',
17
- "'": '&apos;',
18
- // '#': '&num;',
19
- // '[': '&lbrak;',
20
- // ']': '&rbrak;',
21
- // '{': '&rbrace;',
22
- // '}': '&lbrace;',
23
- };
24
- function escape(value) {
25
- return value.replace(escapeRe, (match) => {
26
- return entities[match] ?? match;
27
- });
28
- }
29
- function makeElement(indent = 0) {
30
- return {
31
- indent,
32
- key: undefined,
33
- id: undefined,
34
- tag: undefined,
35
- class: undefined,
36
- text: undefined,
37
- attrs: {},
38
- html: '',
39
- mount: undefined,
40
- serializerConfig: undefined,
41
- chain: undefined,
42
- beforeRender: undefined,
43
- };
44
- }
45
- function makeFragment(type = 'bare') {
46
- return {
47
- type,
48
- html: '',
49
- template: false,
50
- mountable: false,
51
- els: [],
52
- refs: [],
53
- mountPoints: [],
54
- };
55
- }
56
- class Doc {
57
- id;
58
- lang;
59
- dir;
60
- meta;
61
- #serializerConfig;
62
- constructor(id, lang, dir, meta, allowAll, allowedAttributes, allowedElements) {
63
- this.id = id;
64
- this.lang = lang;
65
- this.dir = dir;
66
- this.meta = Object.freeze(meta);
67
- this.#serializerConfig = {
68
- allowAll: allowAll ?? false,
69
- allowedAttributes: allowedAttributes ?? [],
70
- allowedElements: {},
71
- };
72
- if (allowedElements != null) {
73
- for (let i = 0, l = allowedElements.length, el = allowedElements[i]; i < l; i++) {
74
- if (typeof el === 'string') {
75
- this.#serializerConfig.allowedElements[el] = {
76
- tag: el,
77
- attrs: [],
78
- };
79
- }
80
- else {
81
- this.#serializerConfig.allowedElements[el.tag] = el;
82
- }
83
- }
84
- }
85
- Object.freeze(this);
86
- }
87
- allowAll() {
88
- }
89
- allowAttributes() {
90
- }
91
- allowElements() {
92
- }
93
- }
94
- const directiveValidator = /^[a-z][a-z\-]*\:[a-z][a-z\-]*$/i;
95
- /**
96
- * Parses a longform document into a object containing the root and fragments
97
- * in the output format.
98
- *
99
- * @param input - The Longform document to parse.
100
- * @param args - Arguments for the Longform parser.
101
- */
102
- function longform(input, args) {
103
- let textIndent = null, verbatimSerialize = true, verbatimIndent = null, verbatimFirst = false, element = makeElement(), fragment = makeFragment()
104
- // the root fragment
105
- , root = null, id = args?.id, lang = args?.lang, dir = args?.dir, meta = {}, doc;
106
- // ids of claimed fragments
107
- const claimed = new Set()
108
- // parsed fragments
109
- , parsed = new Map(Object.entries(args?.fragments ?? {})), output = Object.create(null), directives = {
110
- id: { attr: ctx => ctx.doc.id },
111
- dir: { attr: ctx => ctx.doc.dir },
112
- lang: { attr: ctx => ctx.doc.lang },
113
- };
114
- let key = args?.key;
115
- if (!args?.predictable && key == null) {
116
- const arr = new Uint8Array(10);
117
- crypto.getRandomValues(arr);
118
- key = arr.toHex().toLowerCase();
119
- }
120
- else if (key == null) {
121
- key = 'lf';
122
- }
123
- output.fragments = {};
124
- output.templates = {};
125
- if (args?.directives != null) {
126
- const entries = Object.entries(args.directives);
127
- for (let i = 0, l = entries.length; i < l; i++) {
128
- if (!directiveValidator.test(entries[i][0])) {
129
- console.warn(`Invalid custom directive name '${entries[i][0]}'`);
130
- continue;
131
- }
132
- directives[entries[i][0]] = entries[i][1];
133
- }
134
- }
135
- /**
136
- * Closes any current in progress element definition
137
- * and creates a new working element.
138
- */
139
- function applyIndent(targetIndent) {
140
- if (Array.isArray(element.beforeRender)) {
141
- const el = {
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
- }
165
- }
166
- if (element.tag != null) {
167
- const root = fragment.type === 'range'
168
- ? targetIndent < 2
169
- : fragment.html === '';
170
- fragment.html += `<${element.tag}`;
171
- if (element.id !== undefined) {
172
- fragment.html += ' id="' + element.id + '"';
173
- }
174
- if (element.class !== undefined) {
175
- fragment.html += ' class="' + element.class + '"';
176
- }
177
- for (const attr of Object.entries(element.attrs)) {
178
- if (attr[0] === 'id' || attr[0] === 'class')
179
- continue;
180
- if (attr[1] == null) {
181
- fragment.html += ' ' + attr[0];
182
- }
183
- else {
184
- fragment.html += ` ${attr[0]}="${attr[1]}"`;
185
- }
186
- }
187
- if (root) {
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}"`;
196
- }
197
- }
198
- if (element.mount !== undefined) {
199
- fragment.html += ` data-${key}-mount="${element.mount}"`;
200
- }
201
- fragment.html += '>';
202
- if (Array.isArray(element.chain)) {
203
- let chained;
204
- for (let i = 0, l = element.chain.length; i < l; i++) {
205
- chained = element.chain[i];
206
- fragment.html += '<' + chained.tag;
207
- if (chained.id !== undefined) {
208
- fragment.html += ' id="' + chained.id + '"';
209
- }
210
- if (chained.class != undefined) {
211
- fragment.html += ' class="' + chained.class + '"';
212
- }
213
- for (const attr of Object.entries(chained.attrs)) {
214
- if (attr[1] === undefined) {
215
- fragment.html += ' ' + attr[0];
216
- }
217
- else {
218
- fragment.html += ` ${attr[0]}="${attr[1]}"`;
219
- }
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
- }
241
- fragment.html += `</${element?.tag}>`;
242
- }
243
- if (targetIndent === 0) {
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])) {
285
- 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
- }
307
- }
308
- if (m1[LINE].trim() === '') {
309
- continue;
310
- }
311
- // The id and lang directives should proceed all other directives and
312
- // fragment declarations.
313
- if (doc === undefined) {
314
- const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? '';
315
- switch (m1[DIRECTIVE]) {
316
- case 'id': {
317
- const url = inlineArgs.trim();
318
- try {
319
- id = id ?? new URL(url, args?.base).toString();
320
- }
321
- catch (err) {
322
- console.warn(`Invalid URL given to @id directive: ${url} base=${args.base}`);
323
- }
324
- continue main;
325
- }
326
- case 'lang': {
327
- lang = lang ?? inlineArgs.trim();
328
- continue main;
329
- }
330
- case 'dir': {
331
- 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
- }
346
- doc = new Doc(id, lang, dir, meta, args?.allowAll, args?.allowedAttributes, args?.allowedElements);
347
- if (args?.outputMode === 'meta') {
348
- return {
349
- key,
350
- id,
351
- lang,
352
- dir,
353
- meta: doc.meta,
354
- };
355
- }
356
- }
357
- switch (m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) {
358
- case '#':
359
- case '##': {
360
- id1.lastIndex = 0;
361
- const indent = (m1[INDENT].length ?? 0) / 2;
362
- if (element.tag != null || textIndent != null) {
363
- applyIndent(indent);
364
- textIndent = null;
365
- }
366
- fragment.id = m1[ID];
367
- if (indent === 0) {
368
- if (m1[FRAGMENT_TYPE] == '[') {
369
- fragment.type = 'range';
370
- }
371
- else if (m1[FRAGMENT_TYPE] === '"') {
372
- fragment.type = 'text';
373
- }
374
- else if (m1[ID_TYPE] === '##') {
375
- fragment.type = 'bare';
376
- }
377
- else {
378
- fragment.type = 'embed';
379
- element.id = fragment.id;
380
- }
381
- }
382
- else {
383
- element.id = fragment.id;
384
- }
385
- break;
386
- }
387
- case '@': {
388
- const indent = m1[INDENT].length / 2;
389
- if (element.tag != null || textIndent != null) {
390
- applyIndent(indent);
391
- }
392
- switch (m1[DIRECTIVE]) {
393
- case 'doctype': {
394
- const args = m1[DIRECTIVE_INLINE_ARGS] ?? 'html';
395
- fragment.html += `<!doctype ${args.trim()}>`;
396
- break;
397
- }
398
- case 'xml': {
399
- const args = m1[DIRECTIVE_INLINE_ARGS] ?? 'version="1.0" encoding="UTF-8"';
400
- fragment.html += `<?xml ${args.trim()}?>`;
401
- break;
402
- }
403
- case 'template': {
404
- let indented = false;
405
- fragment.template = indent === 0;
406
- templateLinesRe.lastIndex = sniffTestRe.lastIndex;
407
- while ((m2 = templateLinesRe.exec(input))) {
408
- if (m2[1] == null && !indented && fragment.id == null) {
409
- id1.lastIndex = 0;
410
- m3 = id1.exec(m2[0]);
411
- if (m3 != null)
412
- fragment.id = m3[3];
413
- fragment.html += m2[0];
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;
423
- }
424
- }
425
- applyIndent(0);
426
- break;
427
- }
428
- case 'mount': {
429
- if (m1[DIRECTIVE_INLINE_ARGS] == null) {
430
- throw new Error('Mount points must have a name');
431
- }
432
- else if (fragment.type !== 'root') {
433
- throw new Error('Mounting is only allowed on a root element');
434
- }
435
- fragment.mountable = true;
436
- element.mount = m1[DIRECTIVE_INLINE_ARGS].trim();
437
- 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
- }
458
- break;
459
- }
460
- case '[':
461
- case '::': {
462
- if (m1[ELEMENT] !== undefined) {
463
- const indent = (m1[INDENT]?.length ?? 0) / 2;
464
- let preformattedType;
465
- let inlineText;
466
- if (element.tag !== undefined ||
467
- element.indent > indent) {
468
- applyIndent(indent);
469
- }
470
- element.indent = indent;
471
- textIndent = null;
472
- if (indent === 0 && fragment.id == null) {
473
- if (root != null) ;
474
- else {
475
- fragment.type = 'root';
476
- root = fragment;
477
- }
478
- }
479
- const parent = element;
480
- outer.lastIndex = 0;
481
- // Looping through chained element declarations
482
- // foo#x.y::bar::free::
483
- while ((m2 = outer.exec(m1[LINE]))) {
484
- let working;
485
- preformattedType = m2[3];
486
- inlineText = m2[4];
487
- if (element.tag === undefined) {
488
- element.tag = m2[1];
489
- working = element;
490
- }
491
- else {
492
- if (parent.chain === undefined)
493
- parent.chain = [];
494
- working = makeElement(indent);
495
- working.tag = m2[1];
496
- parent.chain.push(working);
497
- }
498
- inner.lastIndex = 0;
499
- // Looping through ids, classes and attrs
500
- while ((m3 = inner.exec(m2[2]))) {
501
- if (m3[2] !== undefined) {
502
- working.id = m3[2];
503
- }
504
- else if (m3[1] !== undefined) {
505
- if (working.class == null) {
506
- working.class = m3[1];
507
- }
508
- else {
509
- working.class += ' ' + m3[1];
510
- }
511
- }
512
- else {
513
- // TODO: Preserve quoting style around attribute values
514
- let value = m3[4] ?? m3[5] ?? m3[6];
515
- // attribute directives
516
- if (value[0] === '@') {
517
- directiveAttr.lastIndex = 0;
518
- m4 = directiveAttr.exec(value);
519
- if (m4 != null) {
520
- const def = directives[m4[1]];
521
- if (def != null && typeof def.attr === 'function') {
522
- value = def.attr({ doc, tag: element.tag });
523
- if (value == null)
524
- break;
525
- }
526
- else {
527
- break;
528
- }
529
- }
530
- }
531
- switch (m3[3]) {
532
- case 'id':
533
- if (!working.id) {
534
- working.id = value;
535
- }
536
- break;
537
- case 'class':
538
- if (!working.class) {
539
- working.class = value;
540
- }
541
- else {
542
- working.class += ' ' + value;
543
- }
544
- break;
545
- default:
546
- working.attrs[m3[3]] = value;
547
- }
548
- }
549
- }
550
- }
551
- // this is a hack to get temp support of mounting
552
- // working. In the future it will be moved to a
553
- // server specific process.
554
- if (element.mount != null) {
555
- const id = element.mount;
556
- applyIndent(indent + 1);
557
- fragment.mountPoints.push({
558
- id,
559
- part: fragment.html,
560
- });
561
- fragment.html = '';
562
- applyIndent(indent);
563
- break;
564
- }
565
- if (preformattedType !== undefined) {
566
- verbatimFirst = true;
567
- verbatimIndent = indent;
568
- verbatimSerialize = preformattedType === '{{';
569
- }
570
- else if (inlineText !== undefined) {
571
- element.text = inlineText;
572
- }
573
- break;
574
- }
575
- attribute1.lastIndex = 0;
576
- m2 = m1[ATTR] !== undefined
577
- ? attribute1.exec(m1[LINE])
578
- : undefined;
579
- if (m2 != null && element.tag != null) {
580
- if (m2[2] === 'id') {
581
- if (element.id == null) {
582
- element.id = m2[3].trim();
583
- }
584
- }
585
- else if (m2[2] === 'class') {
586
- if (element.class != null) {
587
- element.class += ' ' + m2[3].trim();
588
- }
589
- else {
590
- element.class = m2[3].trim();
591
- }
592
- }
593
- else if (element.attrs[m2[2]] != null) {
594
- element.attrs[m2[2]] += m2[3];
595
- }
596
- else {
597
- element.attrs[m2[2]] = m2[3];
598
- }
599
- break;
600
- }
601
- }
602
- default: {
603
- m2 = text1.exec(m1[0]);
604
- if (m2 == null) {
605
- break;
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
- }
654
- }
655
- fragment.refs = [];
656
- return fragment;
657
- }
658
- if (root?.mountable) {
659
- output.mountable = true;
660
- output.tail = root.html;
661
- output.mountPoints = root.mountPoints;
662
- return output;
663
- }
664
- for (let i = 0; i < parsed.size + 1; i++) {
665
- let fragment;
666
- if (i === 0 && root == null) {
667
- continue;
668
- }
669
- else if (i === 0) {
670
- fragment = root;
671
- }
672
- else {
673
- fragment = arr[i - 1];
674
- }
675
- if (fragment.refs == null || fragment.refs.length === 0) {
676
- continue;
677
- }
678
- flatten(fragment);
679
- }
680
- if (root?.html != null) {
681
- output.root = root.html;
682
- output.selector = `[data-${key}-root]`;
683
- }
684
- for (let i = 0; i < arr.length; i++) {
685
- let selector;
686
- const fragment = arr[i];
687
- if (fragment == null || claimed.has(fragment.id)) {
688
- continue;
689
- }
690
- switch (fragment.type) {
691
- case 'embed': {
692
- if (args?.predictable) {
693
- selector = `[id=${fragment.id}]`;
694
- break;
695
- }
696
- }
697
- case 'bare':
698
- case 'range': selector = `[data-${key}=${fragment.id}]`;
699
- }
700
- output.fragments[fragment.id] = {
701
- id: fragment.id,
702
- selector,
703
- type: fragment.type,
704
- html: fragment.html,
705
- };
706
- }
707
- if (doc == null)
708
- doc = new Doc(id, lang, dir, meta);
709
- output.key = key;
710
- output.id = doc?.id;
711
- output.lang = doc?.lang;
712
- output.dir = doc?.dir;
713
- return output;
714
- }
715
- /**
716
- * Processes a client side Longform template to HTML fragment string.
717
- *
718
- * @param fragment - The fragment identifier.
719
- * @param args - A record of template arguments.
720
- * @param getFragment - A function which returns an already processed fragment's HTML string.
721
- * @returns The processed template.
722
- */
723
- function processTemplate(template, args, getFragment) {
724
- const lf = template.replace(templateRe, (_match, param, ref) => {
725
- if (ref != null) {
726
- const fragment = getFragment(ref);
727
- if (fragment == null)
728
- return '';
729
- return fragment;
730
- }
731
- return args[param] != null ? escape(args[param].toString()) : '';
732
- });
733
- return Object.values(longform(lf).fragments)[0]?.html ?? null;
734
- }
735
-
736
- export { longform, processTemplate };
1
+ const e=/^((?: )*)(?:(@)([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*)|(--).*|(.+))$/gim,t=/([a-z][\w\-]*(?::[a-z][\w\-]*)?)((?:(?:[^:])|(?::(?!:)))*)::(?: (?:({{?)|(.*)))?/gi,l=/(?:\.([a-z][\w\-]+)|#([a-z][\w\-]+)|\[([a-z][a-z\-]+(?::[a-z][a-z|\-]*)?)(?:=(?:"([^"]+)"|'([^']+)'|([^\]]+)))?\])/gi,n=/((?:\ \ )+)\[(\w[\w-]*(?::\w[\w-]*)?)(?:=([^\n]+))?\]/,i=/^@([a-z][\w\-]*(?::[a-z][\w\-]*)?)$/i,a=/((?:\ \ )+)?#(#)?([\w\-]+)(?: ([\["]))?/i,r=/#\[([\w\-]+)\]/g,s=/([&<>"'#\[\]{}])/g,o=/^(\ \ )?([^\n]+)$/gm,d=/#(#)?{([a-z][\w\-]*(?::[a-z][\w\-]*)?)}|#\[([a-z][\w\-]*(?::[a-z][\w\-]*)?)\]/g,c=new Set(["}","}}"]),m=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wrb"]);let f=null,h=null,u=null,g=null;const b={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;"};function p(e){return e.replace(s,e=>b[e]??e)}function w(e=0){return{indent:e,key:void 0,id:void 0,tag:void 0,class:void 0,text:void 0,attrs:{},html:"",mount:void 0,serializerConfig:void 0,chain:void 0,beforeRender:void 0}}function y(e="bare"){return{id:void 0,type:e,html:"",template:!1,mountable:!1,els:[],refs:[],mountPoints:[]}}class v{id;lang;dir;meta;#e;constructor(e,t,l,n,i,a,r){if(this.id=e,this.lang=t,this.dir=l,this.meta=Object.freeze(n),this.#e={allowAll:i??!1,allowedAttributes:a??[],allowedElements:{}},null!=r)for(let e=0,t=r.length,l=r[e];e<t;e++)"string"==typeof l?this.#e.allowedElements[l]={tag:l,attrs:[]}:this.#e.allowedElements[l.tag]=l;Object.freeze(this)}allowAll(){}allowAttributes(){}allowElements(){}}const z=/^[a-z][a-z\-]*\:[a-z][a-z\-]*$/i;async function A(s,d){let m,b,A=!1,k="",R=0,I=0,j=w(),O=y(),E=null,P=d?.id,C=d?.lang,S=d?.dir,M={},U={},L=0;const q=new Set,D=new Map(Object.entries(d?.fragments??{})),F=Object.create(null),H={id:{attr:e=>e.doc.id},dir:{attr:e=>e.doc.dir},lang:{attr:e=>e.doc.lang}},T=[];let V=d?.key;if(d?.predictable||null!=V)null==V&&(V="lf");else{const e=new Uint8Array(10);crypto.getRandomValues(e),V=e.toHex().toLowerCase()}if(F.fragments={},F.templates={},null!=d?.directives){const e=Object.entries(d.directives);for(let t=0,l=e.length,n=e[t];t<l;t++)z.test(n[0])?H[n[0]]=n[1]:console.warn("Invalid custom directive name '$e{[0]}'")}s+="\n ",e.lastIndex=0;e:for(;f=e.exec(s);){if("--"===f[10])continue;O.template&&(O.html+=f[0]);const z=f[1].length/2;if(!A||0===z){if(A&&(A=!1),0!==I){if(z>=R&&(1!==I||void 0===(f[2]??f[5]??f[8]??f[9]))){1===I?k+=" "+f[0].trim():(""!==k&&(k+="\n"),k+=f[0].replace(f[1],""));continue}if(""===f[0].trim()&&s.length!==f.index+f[0].length){1!==I&&(k+="\n");continue}switch(I){case 1:for(O.html+=k;h=r.exec(k);){const e=O.html.length+h.index-k.length;O.refs.push({id:h[1],start:e,end:e+h[0].length})}$(z,V,j,O,b,D,F,d);break;case 2:O.html+=p(k);break;case 3:O.html+=k+"\n";break;case 4:if(null==m.def)break;if(null==b)"function"==typeof m.def.meta&&(M[f[3]]=m.def.meta({inlineArgs:m.inlineArgs,blockArgs:k}));else if("function"==typeof m.def.render)try{j.html+=m.def.render({doc:b,inlineArgs:m.inlineArgs,blockArgs:k})}catch(e){console.error(`Error in calling directive ${m.name}`),console.error(e)}else if("function"==typeof m.def.asyncRender){L++;const e=y("embed");e.id=`@${L}`,D.set(e.id,e),O.refs.push({id:e.id,start:O.html.length,end:O.html.length}),T.push(m.def.asyncRender({doc:b,inlineArgs:m.inlineArgs,blockArgs:k}).then(t=>{e.html=t??""}).catch(e=>{console.error(`Error in calling directive ${m.name}`),console.error(e)}))}else"function"==typeof m.def.element?(null==j.beforeRender&&(j.beforeRender=[]),j.beforeRender.push({blockArgs:k,inlineArgs:m.inlineArgs,element:m.def.element})):console.warn(`Directive used in incorrect context ${m.name}`)}if(I=0,k="",c.has(f[0].trim()))continue}if(""!==f[0].trim()){if(void 0===b){let e=!1;const t=f[4]??"";switch(f[3]){case"id":const l=t.trim();e=!0;try{P=P??new URL(l,d?.base).toString()}catch(e){console.warn(`Invalid URL given to @id directive: ${l} base=${d?.base}`)}break;case"lang":e=!0,C=C??t.trim();break;case"dir":e=!0,S=S??t.trim()}const l=H[f[3]];if("function"==typeof l?.meta||e){R=z+1,I=4,m={name:f[3],inlineArgs:t,def:l};continue e}if("head"===d?.outputMode)return{key:V,id:P,lang:C,dir:S,meta:M,data:U};b=new v(P,C,S,M,d?.allowAll,d?.allowedAttributes,d?.allowedElements)}switch(f[2]??f[5]??f[8]??f[9]){case"#":case"##":[j,O]=$(z,V,j,O,b,D,F,d),O.id=f[6],0===z?"["==f[7]?O.type="range":'"'===f[7]?O.type="text":"##"===f[5]?O.type="bare":(O.type="embed",j.id=O.id):j.id=O.id;break;case"@":void 0!==j.tag&&([j,O]=$(z,V,j,O,b,D,F,d));const r=f[4]??"";switch(f[3]){case"template":if(0===z){let t=!1;for(O.template=!0,o.lastIndex=e.lastIndex;h=o.exec(s);)if(null!=h[1]||t||null!=O.id){if(null==h[1]&&t){e.lastIndex=o.lastIndex-h[0].length;break}O.html+="\n"+h[0],null!=h[1]&&(t=!0)}else u=a.exec(h[0]),null!=u&&(O.id=u[3]),O.html+=h[0];[j,O]=$(0,V,j,O,b,D,F,d)}continue e;case"doctype":O.html+=`<!doctype ${(r.trim()||"html").trim()}>`;break;case"xml":O.html+=`<?xml ${r.trim()||'version="1.0" encoding="UTF-8"'}?>`;break;case"mount":if("mountable"!==d?.outputMode)break;""===r?console.warn("Mount points must have a name"):"root"!==O.type&&console.warn("Mounting is only allowed on a root element"),O.mountable=!0,j.mount=r.trim()}const c=H[f[3]];R=z+1,I=4,m={name:f[3],inlineArgs:f[4],def:c};break;case"::":if(void 0!==f[8]){let e,n;(void 0!==j.tag||j.indent>z)&&([j,O]=$(z,V,j,O,b,D,F,d)),j.indent=z,0===z&&null==O.id&&(null!=E?A=!0:(O.type="root",E=O));const a=j;for(t.lastIndex=0;h=t.exec(f[0]);){let t;for(e=h[3],n=h[4],void 0===j.tag?(j.tag=h[1],t=j):(void 0===a.chain&&(a.chain=[]),t=w(z),t.tag=h[1],a.chain.push(t)),l.lastIndex=0;u=l.exec(h[2]);)if(void 0!==u[2])t.id=u[2];else if(void 0!==u[1])null==t.class?t.class=u[1]:t.class+=" "+u[1];else{let e=u[4]??u[5]??u[6];if("@"===e[0]&&(i.lastIndex=0,g=i.exec(e),null!=g)){const t=H[g[1]];if(null==t||"function"!=typeof t.attr)break;if(e=t.attr({doc:b,tag:j.tag}),null==e)break}switch(u[3]){case"id":t.id||"string"!=typeof e||(t.id=e);break;case"class":t.class||"string"!=typeof e?"string"==typeof e&&(t.class+=" "+e):t.class=e;break;default:!1!==e&&(t.attrs[u[3]]=e)}}}if(null!=j.mount){const e=j.mount;[j,O]=$(z+1,V,j,O,b,D,F,d),null==O.mountPoints&&(O.mountPoints=[]),O.mountPoints.push({id:e,part:O.html}),O.html="",[j,O]=$(z,V,j,O,b,D,F,d);break}void 0!==e?(void 0!==j.tag&&([j,O]=$(z+1,V,j,O,b,D,F,d)),R=z+1,I="{{"===e?3:2):void 0!==n&&(j.text=n);break}case"[":if(n.lastIndex=0,h=void 0!==f[9]?n.exec(f[0]):null,null!=h&&null!=j.tag){"id"===h[2]?null==j.id&&(j.id=h[3].trim()):"class"===h[2]?null!=j.class?j.class+=" "+h[3].trim():j.class=h[3].trim():null!=j.attrs[h[2]]?j.attrs[h[2]]+=h[3]:j.attrs[h[2]]=h[3];break}default:void 0!==j.tag&&([j,O]=$(z,V,j,O,b,D,F,d)),k=f[0].trim(),R=z,I=1}}}}if($(0,V,j,O,b,D,F,d),T.length>0&&await Promise.all(T),E?.mountable)return F.mountable=!0,F.tail=E.html,F.mountPoints=E.mountPoints??[],F;const B=Array.from(D.values());for(let e=0;e<D.size+1;e++){let t;0===e&&null==E||(t=0===e?E:B[e-1],null!=t.refs&&0!==t.refs.length&&x(t,q,D))}null!=E?.html&&(F.root=E.html,F.selector=`[data-${V}-root]`);for(let e=0,t=B.length,l=B[e];e<t;e++){let e;if(null!=l&&!q.has(l.id)){switch(l.type){case"embed":if(d?.predictable){e=`[id=${l.id}]`;break}case"bare":case"range":e=`[data-${V}=${l.id}]`}F.fragments[l.id]={id:l.id,selector:e,type:l.type,html:l.html}}}return F.key=V,F.id=P,F.lang=C,F.dir=S,F.meta=M,F}async function k(e,t,l,n){const i=e.replace(d,(e,l,i,a)=>{if(void 0!==a&&"function"==typeof n){const e=n(a);return null==e?"":e}return null!=t[i]?p(t[i].toString()):""});return Object.values((await A(i,l)).fragments)[0]?.html??null}function $(e,t,l,n,i,a,r,s){if(void 0!==l.tag){if(Array.isArray(l.beforeRender)){const e={id:l.id,tag:l.tag,class:l.class,attrs:l.attrs},t=[];for(let e=0,n=l.chain.length,i=l.chain[e];e<n;e++)t.push({id:i.id,tag:i.tag,class:i.class,attrs:i.attrs});for(let n=0,a=l.beforeRender.length,r=l.beforeRender[n];n<a;n++)r.element({el:e,chain:t,doc:i,inlineArgs:r.inlineArgs,blockArgs:r.blockArgs})}const a="range"===n.type?e<2:""===n.html;n.html+=`<${l.tag}`,void 0!==l.id&&(n.html+=' id="'+l.id+'"'),void 0!==l.class&&(n.html+=' class="'+l.class+'"');for(const e of Object.entries(l.attrs))"id"!==e[0]&&"class"!==e[0]&&(null==e[1]?n.html+=" "+e[0]:n.html+=` ${e[0]}="${e[1]}"`);if(a&&("root"===n.type?n.html+=` data-${t}-root`:"bare"===n.type||"range"===n.type?n.html+=` data-${t}="${n.id}"`:"embed"!==n.type||s?.predictable||(n.html+=` data-${t}="${n.id}"`)),void 0!==l.mount&&(n.html+=` data-${t}-mount="${l.mount}"`),n.html+=">",Array.isArray(l.chain)){let e;for(let t=0,i=l.chain.length;t<i;t++){e=l.chain[t],n.html+="<"+e.tag,void 0!==e.id&&(n.html+=' id="'+e.id+'"'),null!=e.class&&(n.html+=' class="'+e.class+'"');for(const t of Object.entries(e.attrs))void 0===t[1]?n.html+=" "+t[0]:n.html+=` ${t[0]}="${t[1]}"`;n.html+=">"}}m.has(l.tag)||null==l.text||(n.html+=l.text),m.has(l.tag)||n.els.push(l)}if(e<=l.indent){for(l=w(e);0!==n.els.length&&(null==e||n.els[n.els.length-1].indent!==e-1);){const e=n.els.pop();if(Array.isArray(e.chain))for(let t=0,l=e.chain.length;t<l;t++)n.html+=`</${e.chain[t].tag}>`;n.html+=`</${e?.tag}>`}0===e&&(n.template?r.templates[n.id]=n.html:"root"!==n.type&&a.set(n.id,n),n=y())}else l=w(e);return[l,n]}function x(e,t,l,n=new Set){let i;if(Array.isArray(e.refs))for(let a=e.refs.length-1;a>-1;a--)if(i=e.refs[a],n.has(i.id)||t.has(i.id)||!l.has(i.id))e.html=e.html.slice(0,i.start)+e.html.slice(i.end);else{n.add(e.id);const a=x(l.get(i.id),t,l,n);e.html=e.html.slice(0,i.start)+a.html+e.html.slice(i.end),"embed"===a.type&&t.add(a.id)}return e.refs=void 0,e}export{A as longform,k as processTemplate};
737
2
  //# sourceMappingURL=longform.min.js.map