@longform/longform 0.0.22 → 0.0.24

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/lib/longform.ts CHANGED
@@ -189,15 +189,15 @@ export async function longform(
189
189
  }
190
190
  , promises: Array<Promise<void>> = []
191
191
 
192
- let key: string = args?.key as string;
192
+ let key: string | undefined = args?.key;
193
193
 
194
- if (!args?.predictable && key == null) {
194
+ if (key == null && args?.annotations === 'obscure') {
195
195
  const arr = new Uint8Array(10);
196
196
 
197
197
  crypto.getRandomValues(arr)
198
198
 
199
199
  key = arr.toHex().toLowerCase();
200
- } else if (key == null) {
200
+ } else if (key == null && args?.annotations === 'predictable') {
201
201
  key = 'lf';
202
202
  }
203
203
 
@@ -207,7 +207,11 @@ export async function longform(
207
207
  if (args?.directives != null) {
208
208
  const entries = Object.entries(args.directives);
209
209
 
210
- for (let i = 0, l = entries.length, e = entries[i]; i < l; i++) {
210
+ let e: [string, DirectiveDef];
211
+
212
+ for (let i = 0, l = entries.length; i < l; i++) {
213
+ e = entries[i];
214
+
211
215
  if (!directiveValidator.test(e[0])) {
212
216
  console.warn(`Invalid custom directive name '$e{[0]}'`);
213
217
 
@@ -306,7 +310,7 @@ export async function longform(
306
310
  doc,
307
311
  inlineArgs: directive.inlineArgs,
308
312
  blockArgs: verbatimText,
309
- }) + ' ';
313
+ }) ?? '' + ' ';
310
314
  } catch (err) {
311
315
  console.error(`Error in calling directive ${directive.name}`)
312
316
  console.error(err)
@@ -451,99 +455,33 @@ export async function longform(
451
455
  }
452
456
 
453
457
  break;
454
- case '@':
455
- if (element.tag !== undefined) {
458
+ case '::':
459
+ if (
460
+ element.tag !== undefined ||
461
+ element.indent > indent
462
+ ) {
456
463
  [element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
457
464
  }
458
465
 
459
- const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? ''
460
-
461
- switch (m1[DIRECTIVE]) {
462
- case 'template':
463
- if (indent === 0) {
464
- let indented = false;
465
- fragment.template = true;
466
-
467
- templateLinesRe.lastIndex = sniffTestRe.lastIndex;
468
- while ((m2 = templateLinesRe.exec(input))) {
469
- if (m2[1] == null && !indented && fragment.id == null) {
470
- m3 = idRe.exec(m2[0]);
471
-
472
- if (m3 != null) fragment.id = m3[3];
473
-
474
- fragment.html += m2[0];
475
- } else if (m2[1] == null && indented) {
476
- sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
477
- break;
478
- } else {
479
- fragment.html += '\n' + m2[0];
480
- if (m2[1] != null) indented = true;
481
- }
482
- }
483
-
484
- [element, fragment] = applyIndent(0, key, element, fragment, doc, parsed, output, args);
485
- }
486
-
487
- continue main;
488
- case 'doctype':
489
- fragment.html += `<!doctype ${(inlineArgs.trim() || 'html').trim()}>`;
490
- break;
491
- case 'xml':
492
- fragment.html += `<?xml ${inlineArgs.trim() || 'version="1.0" encoding="UTF-8"'}?>`;
493
- break;
494
- case 'mount':
495
- if (args?.outputMode !== 'mountable') break;
496
-
497
- if (inlineArgs === '') {
498
- console.warn('Mount points must have a name');
499
- } else if (fragment.type !== 'root') {
500
- console.warn('Mounting is only allowed on a root element');
501
- }
502
-
503
- fragment.mountable = true;
504
- element.mount = inlineArgs.trim();
505
- break;
466
+ element.indent = indent;
467
+
468
+ if (indent === 0 && fragment.id == null) {
469
+ if (root != null) {
470
+ // skip if root is found and this fragment
471
+ // has no id
472
+ skipping = true;
473
+ } else {
474
+ fragment.type = 'root';
475
+ root = fragment;
476
+ }
506
477
  }
507
-
508
- const def = directives[m1[DIRECTIVE]];
509
478
 
510
- // A directive may not be defined but we want to process
511
- // any block args to keep the output valid. Builtin directives
512
- // will be ignored unless they require block args.
513
- verbatimIndent = indent + 1;
514
- verbatimType = 4;
515
- directive = {
516
- name: m1[DIRECTIVE],
517
- inlineArgs: m1[DIRECTIVE_INLINE_ARGS],
518
- def,
519
- };
520
-
521
- break;
522
- case '::':
523
- if (m1[ELEMENT] !== undefined) {
479
+ // output mode templates parses fragments as though
480
+ // they were templates, including the root fragment
481
+ if (args?.outputMode !== 'templates') {
524
482
  let preformattedType: string | undefined;
525
483
  let inlineText: string | undefined;
526
484
 
527
- if (
528
- element.tag !== undefined ||
529
- element.indent > indent
530
- ) {
531
- [element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
532
- }
533
-
534
- element.indent = indent;
535
-
536
- if (indent === 0 && fragment.id == null) {
537
- if (root != null) {
538
- // skip if root is found and this fragment
539
- // has no id
540
- skipping = true;
541
- } else {
542
- fragment.type = 'root';
543
- root = fragment;
544
- }
545
- }
546
-
547
485
  const parent = element;
548
486
  outerRe.lastIndex = 0;
549
487
 
@@ -650,6 +588,79 @@ export async function longform(
650
588
 
651
589
  break;
652
590
  }
591
+
592
+ // handle as directive if output mode is templates
593
+ // the templateLinesRe directive will need to re-process this
594
+ // line so we rewind the sniffTestRe last index for it to copy
595
+ sniffTestRe.lastIndex = sniffTestRe.lastIndex - m1[0].length;
596
+ case '@':
597
+ if (element.tag !== undefined) {
598
+ [element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
599
+ }
600
+
601
+ const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? ''
602
+
603
+ switch (m1[DIRECTIVE] ?? 'template') {
604
+ case 'template':
605
+ if (indent === 0) {
606
+ let indented = false;
607
+ fragment.template = true;
608
+
609
+ templateLinesRe.lastIndex = sniffTestRe.lastIndex;
610
+ while ((m2 = templateLinesRe.exec(input))) {
611
+ if (m2[1] == null && !indented && fragment.id == null) {
612
+ m3 = idRe.exec(m2[0]);
613
+
614
+ if (m3 != null) fragment.id = m3[3];
615
+
616
+ fragment.html += m2[0];
617
+ } else if (m2[1] == null && indented) {
618
+ sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
619
+ break;
620
+ } else {
621
+ fragment.html += '\n' + m2[0];
622
+ if (m2[1] != null) indented = true;
623
+ }
624
+ }
625
+
626
+ [element, fragment] = applyIndent(0, key, element, fragment, doc, parsed, output, args);
627
+ }
628
+
629
+ continue main;
630
+ case 'doctype':
631
+ fragment.html += `<!doctype ${(inlineArgs.trim() || 'html').trim()}>`;
632
+ break;
633
+ case 'xml':
634
+ fragment.html += `<?xml ${inlineArgs.trim() || 'version="1.0" encoding="UTF-8"'}?>`;
635
+ break;
636
+ case 'mount':
637
+ if (args?.outputMode !== 'mountable') break;
638
+
639
+ if (inlineArgs === '') {
640
+ console.warn('Mount points must have a name');
641
+ } else if (fragment.type !== 'root') {
642
+ console.warn('Mounting is only allowed on a root element');
643
+ }
644
+
645
+ fragment.mountable = true;
646
+ element.mount = inlineArgs.trim();
647
+ break;
648
+ }
649
+
650
+ const def = directives[m1[DIRECTIVE]];
651
+
652
+ // A directive may not be defined but we want to process
653
+ // any block args to keep the output valid. Builtin directives
654
+ // will be ignored unless they require block args.
655
+ verbatimIndent = indent + 1;
656
+ verbatimType = 4;
657
+ directive = {
658
+ name: m1[DIRECTIVE],
659
+ inlineArgs: m1[DIRECTIVE_INLINE_ARGS],
660
+ def,
661
+ };
662
+
663
+ break;
653
664
  case '[':
654
665
  // TODO: Add attr directive support.
655
666
  attributeRe.lastIndex = 0;
@@ -719,13 +730,18 @@ export async function longform(
719
730
  }
720
731
 
721
732
  if (root?.html != null) {
722
- output.root = root.html;
723
- output.selector = `[data-${key}-root]`;
733
+ if (root.template) {
734
+ output.template = root.html;
735
+ } else {
736
+ output.root = root.html;
737
+
738
+ if (key != null) output.selector = `[data-${key}-root]`;
739
+ }
724
740
  }
725
741
 
726
742
  let f: WorkingFragment;
727
743
  for (let i = 0, l = arr.length, f = arr[i]; i < l; i++) {
728
- let selector!: string;
744
+ let selector: string = undefined as unknown as string;
729
745
 
730
746
  f = arr[i];
731
747
 
@@ -734,14 +750,16 @@ export async function longform(
734
750
  continue;
735
751
  }
736
752
 
737
- switch (f.type) {
738
- case 'embed':
739
- if (args?.predictable) {
740
- selector = `[id=${f.id}]`;
741
- break;
742
- }
743
- case 'bare':
744
- case 'range': selector = `[data-${key}=${f.id}]`;
753
+ if (key != null) {
754
+ switch (f.type) {
755
+ case 'embed':
756
+ if (args?.annotations === 'obscure') {
757
+ selector = `[id=${f.id}]`;
758
+ break;
759
+ }
760
+ case 'bare':
761
+ case 'range': selector = `[data-${key}=${f.id}]`;
762
+ }
745
763
  }
746
764
 
747
765
  output.fragments[f.id as string] = {
@@ -752,7 +770,7 @@ export async function longform(
752
770
  };
753
771
  }
754
772
 
755
- output.key = key;
773
+ output.key = key as string;
756
774
  output.id = id;
757
775
  output.lang = lang;
758
776
  output.dir = dir;
@@ -796,7 +814,7 @@ export async function processTemplate(
796
814
  */
797
815
  function applyIndent(
798
816
  targetIndent: number,
799
- key: string,
817
+ key: string | undefined,
800
818
  element: WorkingElement,
801
819
  fragment: WorkingFragment,
802
820
  doc: Doc,
@@ -858,18 +876,20 @@ function applyIndent(
858
876
  }
859
877
  }
860
878
 
861
- if (root) {
862
- if (fragment.type === 'root') {
863
- fragment.html += ` data-${key}-root`;
864
- } else if (fragment.type === 'bare' || fragment.type === 'range') {
865
- fragment.html += ` data-${key}="${fragment.id}"`;
866
- } else if (fragment.type === 'embed' && !args?.predictable) {
867
- fragment.html += ` data-${key}="${fragment.id}"`;
879
+ if (key != null) {
880
+ if (root) {
881
+ if (fragment.type === 'root') {
882
+ fragment.html += ` data-${key}-root`;
883
+ } else if (fragment.type === 'bare' || fragment.type === 'range') {
884
+ fragment.html += ` data-${key}="${fragment.id}"`;
885
+ } else if (fragment.type === 'embed' && args?.annotations === 'obscure') {
886
+ fragment.html += ` data-${key}="${fragment.id}"`;
887
+ }
868
888
  }
869
- }
870
889
 
871
- if (element.mount !== undefined) {
872
- fragment.html += ` data-${key}-mount="${element.mount}"`;
890
+ if (element.mount !== undefined) {
891
+ fragment.html += ` data-${key}-mount="${element.mount}"`;
892
+ }
873
893
  }
874
894
 
875
895
  fragment.html += '>';
@@ -934,10 +954,12 @@ function applyIndent(
934
954
  }
935
955
 
936
956
  if (targetIndent === 0) {
937
- if (fragment.template) {
938
- output.templates[fragment.id] = fragment.html;
939
- } else if (fragment.type !== 'root') {
940
- parsed.set(fragment.id, fragment);
957
+ if (fragment.type !== 'root') {
958
+ if (fragment.template) {
959
+ output.templates[fragment.id] = fragment.html;
960
+ } else {
961
+ parsed.set(fragment.id, fragment);
962
+ }
941
963
  }
942
964
 
943
965
  fragment = makeFragment();
package/lib/types.ts CHANGED
@@ -151,10 +151,16 @@ export type AppliedDirective = {
151
151
  element?: (ctx: ElementCtx) => void;
152
152
  }
153
153
 
154
+ export type Annotation =
155
+ | 'predictable'
156
+ | 'obscure'
157
+ ;
158
+
154
159
  export type OutputMode =
155
160
  | 'doc'
156
161
  | 'head'
157
162
  | 'mountable'
163
+ | 'templates'
158
164
  ;
159
165
 
160
166
  export type LongformArgs = {
@@ -165,14 +171,6 @@ export type LongformArgs = {
165
171
  */
166
172
  key?: string;
167
173
 
168
- /**
169
- * If true the parser will default to using the 'lf' key for data attributes
170
- * and fragments with embedded ids will not used data attributes for selectors.
171
- *
172
- * This is less safe in environments where Longform is used for SSR.
173
- */
174
- predictable?: boolean;
175
-
176
174
  /**
177
175
  * The id of the document. If set the @id is ignored.
178
176
  * This can be used when creating partial re-usable documents.
@@ -204,6 +202,11 @@ export type LongformArgs = {
204
202
  */
205
203
  meta?: Record<string, unknown>;
206
204
 
205
+ /**
206
+ * Style in which data attributes are added to output HTML markup.
207
+ */
208
+ annotations?: Annotation;
209
+
207
210
  /**
208
211
  * The output mode of the document.
209
212
  */
@@ -226,6 +229,7 @@ export type ParsedResult = {
226
229
  data: Record<string, unknown>;
227
230
  mountable?: boolean;
228
231
  root?: string;
232
+ template?: string;
229
233
  selector?: string;
230
234
  mountPoints: MountPoint[];
231
235
  tail?: string;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "author": "Matthew Quinn",
5
5
  "homepage": "https://github.com/occultist-dev/longform",
6
6
  "license": "MIT",
7
- "version": "0.0.22",
7
+ "version": "0.0.24",
8
8
  "type": "module",
9
9
  "keywords": [
10
10
  "HTML",