@aglyn/plugins-ai 1.0.0-beta.291 → 1.0.0-beta.293

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.
Files changed (149) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-site-import-job.component.js +5 -4
  3. package/src/lib/components/ai-site-import-job.component.js.map +1 -1
  4. package/src/lib/components/ai-site-import-start.component.js +3 -3
  5. package/src/lib/components/ai-site-import-start.component.js.map +1 -1
  6. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  7. package/src/lib/components/ai-site-start-card.component.js +129 -29
  8. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  9. package/src/lib/components/ai-site-start-steps.component.d.ts +15 -3
  10. package/src/lib/components/ai-site-start-steps.component.js +42 -25
  11. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  12. package/src/lib/components/ai-site-style-previews.d.ts +17 -0
  13. package/src/lib/components/ai-site-style-previews.js +26 -95
  14. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  15. package/src/lib/declarations.server.js +22 -0
  16. package/src/lib/declarations.server.js.map +1 -1
  17. package/src/lib/jobs/ai-job-import-step.d.ts +21 -2
  18. package/src/lib/jobs/ai-job-import-step.js +179 -49
  19. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  20. package/src/lib/jobs/ai-job-layout-language.d.ts +27 -3
  21. package/src/lib/jobs/ai-job-layout-language.js +156 -113
  22. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  23. package/src/lib/jobs/ai-job-layout-step.js +56 -17
  24. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  25. package/src/lib/jobs/ai-job-page-language.d.ts +25 -15
  26. package/src/lib/jobs/ai-job-page-language.js +120 -66
  27. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  28. package/src/lib/jobs/ai-job-page-step.js +27 -7
  29. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  30. package/src/lib/jobs/ai-job-site-datasets.d.ts +12 -4
  31. package/src/lib/jobs/ai-job-site-datasets.js +36 -16
  32. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  33. package/src/lib/jobs/ai-jobs.js +6 -2
  34. package/src/lib/jobs/ai-jobs.js.map +1 -1
  35. package/src/lib/jobs/ai-layout-stock-photos.d.ts +50 -1
  36. package/src/lib/jobs/ai-layout-stock-photos.js +586 -35
  37. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  38. package/src/lib/jobs/ai-site-import-compile.d.ts +147 -0
  39. package/src/lib/jobs/ai-site-import-compile.js +1143 -0
  40. package/src/lib/jobs/ai-site-import-compile.js.map +1 -0
  41. package/src/lib/jobs/ai-site-import-document.d.ts +9 -36
  42. package/src/lib/jobs/ai-site-import-document.js +23 -489
  43. package/src/lib/jobs/ai-site-import-document.js.map +1 -1
  44. package/src/lib/jobs/ai-site-import-look.d.ts +54 -7
  45. package/src/lib/jobs/ai-site-import-look.js +163 -52
  46. package/src/lib/jobs/ai-site-import-look.js.map +1 -1
  47. package/src/lib/jobs/ai-site-import-publish.d.ts +7 -1
  48. package/src/lib/jobs/ai-site-import-publish.js +19 -5
  49. package/src/lib/jobs/ai-site-import-publish.js.map +1 -1
  50. package/src/lib/jobs/ai-site-late-photos.js +4 -2
  51. package/src/lib/jobs/ai-site-late-photos.js.map +1 -1
  52. package/src/lib/jobs/ai-site-publish.d.ts +10 -1
  53. package/src/lib/jobs/ai-site-publish.js +8 -3
  54. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  55. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.d.ts +23 -0
  56. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.js +677 -0
  57. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.js.map +1 -0
  58. package/src/lib/layout-language/ai-layout-compiler.d.ts +26 -0
  59. package/src/lib/layout-language/ai-layout-compiler.js +677 -33
  60. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  61. package/src/lib/layout-language/ai-layout-frame-hollows.js +5 -1
  62. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -1
  63. package/src/lib/layout-language/ai-layout-frame-source.d.ts +196 -0
  64. package/src/lib/layout-language/ai-layout-frame-source.js +726 -0
  65. package/src/lib/layout-language/ai-layout-frame-source.js.map +1 -0
  66. package/src/lib/layout-language/ai-layout-frame.d.ts +22 -0
  67. package/src/lib/layout-language/ai-layout-frame.js +121 -29
  68. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  69. package/src/lib/layout-language/ai-layout-language.d.ts +103 -37
  70. package/src/lib/layout-language/ai-layout-language.js +85 -120
  71. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  72. package/src/lib/model/ai-credit-estimate.d.ts +11 -7
  73. package/src/lib/model/ai-credit-estimate.js +11 -7
  74. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  75. package/src/lib/model/ai-site-import.d.ts +307 -13
  76. package/src/lib/model/ai-site-import.js +227 -19
  77. package/src/lib/model/ai-site-import.js.map +1 -1
  78. package/src/lib/model/ai-site-look.d.ts +65 -1
  79. package/src/lib/model/ai-site-look.js +141 -23
  80. package/src/lib/model/ai-site-look.js.map +1 -1
  81. package/src/lib/model/ai-site-photos.d.ts +2 -0
  82. package/src/lib/model/ai-site-photos.js.map +1 -1
  83. package/src/lib/model/ai-site-start-steps.d.ts +4 -3
  84. package/src/lib/model/ai-site-start-steps.js +11 -3
  85. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  86. package/src/lib/model/ai-site-start.d.ts +23 -2
  87. package/src/lib/model/ai-site-start.js +25 -3
  88. package/src/lib/model/ai-site-start.js.map +1 -1
  89. package/src/lib/providers/routing.d.ts +11 -0
  90. package/src/lib/providers/routing.js +23 -0
  91. package/src/lib/providers/routing.js.map +1 -1
  92. package/src/lib/runtime/ai-doctrine-validators.d.ts +4 -2
  93. package/src/lib/runtime/ai-doctrine-validators.js +7 -2
  94. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  95. package/src/lib/runtime/ai-icon-library.d.ts +4 -0
  96. package/src/lib/runtime/ai-icon-library.js +4 -0
  97. package/src/lib/runtime/ai-icon-library.js.map +1 -1
  98. package/src/lib/runtime/ai-node-tree.js +5 -1
  99. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  100. package/src/lib/runtime/ai-palette.d.ts +2 -0
  101. package/src/lib/runtime/ai-palette.generated.js +19 -3
  102. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  103. package/src/lib/runtime/ai-palette.js +2 -1
  104. package/src/lib/runtime/ai-palette.js.map +1 -1
  105. package/src/lib/site-import/fixtures/site-layout-recordings.d.ts +50 -0
  106. package/src/lib/site-import/fixtures/site-layout-recordings.js +86 -0
  107. package/src/lib/site-import/fixtures/site-layout-recordings.js.map +1 -0
  108. package/src/lib/site-import/site-import-crawl.d.ts +18 -1
  109. package/src/lib/site-import/site-import-crawl.js +70 -10
  110. package/src/lib/site-import/site-import-crawl.js.map +1 -1
  111. package/src/lib/site-import/site-import-css.d.ts +110 -0
  112. package/src/lib/site-import/site-import-css.js +826 -0
  113. package/src/lib/site-import/site-import-css.js.map +1 -0
  114. package/src/lib/site-import/site-import-html.d.ts +116 -1
  115. package/src/lib/site-import/site-import-html.js +538 -46
  116. package/src/lib/site-import/site-import-html.js.map +1 -1
  117. package/src/lib/site-import/site-import-layout.d.ts +79 -0
  118. package/src/lib/site-import/site-import-layout.js +1608 -0
  119. package/src/lib/site-import/site-import-layout.js.map +1 -0
  120. package/src/lib/site-import/site-import-nav.d.ts +40 -0
  121. package/src/lib/site-import/site-import-nav.js +437 -0
  122. package/src/lib/site-import/site-import-nav.js.map +1 -0
  123. package/src/lib/site-import/site-import-page.d.ts +8 -1
  124. package/src/lib/site-import/site-import-page.js +24 -2
  125. package/src/lib/site-import/site-import-page.js.map +1 -1
  126. package/src/lib/assets/site-styles/beauty.webp +0 -0
  127. package/src/lib/assets/site-styles/blog.webp +0 -0
  128. package/src/lib/assets/site-styles/business.webp +0 -0
  129. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  130. package/src/lib/assets/site-styles/education.webp +0 -0
  131. package/src/lib/assets/site-styles/events.webp +0 -0
  132. package/src/lib/assets/site-styles/fitness.webp +0 -0
  133. package/src/lib/assets/site-styles/landing.webp +0 -0
  134. package/src/lib/assets/site-styles/music.webp +0 -0
  135. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  136. package/src/lib/assets/site-styles/personal.webp +0 -0
  137. package/src/lib/assets/site-styles/photography.webp +0 -0
  138. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  139. package/src/lib/assets/site-styles/professional.webp +0 -0
  140. package/src/lib/assets/site-styles/realestate.webp +0 -0
  141. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  142. package/src/lib/assets/site-styles/store.webp +0 -0
  143. package/src/lib/assets/site-styles/studio.webp +0 -0
  144. package/src/lib/assets/site-styles/trades.webp +0 -0
  145. package/src/lib/assets/site-styles/wellness.webp +0 -0
  146. package/src/lib/assets/site-styles/yoga.webp +0 -0
  147. package/src/lib/site-import/site-import-review.d.ts +0 -40
  148. package/src/lib/site-import/site-import-review.js +0 -149
  149. package/src/lib/site-import/site-import-review.js.map +0 -1
@@ -50,7 +50,12 @@
50
50
  /** A mega-menu's entries and the footer's columns together. */ navLinks: 150,
51
51
  forms: 10,
52
52
  fieldsPerForm: 30,
53
- icons: 12
53
+ icons: 12,
54
+ /** The structure's ceilings (AGL-4106): elements, inline icons, links and the page's own style sheets. */ elements: 20000,
55
+ svgIcons: 200,
56
+ anchors: 800,
57
+ styleChars: 1500000,
58
+ stylesheets: 4
54
59
  };
55
60
  /** Elements removed with everything inside them. */ const REMOVED_WITH_CONTENT = [
56
61
  'script',
@@ -68,10 +73,14 @@
68
73
  'video',
69
74
  'audio'
70
75
  ];
71
- /** Form controls, removed with their content once the forms are summarized. */ const FORM_CONTROLS = [
76
+ /**
77
+ * Form controls, removed with their content once the forms are summarized.
78
+ * A `<button>` outside a form is not one (AGL-4106): its words are a line
79
+ * ("Watch the 40-second film", a pricing tab's name) and a picture inside it
80
+ * is still a picture. A form's own buttons go with the form.
81
+ */ const FORM_CONTROLS = [
72
82
  'textarea',
73
- 'select',
74
- 'button'
83
+ 'select'
75
84
  ];
76
85
  /** Void elements removed wherever they appear. */ const REMOVED_VOID = /<(?:embed|param|source|track|link|base)\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi;
77
86
  const PASSWORD_INPUT = /<input\b(?:[^>"']|"[^"]*"|'[^']*')*\btype\s*=\s*["']?password\b/i;
@@ -91,6 +100,8 @@ const BLOCK_TAGS = {
91
100
  figcaption: 'paragraph',
92
101
  caption: 'paragraph',
93
102
  label: 'paragraph',
103
+ /** A disclosure's question, a button's words (AGL-4106). */ summary: 'paragraph',
104
+ button: 'paragraph',
94
105
  blockquote: 'quote'
95
106
  };
96
107
  /** Tags that end a run of loose text without being a block themselves. */ const BREAK_TAGS = new Set([
@@ -109,6 +120,8 @@ const BLOCK_TAGS = {
109
120
  'tr',
110
121
  'tbody',
111
122
  'thead',
123
+ 'tfoot',
124
+ 'details',
112
125
  'figure',
113
126
  'form',
114
127
  'hr',
@@ -274,6 +287,8 @@ function removeElements(html, tags) {
274
287
  kept.push({
275
288
  fields
276
289
  });
290
+ // Where the form sat, for the layout (AGL-4106): a marker, never words.
291
+ return ` <aglyn-form data-form="${kept.length - 1}"> `;
277
292
  }
278
293
  return ' ';
279
294
  });
@@ -430,12 +445,325 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
430
445
  sameAs: [].concat((_node_sameAs = node['sameAs']) != null ? _node_sameAs : []).map((entry)=>urlOf(entry)).filter(Boolean).slice(0, 20)
431
446
  };
432
447
  }
448
+ /*------------------------------------------
449
+ * How the page is built (AGL-4106)
450
+ *-----------------------------------------*/ const SVG_ELEMENT = /<svg\b((?:[^>"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/svg\s*>/gi;
451
+ const SVG_SHAPE = /<(path|circle|rect|ellipse|line|polyline|polygon)\b((?:[^>"']|"[^"]*"|'[^']*')*)>/gi;
452
+ /** What a drawing keeps of a shape: its geometry and paint, never a link, a script or a reference. */ const SVG_SHAPE_ATTRIBUTES = new Set([
453
+ 'd',
454
+ 'cx',
455
+ 'cy',
456
+ 'r',
457
+ 'rx',
458
+ 'ry',
459
+ 'x',
460
+ 'y',
461
+ 'x1',
462
+ 'y1',
463
+ 'x2',
464
+ 'y2',
465
+ 'width',
466
+ 'height',
467
+ 'points',
468
+ 'fill',
469
+ 'fill-rule',
470
+ 'clip-rule',
471
+ 'fill-opacity',
472
+ 'stroke',
473
+ 'stroke-width',
474
+ 'stroke-linecap',
475
+ 'stroke-linejoin',
476
+ 'opacity',
477
+ 'transform'
478
+ ]);
479
+ const SVG_SAFE_VALUE = /^[\w\s.,#%()+-]*$/;
480
+ const SVG_PATH_DATA = /^[MmZzLlHhVvCcSsQqTtAa0-9eE.,\s+-]+$/;
481
+ const VIEW_BOX = /^-?[\d.]+(?:[\s,]+-?[\d.]+){3}$/;
482
+ /** An `<svg>`'s drawing area: its `viewBox`, else its size, else the 24-unit square of an icon set. */ function viewBoxOf(attributes) {
483
+ var _attributes_viewbox, _attributes_width, _attributes_height;
484
+ const written = ((_attributes_viewbox = attributes['viewbox']) != null ? _attributes_viewbox : '').trim().replace(/[\s,]+/g, ' ');
485
+ if (VIEW_BOX.test(written)) return written;
486
+ const width = parseFloat((_attributes_width = attributes['width']) != null ? _attributes_width : '');
487
+ const height = parseFloat((_attributes_height = attributes['height']) != null ? _attributes_height : '');
488
+ return width > 0 && height > 0 ? `0 0 ${width} ${height}` : '0 0 24 24';
489
+ }
490
+ /**
491
+ * Every inline `<svg>` turned into a marker the walk reads as an icon at its
492
+ * place, its drawing kept in `icons` once however often it repeats. A drawing
493
+ * that embeds anything but shapes (a picture, text, a reference) is no icon
494
+ * and goes, as every `<svg>` did before.
495
+ */ function readSvgIcons(html, icons) {
496
+ const seen = new Map();
497
+ return html.replace(SVG_ELEMENT, (whole, attributeSource, inner)=>{
498
+ var _icon_path;
499
+ if (/<(?:image|foreignobject|use|text|script)\b/i.test(inner)) return ' ';
500
+ const shapes = [
501
+ ...inner.matchAll(SVG_SHAPE)
502
+ ];
503
+ if (!shapes.length) return ' ';
504
+ const viewBox = viewBoxOf(parseAttributes(attributeSource));
505
+ let icon = null;
506
+ if (shapes.length === 1 && shapes[0][1].toLowerCase() === 'path') {
507
+ var _parseAttributes_d;
508
+ const d = ((_parseAttributes_d = parseAttributes(shapes[0][2])['d']) != null ? _parseAttributes_d : '').trim();
509
+ if (d && d.length <= 4096 && SVG_PATH_DATA.test(d)) icon = {
510
+ viewBox,
511
+ path: d
512
+ };
513
+ }
514
+ if (!icon && whole.length <= 16384 && shapes.length <= 64) {
515
+ const drawn = shapes.map(([, tag, source])=>{
516
+ const kept = Object.entries(parseAttributes(source)).filter(([name, value])=>SVG_SHAPE_ATTRIBUTES.has(name) && SVG_SAFE_VALUE.test(value) && !/url\s*\(/i.test(value)).map(([name, value])=>`${name}="${value}"`);
517
+ return `<${tag.toLowerCase()}${kept.length ? ` ${kept.join(' ')}` : ''}/>`;
518
+ });
519
+ icon = {
520
+ viewBox,
521
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}">${drawn.join('')}</svg>`
522
+ };
523
+ }
524
+ if (!icon) return ' ';
525
+ const key = `${icon.viewBox}|${(_icon_path = icon.path) != null ? _icon_path : icon.svg}`;
526
+ let index = seen.get(key);
527
+ if (index === undefined) {
528
+ if (icons.length >= SITE_IMPORT_PAGE_LIMITS.svgIcons) return ' ';
529
+ index = icons.length;
530
+ icons.push(icon);
531
+ seen.set(key, index);
532
+ }
533
+ return ` <aglyn-svg data-icon="${index}"> `;
534
+ });
535
+ }
536
+ /** The page's own `<style>` sheets and the stylesheets it links, print ones left out. */ function readStyleSheets(html, pageUrl) {
537
+ const styles = [];
538
+ let chars = 0;
539
+ for (const match of html.matchAll(/<style\b((?:[^>"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/style\s*>/gi)){
540
+ var _parseAttributes_media, _match_;
541
+ const media = ((_parseAttributes_media = parseAttributes(match[1])['media']) != null ? _parseAttributes_media : '').toLowerCase();
542
+ if (media && !/\b(?:all|screen)\b/.test(media)) continue;
543
+ const css = (_match_ = match[2]) != null ? _match_ : '';
544
+ if (!css.trim() || chars + css.length > SITE_IMPORT_PAGE_LIMITS.styleChars) continue;
545
+ chars += css.length;
546
+ styles.push(css);
547
+ }
548
+ const stylesheets = [];
549
+ for (const match of html.matchAll(/<link\b((?:[^>"']|"[^"]*"|'[^']*')*)>/gi)){
550
+ var _attributes_rel, _attributes_media;
551
+ const attributes = parseAttributes(match[1]);
552
+ if (!((_attributes_rel = attributes['rel']) != null ? _attributes_rel : '').toLowerCase().split(/\s+/).includes('stylesheet')) continue;
553
+ const media = ((_attributes_media = attributes['media']) != null ? _attributes_media : '').toLowerCase();
554
+ if (media && !/\b(?:all|screen)\b/.test(media)) continue;
555
+ const url = resolveSiteUrl(attributes['href'], pageUrl);
556
+ if (!url || url.protocol !== 'https:' || stylesheets.includes(url.toString())) continue;
557
+ stylesheets.push(url.toString());
558
+ if (stylesheets.length >= SITE_IMPORT_PAGE_LIMITS.stylesheets) break;
559
+ }
560
+ return {
561
+ styles,
562
+ stylesheets
563
+ };
564
+ }
565
+ /** Elements with no content and no end tag. */ const VOID_TAGS = new Set([
566
+ 'area',
567
+ 'base',
568
+ 'br',
569
+ 'col',
570
+ 'embed',
571
+ 'hr',
572
+ 'img',
573
+ 'input',
574
+ 'link',
575
+ 'meta',
576
+ 'param',
577
+ 'source',
578
+ 'track',
579
+ 'wbr',
580
+ 'aglyn-svg',
581
+ 'aglyn-form'
582
+ ]);
583
+ /** Start tags that end an open `<p>`, as a browser reads them. */ const CLOSES_P = new Set([
584
+ 'address',
585
+ 'article',
586
+ 'aside',
587
+ 'blockquote',
588
+ 'details',
589
+ 'div',
590
+ 'dl',
591
+ 'fieldset',
592
+ 'figcaption',
593
+ 'figure',
594
+ 'footer',
595
+ 'form',
596
+ 'h1',
597
+ 'h2',
598
+ 'h3',
599
+ 'h4',
600
+ 'h5',
601
+ 'h6',
602
+ 'header',
603
+ 'hr',
604
+ 'main',
605
+ 'menu',
606
+ 'nav',
607
+ 'ol',
608
+ 'p',
609
+ 'pre',
610
+ 'section',
611
+ 'table',
612
+ 'ul'
613
+ ]);
614
+ /** For each tag that ends an open one of its own kind: the kinds it ends, and the tags that wall the search off. */ const IMPLIED_END = {
615
+ li: {
616
+ ends: new Set([
617
+ 'li'
618
+ ]),
619
+ walls: new Set([
620
+ 'ul',
621
+ 'ol',
622
+ 'menu'
623
+ ])
624
+ },
625
+ dt: {
626
+ ends: new Set([
627
+ 'dt',
628
+ 'dd'
629
+ ]),
630
+ walls: new Set([
631
+ 'dl'
632
+ ])
633
+ },
634
+ dd: {
635
+ ends: new Set([
636
+ 'dt',
637
+ 'dd'
638
+ ]),
639
+ walls: new Set([
640
+ 'dl'
641
+ ])
642
+ },
643
+ tr: {
644
+ ends: new Set([
645
+ 'tr',
646
+ 'td',
647
+ 'th'
648
+ ]),
649
+ walls: new Set([
650
+ 'table',
651
+ 'tbody',
652
+ 'thead',
653
+ 'tfoot'
654
+ ])
655
+ },
656
+ td: {
657
+ ends: new Set([
658
+ 'td',
659
+ 'th'
660
+ ]),
661
+ walls: new Set([
662
+ 'tr',
663
+ 'table'
664
+ ])
665
+ },
666
+ th: {
667
+ ends: new Set([
668
+ 'td',
669
+ 'th'
670
+ ]),
671
+ walls: new Set([
672
+ 'tr',
673
+ 'table'
674
+ ])
675
+ },
676
+ option: {
677
+ ends: new Set([
678
+ 'option'
679
+ ]),
680
+ walls: new Set([
681
+ 'select',
682
+ 'datalist'
683
+ ])
684
+ },
685
+ tbody: {
686
+ ends: new Set([
687
+ 'tbody',
688
+ 'thead',
689
+ 'tfoot',
690
+ 'tr',
691
+ 'td',
692
+ 'th'
693
+ ]),
694
+ walls: new Set([
695
+ 'table'
696
+ ])
697
+ },
698
+ thead: {
699
+ ends: new Set([
700
+ 'tbody',
701
+ 'thead',
702
+ 'tfoot',
703
+ 'tr',
704
+ 'td',
705
+ 'th'
706
+ ]),
707
+ walls: new Set([
708
+ 'table'
709
+ ])
710
+ },
711
+ tfoot: {
712
+ ends: new Set([
713
+ 'tbody',
714
+ 'thead',
715
+ 'tfoot',
716
+ 'tr',
717
+ 'td',
718
+ 'th'
719
+ ]),
720
+ walls: new Set([
721
+ 'table'
722
+ ])
723
+ }
724
+ };
725
+ const P_WALLS = new Set([
726
+ 'button',
727
+ 'table',
728
+ 'td',
729
+ 'th',
730
+ 'caption',
731
+ 'object',
732
+ 'template',
733
+ 'html',
734
+ 'marquee',
735
+ 'applet'
736
+ ]);
737
+ /** The attributes the layout reader reads. */ function elementAttrs(attributes) {
738
+ var _attributes_datasectiontheme, _attributes_ariahidden;
739
+ var _text, _text1, _text2;
740
+ const attrs = {};
741
+ const text = (value, max)=>{
742
+ const clean = String(value != null ? value : '').replace(/\s+/g, ' ').trim();
743
+ return clean ? clean.slice(0, max) : undefined;
744
+ };
745
+ const put = (key, value)=>{
746
+ if (value !== undefined) attrs[key] = value;
747
+ };
748
+ put('class', text(attributes['class'], 600));
749
+ put('id', text(attributes['id'], 120));
750
+ put('style', text(attributes['style'], 1000));
751
+ put('role', (_text = text(attributes['role'], 40)) == null ? void 0 : _text.toLowerCase());
752
+ put('label', text(attributes['aria-label'], 200));
753
+ put('href', text(attributes['href'], 2048));
754
+ put('theme', (_text1 = text((_attributes_datasectiontheme = attributes['data-section-theme']) != null ? _attributes_datasectiontheme : attributes['data-theme'], 40)) == null ? void 0 : _text1.toLowerCase());
755
+ put('type', (_text2 = text(attributes['type'], 40)) == null ? void 0 : _text2.toLowerCase());
756
+ put('controls', text(attributes['aria-controls'], 120));
757
+ put('labelledby', text(attributes['aria-labelledby'], 120));
758
+ if ('hidden' in attributes || ((_attributes_ariahidden = attributes['aria-hidden']) != null ? _attributes_ariahidden : '').toLowerCase() === 'true') attrs.hidden = true;
759
+ return attrs;
760
+ }
433
761
  /**
434
762
  * The words, structure, pictures and links of one page.
435
763
  *
436
764
  * `isSameSite(url)` says whether an address is on the site being imported;
437
765
  * only those become `links`, and images are marked by it.
438
- */ export function extractSiteImportPage(html, pageUrl, isSameSite) {
766
+ */ export function extractSiteImportPage(html, pageUrl, isSameSite, options = {}) {
439
767
  var _ref, _ref1;
440
768
  var _resolveSiteUrl, _parseAttributes_lang;
441
769
  const empty = {
@@ -465,6 +793,14 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
465
793
  // Read before scripts go: the structured data is a script's body.
466
794
  const organization = readOrganization(source);
467
795
  const icons = readIcons(source, pageUrl);
796
+ const keep = options.structure === true;
797
+ const sheets = keep ? readStyleSheets(source, pageUrl) : {
798
+ styles: [],
799
+ stylesheets: []
800
+ };
801
+ // An inline icon is a marker at its place (AGL-4106); its drawing is never words.
802
+ const svgIcons = [];
803
+ source = readSvgIcons(source, svgIcons);
468
804
  source = removeElements(readPictures(source), REMOVED_WITH_CONTENT).replace(REMOVED_VOID, ' ');
469
805
  // Forms next, so a field list is read before the controls go.
470
806
  const forms = takeForms(source);
@@ -513,24 +849,112 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
513
849
  let buffer = '';
514
850
  /** The link being read: its menu address (http(s) only), where it goes, its words and its first picture. */ let anchor = null;
515
851
  /** The links read into the block being built, written after it. */ let pending = [];
852
+ // How the page is built (AGL-4106): every element, in a flat table, and
853
+ // the element each block's words sit in. Kept only when asked for.
854
+ const elements = [];
855
+ const blockElement = [];
856
+ const anchors = [];
857
+ const texts = [];
858
+ /** Where in `buffer` the words no link has taken begin: a menu's own words are read from here. */ let ownFrom = 0;
859
+ /** A menu's or a header's own words, outside its links (AGL-4106), from `ownFrom` on. */ const takeOwnWords = (element)=>{
860
+ if (!keep || element < 0 || !(navDepth > 0 || headerDepth > 0) || texts.length >= SITE_IMPORT_PAGE_LIMITS.anchors) return;
861
+ const words = clip(collapse(decodeHtmlEntities(buffer.slice(ownFrom))), 300);
862
+ if (words) texts.push({
863
+ element,
864
+ text: words
865
+ });
866
+ };
867
+ /** The elements open where the walk is, innermost last. */ const open = [];
868
+ let mainElement = -1;
869
+ let roleMain = -1;
870
+ let bodyElement = -1;
871
+ /** The element the loose words being read began in. */ let looseElement = -1;
872
+ const top = ()=>open.length ? open[open.length - 1] : -1;
516
873
  const region = ()=>footerDepth > 0 ? 'footer' : headerDepth > 0 ? 'header' : 'main';
517
874
  // A site without a <nav> keeps its menu in the header; its links are the nav.
518
875
  const inNav = ()=>navDepth > 0 || !hasNav && headerDepth > 0;
519
876
  const room = ()=>blocks.length < SITE_IMPORT_PAGE_LIMITS.blocks;
877
+ /** A block, and where its words sit. */ const push = (block, element)=>{
878
+ blocks.push(block);
879
+ if (!keep) return;
880
+ blockElement.push(element);
881
+ const owner = elements[element];
882
+ if (!owner) return;
883
+ owner.blocks.push(blocks.length - 1);
884
+ if (owner.order === undefined) owner.order = blocks.length - 1;
885
+ };
886
+ /** Pops the open elements down to the innermost `tags` one, unless a wall stands first. */ const endOpen = (tags, walls)=>{
887
+ for(let at = open.length - 1; at >= 0; at -= 1){
888
+ var _ref;
889
+ var _elements_open_at;
890
+ const tag = (_ref = (_elements_open_at = elements[open[at]]) == null ? void 0 : _elements_open_at.tag) != null ? _ref : '';
891
+ if (tags.has(tag)) {
892
+ open.length = at;
893
+ return;
894
+ }
895
+ if (walls.has(tag)) return;
896
+ }
897
+ };
898
+ const P_ONLY = new Set([
899
+ 'p'
900
+ ]);
901
+ /** An element opens: what it implies is closed first, then it is the innermost. */ const openElement = (tag, attributes, selfClosing)=>{
902
+ var _attributes_role;
903
+ var _elements_parent;
904
+ if (!keep) return -1;
905
+ if (CLOSES_P.has(tag)) endOpen(P_ONLY, P_WALLS);
906
+ const implied = IMPLIED_END[tag];
907
+ if (implied) endOpen(implied.ends, implied.walls);
908
+ const isVoid = VOID_TAGS.has(tag);
909
+ if (isVoid && tag !== 'img' && tag !== 'aglyn-svg' && tag !== 'aglyn-form') return -1;
910
+ if (elements.length >= SITE_IMPORT_PAGE_LIMITS.elements) return -1;
911
+ const id = elements.length;
912
+ const parent = top();
913
+ elements.push({
914
+ id,
915
+ tag,
916
+ parent,
917
+ children: [],
918
+ region: region(),
919
+ nav: inNav(),
920
+ attrs: elementAttrs(attributes),
921
+ blocks: []
922
+ });
923
+ (_elements_parent = elements[parent]) == null ? void 0 : _elements_parent.children.push(id);
924
+ if (!isVoid && !selfClosing) open.push(id);
925
+ if (tag === 'body' && bodyElement < 0) bodyElement = id;
926
+ if (tag === 'main' && mainElement < 0) mainElement = id;
927
+ if (roleMain < 0 && ((_attributes_role = attributes['role']) != null ? _attributes_role : '').toLowerCase() === 'main') roleMain = id;
928
+ return id;
929
+ };
930
+ /** An element closes: it and everything still open inside it. */ const closeElement = (tag)=>{
931
+ if (!keep) return;
932
+ for(let at = open.length - 1; at >= 0; at -= 1){
933
+ var _elements_open_at;
934
+ if (((_elements_open_at = elements[open[at]]) == null ? void 0 : _elements_open_at.tag) === tag) {
935
+ open.length = at;
936
+ return;
937
+ }
938
+ }
939
+ };
520
940
  const flush = ()=>{
521
- var _ref;
941
+ var _ref, _ref1;
522
942
  const raw = buffer;
523
943
  const written = pending;
944
+ const loose = current === null;
945
+ const element = (_ref = current == null ? void 0 : current.element) != null ? _ref : looseElement;
946
+ if (!anchor) takeOwnWords(element);
947
+ ownFrom = 0;
524
948
  buffer = '';
525
949
  pending = [];
950
+ looseElement = -1;
526
951
  if (anchor && collapse(decodeHtmlEntities(raw.slice(anchor.start)))) anchor.crossed = true;
527
952
  if (anchor) anchor.start = 0;
528
- const kind = (_ref = current == null ? void 0 : current.kind) != null ? _ref : 'paragraph';
953
+ const kind = (_ref1 = current == null ? void 0 : current.kind) != null ? _ref1 : 'paragraph';
529
954
  const level = current == null ? void 0 : current.level;
530
- const loose = current === null;
531
955
  current = null;
532
- if (navDepth > 0) return;
533
956
  const text = clip(collapse(decodeHtmlEntities(raw)), SITE_IMPORT_PAGE_LIMITS.blockChars);
957
+ if (navDepth > 0) return;
534
958
  // Loose words that are only links (a row of buttons, an address in a
535
959
  // card) are those links, each a block of its own; words around a link
536
960
  // keep their block, and the link follows it.
@@ -543,18 +967,18 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
543
967
  const last = blocks[blocks.length - 1];
544
968
  if (!(last && last.kind !== 'image' && last.kind !== 'link' && last.text === text)) {
545
969
  if (kind === 'heading') {
546
- blocks.push({
970
+ push({
547
971
  kind,
548
972
  level: level != null ? level : 2,
549
973
  text,
550
974
  region: region()
551
- });
975
+ }, element);
552
976
  } else {
553
- blocks.push({
977
+ push({
554
978
  kind,
555
979
  text,
556
980
  region: region()
557
- });
981
+ }, element);
558
982
  }
559
983
  }
560
984
  }
@@ -563,14 +987,14 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
563
987
  const last = blocks[blocks.length - 1];
564
988
  if ((last == null ? void 0 : last.kind) === 'link' && last.href === link.href && last.text === link.text) continue;
565
989
  const inline = !onlyLinks || link.crossed;
566
- blocks.push(_extends({
990
+ push(_extends({
567
991
  kind: 'link',
568
992
  text: link.text,
569
993
  href: link.href,
570
994
  region: link.region
571
995
  }, inline ? {
572
996
  inline: true
573
- } : {}));
997
+ } : {}), link.element);
574
998
  }
575
999
  };
576
1000
  /** A boundary between two runs of words: in the block, and in the link being read. */ const boundary = (block)=>{
@@ -579,22 +1003,9 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
579
1003
  anchor.text += RUN_BREAK;
580
1004
  if (block && anchor.lines[anchor.lines.length - 1] !== '') anchor.lines.push('');
581
1005
  };
582
- for (const match of body.matchAll(new RegExp(`${TAG.source}|([^<]+)|<`, 'g'))){
583
- var _match_;
584
- const text = match[4];
585
- if (text !== undefined || match[2] === undefined) {
586
- const run = text != null ? text : '<';
587
- buffer += run;
588
- if (anchor) {
589
- anchor.text += run;
590
- anchor.lines[anchor.lines.length - 1] += run;
591
- }
592
- continue;
593
- }
594
- const closing = match[1] === '/';
595
- const tag = match[2].toLowerCase();
596
- const attributes = closing ? {} : parseAttributes((_match_ = match[3]) != null ? _match_ : '');
1006
+ /** One tag of the page, read where the walk is. */ const read = (closing, tag, attributes, element)=>{
597
1007
  if (tag === 'a') {
1008
+ var _ref;
598
1009
  if (closing) {
599
1010
  if (anchor) {
600
1011
  var _anchor_lines_find;
@@ -620,14 +1031,33 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
620
1031
  start: anchor.start,
621
1032
  end: buffer.length,
622
1033
  crossed: anchor.crossed,
623
- region: anchor.region
1034
+ region: anchor.region,
1035
+ element: anchor.element
624
1036
  });
625
1037
  }
1038
+ if (keep && anchor.element >= 0 && anchors.length < SITE_IMPORT_PAGE_LIMITS.anchors) {
1039
+ const lines = anchor.lines.map((line)=>clip(collapse(decodeHtmlEntities(line)), 300)).filter(Boolean);
1040
+ anchors.push(_extends({
1041
+ element: anchor.element,
1042
+ href: anchor.href,
1043
+ label: firstLine,
1044
+ text: clip(lines.slice(1).join(' '), 300),
1045
+ region: anchor.region,
1046
+ nav: inNav()
1047
+ }, anchor.icon !== undefined ? {
1048
+ icon: anchor.icon
1049
+ } : {}, anchor.image ? {
1050
+ image: anchor.image.url
1051
+ } : {}));
1052
+ }
626
1053
  anchor = null;
627
1054
  }
628
1055
  boundary(false);
629
- continue;
1056
+ ownFrom = buffer.length;
1057
+ return;
630
1058
  }
1059
+ // Words before a link in a menu are its own: a column's heading.
1060
+ if (!anchor) takeOwnWords((_ref = current == null ? void 0 : current.element) != null ? _ref : looseElement >= 0 ? looseElement : top());
631
1061
  boundary(false);
632
1062
  const url = resolveSiteUrl(attributes['href'], pageUrl);
633
1063
  anchor = {
@@ -640,7 +1070,8 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
640
1070
  image: null,
641
1071
  start: buffer.length,
642
1072
  crossed: false,
643
- region: region()
1073
+ region: region(),
1074
+ element
644
1075
  };
645
1076
  if (url && isSameSite(url) && links.length < SITE_IMPORT_PAGE_LIMITS.links) {
646
1077
  const key = url.toString();
@@ -649,7 +1080,27 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
649
1080
  links.push(key);
650
1081
  }
651
1082
  }
652
- continue;
1083
+ return;
1084
+ }
1085
+ if (tag === 'aglyn-svg') {
1086
+ // An icon at its place: never words (AGL-4106).
1087
+ const index = Number(attributes['data-icon']);
1088
+ if (keep && element >= 0 && Number.isInteger(index) && svgIcons[index]) {
1089
+ const own = elements[element];
1090
+ own.icon = index;
1091
+ own.order = blocks.length - 0.5;
1092
+ if (anchor && anchor.icon === undefined) anchor.icon = index;
1093
+ }
1094
+ return;
1095
+ }
1096
+ if (tag === 'aglyn-form') {
1097
+ const index = Number(attributes['data-form']);
1098
+ if (keep && element >= 0 && Number.isInteger(index)) {
1099
+ const own = elements[element];
1100
+ own.form = index;
1101
+ own.order = blocks.length - 0.5;
1102
+ }
1103
+ return;
653
1104
  }
654
1105
  if (tag === 'img' && !closing) {
655
1106
  const url = resolveSiteUrl(attributes['src'] || attributes['data-src'] || firstSrcset(attributes['srcset'] || attributes['data-srcset']), pageUrl);
@@ -672,16 +1123,16 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
672
1123
  });
673
1124
  if (navDepth === 0) {
674
1125
  flush();
675
- if (room()) blocks.push({
1126
+ if (room()) push({
676
1127
  kind: 'image',
677
1128
  url: key,
678
1129
  alt,
679
1130
  region: region()
680
- });
1131
+ }, element);
681
1132
  }
682
1133
  }
683
1134
  }
684
- continue;
1135
+ return;
685
1136
  }
686
1137
  if (tag === 'nav' || tag === 'header' || tag === 'footer') {
687
1138
  flush();
@@ -690,41 +1141,69 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
690
1141
  if (tag === 'nav') navDepth = Math.max(0, navDepth + step);
691
1142
  if (tag === 'header') headerDepth = Math.max(0, headerDepth + step);
692
1143
  if (tag === 'footer') footerDepth = Math.max(0, footerDepth + step);
693
- continue;
1144
+ // The landmark is in its own region.
1145
+ const own = elements[element];
1146
+ if (own) {
1147
+ own.region = region();
1148
+ own.nav = inNav();
1149
+ }
1150
+ return;
694
1151
  }
695
1152
  const heading = /^h([1-6])$/.exec(tag);
696
1153
  if (heading || BLOCK_TAGS[tag]) {
697
1154
  if (closing) {
698
1155
  if ((current == null ? void 0 : current.tag) === tag) flush();
699
1156
  boundary(true);
700
- continue;
1157
+ return;
701
1158
  }
702
1159
  flush();
703
1160
  boundary(true);
704
1161
  current = heading ? {
705
1162
  kind: 'heading',
706
1163
  level: Number(heading[1]),
707
- tag
1164
+ tag,
1165
+ element
708
1166
  } : {
709
1167
  kind: BLOCK_TAGS[tag],
710
- tag
1168
+ tag,
1169
+ element
711
1170
  };
712
- continue;
1171
+ return;
713
1172
  }
714
1173
  if (BREAK_TAGS.has(tag)) {
715
1174
  if (tag === 'br' && current) {
716
1175
  buffer += ' ';
717
1176
  if (anchor) anchor.text += ' ';
718
- continue;
1177
+ return;
719
1178
  }
720
1179
  flush();
721
1180
  boundary(true);
722
- continue;
1181
+ return;
723
1182
  }
724
1183
  if (RUN_BOUNDARY.has(tag)) boundary(false);
1184
+ };
1185
+ for (const match of body.matchAll(new RegExp(`${TAG.source}|([^<]+)|<`, 'g'))){
1186
+ var _match_, _match_1;
1187
+ const text = match[4];
1188
+ if (text !== undefined || match[2] === undefined) {
1189
+ const run = text != null ? text : '<';
1190
+ buffer += run;
1191
+ if (keep && looseElement < 0 && current === null && run.trim()) looseElement = top();
1192
+ if (anchor) {
1193
+ anchor.text += run;
1194
+ anchor.lines[anchor.lines.length - 1] += run;
1195
+ }
1196
+ continue;
1197
+ }
1198
+ const closing = match[1] === '/';
1199
+ const tag = match[2].toLowerCase();
1200
+ const attributes = closing ? {} : parseAttributes((_match_ = match[3]) != null ? _match_ : '');
1201
+ const element = closing ? -1 : openElement(tag, attributes, /\/\s*$/.test((_match_1 = match[3]) != null ? _match_1 : ''));
1202
+ read(closing, tag, attributes, element);
1203
+ if (closing) closeElement(tag);
725
1204
  }
726
1205
  flush();
727
- return {
1206
+ return _extends({
728
1207
  title,
729
1208
  description,
730
1209
  lang,
@@ -743,7 +1222,20 @@ const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO
743
1222
  removed: forms.removed
744
1223
  },
745
1224
  loginForm: false
746
- };
1225
+ }, keep ? {
1226
+ structure: {
1227
+ elements,
1228
+ blockElement,
1229
+ icons: svgIcons,
1230
+ anchors,
1231
+ texts,
1232
+ styles: sheets.styles,
1233
+ stylesheets: sheets.stylesheets,
1234
+ main: mainElement >= 0 ? mainElement : roleMain,
1235
+ body: bodyElement,
1236
+ root: htmlTag ? elementAttrs(parseAttributes(htmlTag[1])) : {}
1237
+ }
1238
+ } : {});
747
1239
  }
748
1240
 
749
1241
  //# sourceMappingURL=site-import-html.js.map