@bettercms-ai/types 1.40.0 → 1.42.0
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/README.md +18 -18
- package/dist/index.d.ts +805 -42
- package/dist/index.js +1186 -18
- package/dist/index.js.map +1 -1
- package/package.json +34 -34
package/dist/index.js
CHANGED
|
@@ -70,6 +70,91 @@ function getBlockType(value) {
|
|
|
70
70
|
return null;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// src/number-format.ts
|
|
74
|
+
var GROUPS = [",", ".", " ", "\xA0", "\u202F", "'", "\u2019"];
|
|
75
|
+
var COMMA_DECIMAL = /^(de|es|it|nl|pt|id|tr|da|fr|sv|nb|no|fi|pl|cs|ru|uk|el|ro|hu|sk|sl|hr|bg)\b/i;
|
|
76
|
+
var SPACE_GROUP = /^(fr|sv|nb|no|fi|pl|cs|ru|uk|sk|hu|bg)\b/i;
|
|
77
|
+
var UNIT = "(?:[kKmMbBxsdgh]|bn|hr|hrs|min|ms|kg|mg|lb|lbs|oz|km|cm|mm|mi|ft|[kKMGT]B|px|pt|\xB0[CF]?)";
|
|
78
|
+
var SUFFIX = new RegExp(`^(?:\\s?(?:\\p{Sc}|[%\u2030\xD7]|${UNIT}))?\\+?(?:\\s?/\\s?\\p{L}{1,10}){0,2}[)\\]*\u2020]?$`, "u");
|
|
79
|
+
var PREFIX = /^(?:[^\p{L}\p{N}\s.,'\u2019#№\-−–—]{1,3}\s?)?$/u;
|
|
80
|
+
var fractionDigits = (value) => {
|
|
81
|
+
const text = value.toFixed(6).replace(/0+$/, "");
|
|
82
|
+
return text.length - text.indexOf(".") - 1;
|
|
83
|
+
};
|
|
84
|
+
function readNumberFormat(raw) {
|
|
85
|
+
const o = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
86
|
+
const text = (v, max) => typeof v === "string" && v.length <= max && !new RegExp("\\p{Nd}", "u").test(v) ? v : void 0;
|
|
87
|
+
const prefix = text(o.prefix, 8);
|
|
88
|
+
const suffix = text(o.suffix, 24);
|
|
89
|
+
const group = typeof o.group === "string" && GROUPS.includes(o.group) ? o.group : void 0;
|
|
90
|
+
const decimal = o.decimal === "," ? "," : void 0;
|
|
91
|
+
const decimals = typeof o.decimals === "number" && Number.isInteger(o.decimals) && o.decimals > 0 && o.decimals <= 6 ? o.decimals : void 0;
|
|
92
|
+
return {
|
|
93
|
+
...prefix ? { prefix } : {},
|
|
94
|
+
...suffix ? { suffix } : {},
|
|
95
|
+
// A group mark that is also the decimal mark would print a number nobody can read back.
|
|
96
|
+
...group && group !== (decimal ?? ".") ? { group } : {},
|
|
97
|
+
...decimal ? { decimal } : {},
|
|
98
|
+
...decimals ? { decimals } : {}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function formatNumber(value, format = {}) {
|
|
102
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "";
|
|
103
|
+
const places = Math.max(format.decimals ?? 0, Math.min(6, fractionDigits(value)));
|
|
104
|
+
const [int, frac] = Math.abs(value).toFixed(places).split(".");
|
|
105
|
+
const grouped = format.group ? int.replace(/\B(?=(\d{3})+(?!\d))/g, format.group) : int;
|
|
106
|
+
return `${value < 0 ? "-" : ""}${format.prefix ?? ""}${grouped}${frac ? `${format.decimal ?? "."}${frac}` : ""}${format.suffix ?? ""}`;
|
|
107
|
+
}
|
|
108
|
+
function parseNumberText(text, opts = {}) {
|
|
109
|
+
if (typeof text !== "string" || text.length === 0 || text.length > 40) return null;
|
|
110
|
+
const digits = /\d/.exec(text);
|
|
111
|
+
if (!digits) return null;
|
|
112
|
+
const start = digits.index;
|
|
113
|
+
const body = /^\d[\d.,'\u2019 \u00a0\u202f]*\d|^\d/.exec(text.slice(start))[0];
|
|
114
|
+
const prefix = text.slice(0, start);
|
|
115
|
+
const suffix = text.slice(start + body.length);
|
|
116
|
+
if (/\d/.test(suffix) || !PREFIX.test(prefix) || !SUFFIX.test(suffix)) return null;
|
|
117
|
+
if (opts.machine && (prefix || suffix || !/^\d+(\.\d+)?$/.test(body))) return null;
|
|
118
|
+
if (/[)\]]$/.test(suffix) !== /^[([]/.test(prefix)) return null;
|
|
119
|
+
const commaLang = COMMA_DECIMAL.test(opts.lang ?? "");
|
|
120
|
+
const marks = [...body].filter((c) => !/\d/.test(c));
|
|
121
|
+
const kinds = [...new Set(marks)];
|
|
122
|
+
let group;
|
|
123
|
+
let decimal;
|
|
124
|
+
if (kinds.length > 2) return null;
|
|
125
|
+
if (kinds.length === 2) {
|
|
126
|
+
const last = marks[marks.length - 1];
|
|
127
|
+
if (last !== "." && last !== "," || marks.filter((m) => m === last).length !== 1) return null;
|
|
128
|
+
decimal = last;
|
|
129
|
+
group = kinds.find((k) => k !== last);
|
|
130
|
+
} else if (kinds.length === 1) {
|
|
131
|
+
const mark = kinds[0];
|
|
132
|
+
const tail = body.slice(body.lastIndexOf(mark) + 1);
|
|
133
|
+
const lone = marks.length === 1 && (mark === "." || mark === ",");
|
|
134
|
+
const asDecimal = lone && (tail.length !== 3 || body.indexOf(mark) > 3 || /^0/.test(body) || (mark === "." ? !commaLang : commaLang));
|
|
135
|
+
if (asDecimal) decimal = mark;
|
|
136
|
+
else group = mark;
|
|
137
|
+
}
|
|
138
|
+
if (group !== void 0 && group === decimal || group && !GROUPS.includes(group)) return null;
|
|
139
|
+
const [intPart, frac = ""] = decimal ? body.split(decimal) : [body];
|
|
140
|
+
const whole = group ? intPart.split(group) : [intPart];
|
|
141
|
+
if (group && (whole.length < 2 || !/^[1-9]\d{0,2}$/.test(whole[0]) || whole.slice(1).some((g) => !/^\d{3}$/.test(g)))) return null;
|
|
142
|
+
const int = whole.join("");
|
|
143
|
+
if (!/^\d+$/.test(int) || int.length > 1 && int.startsWith("0") || int.length + frac.length > 15 || decimal && !/^\d+$/.test(frac)) return null;
|
|
144
|
+
const value = Number(frac ? `${int}.${frac}` : int);
|
|
145
|
+
if (!group && !opts.machine && int.length < 4) group = SPACE_GROUP.test(opts.lang ?? "") ? "\xA0" : commaLang ? "." : ",";
|
|
146
|
+
if (!decimal && (group === "." || commaLang && !opts.machine)) decimal = ",";
|
|
147
|
+
if (group === (decimal ?? ".")) group = void 0;
|
|
148
|
+
const format = {
|
|
149
|
+
...prefix ? { prefix } : {},
|
|
150
|
+
...suffix ? { suffix } : {},
|
|
151
|
+
...group ? { group } : {},
|
|
152
|
+
...decimal === "," ? { decimal } : {},
|
|
153
|
+
...frac ? { decimals: frac.length } : {}
|
|
154
|
+
};
|
|
155
|
+
return Number.isFinite(value) && frac.length <= 6 && formatNumber(value, format) === text ? { value, format } : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
73
158
|
// src/block-bindings.ts
|
|
74
159
|
var BLOCK_BINDINGS = {
|
|
75
160
|
heading: [{ path: "text", kind: "text" }],
|
|
@@ -406,7 +491,7 @@ function fieldStyleCssText(style) {
|
|
|
406
491
|
}
|
|
407
492
|
|
|
408
493
|
// src/component.ts
|
|
409
|
-
var SECTION_DOCTRINE = "STRUCTURE (separate from schema): a page is composed of SECTIONS. NEVER build a page out of loose top-level heading/text/image/button/spacer blocks \u2014 they cannot be moved, duplicated or swapped as a unit, the visual editor cannot outline or name them, and every one of them becomes its own section in the editor. A hero of a headline, a lede and two CTAs is ONE section, not four. TWO SHAPES, and the choice is about REUSE. (1) A band that appears on more than one page, or that needs layout variants, is a COMPONENT with a `sectionType` \u2014 see create_component. Components sharing a `sectionType` are that section's VARIANTS (one Hero: 'Centered' for the home page and 'Two-column' for about, same prop keys so a swap keeps the content). This is also the only shape the editor's 'Add a section' picker can insert, and the only one that gets a family name and a variant switcher. (2) A genuinely one-off band on a single page is a `section` BLOCK whose `props.children` hold its blocks. THE TRADEOFF, stated in the present tense because it is real today: inside a component, ONLY the leaves a declared prop TARGETS are click-to-edit on the canvas \u2014 each declared prop's `target` is re-keyed to that PLACEMENT's own override address, so editing it changes this page and not the shared definition. A prop with no `target` still SHOWS as a dock control, but its override reaches no rendered slot, so editing it changes nothing on the page. Copy that no prop points at is worse still: no binding and no control, unreachable from click-to-edit AND from the dock, changeable only by editing the component definition, which rewrites every page that places it. A `section` block's children stay click-to-edit unconditionally. So when you choose a component, DECLARE A PROP \u2014 WITH A `target` \u2014 for every string, link and image a marketer will ever touch; a component with un-propped editable copy is the defect, not the component. In the dock an unset prop shows EMPTY and inherits the definition's default, so set props explicitly when you want the current copy visible there. An IMPORTED site's components are its own (read the repo); its nav, footer and modals are the Layout. On a blank site, do not hand-write a band's JSON: start from a built-in section blueprint (list_components returns locked `builtin:*` blueprints with no projectId \u2014 hero-centered, hero-split, feature-grid-three, cta-banner and
|
|
494
|
+
var SECTION_DOCTRINE = "STRUCTURE (separate from schema): a page is composed of SECTIONS. NEVER build a page out of loose top-level heading/text/image/button/spacer blocks \u2014 they cannot be moved, duplicated or swapped as a unit, the visual editor cannot outline or name them, and every one of them becomes its own section in the editor. A hero of a headline, a lede and two CTAs is ONE section, not four. TWO SHAPES, and the choice is about REUSE. (1) A band that appears on more than one page, or that needs layout variants, is a COMPONENT with a `sectionType` \u2014 see create_component. Components sharing a `sectionType` are that section's VARIANTS (one Hero: 'Centered' for the home page and 'Two-column' for about, same prop keys so a swap keeps the content). This is also the only shape the editor's 'Add a section' picker can insert, and the only one that gets a family name and a variant switcher. (2) A genuinely one-off band on a single page is a `section` BLOCK whose `props.children` hold its blocks. THE TRADEOFF, stated in the present tense because it is real today: inside a component, ONLY the leaves a declared prop TARGETS are click-to-edit on the canvas \u2014 each declared prop's `target` is re-keyed to that PLACEMENT's own override address, so editing it changes this page and not the shared definition. A prop with no `target` still SHOWS as a dock control, but its override reaches no rendered slot, so editing it changes nothing on the page. Copy that no prop points at is worse still: no binding and no control, unreachable from click-to-edit AND from the dock, changeable only by editing the component definition, which rewrites every page that places it. A `section` block's children stay click-to-edit unconditionally. So when you choose a component, DECLARE A PROP \u2014 WITH A `target` \u2014 for every string, link and image a marketer will ever touch; a component with un-propped editable copy is the defect, not the component. In the dock an unset prop shows EMPTY and inherits the definition's default, so set props explicitly when you want the current copy visible there. An IMPORTED site's components are its own (read the repo); its nav, footer and modals are the Layout. On a blank site, do not hand-write a band's JSON: start from a built-in section blueprint (list_components returns locked `builtin:*` blueprints with no projectId \u2014 hero-centered, hero-split, feature-grid-three, cta-banner and more), each already rooted in a `section` block with its editable leaves declared as props. INLINE its blockJson as a `section` block for a one-off band; for a recurring band, materialize it with create_component `from` so it becomes project-scoped before implementation validation, Output or publication. Direct `builtin:*` component references exist only for legacy delivery compatibility. Two consecutive call-to-action buttons are two sibling `button` blocks inside the same section \u2014 never a `columns` block, which is a `repeat(N,1fr)` grid and would stretch each CTA to half the container. Buttons are inline-level and flow side by side on their own.";
|
|
410
495
|
var COMPONENT_PROP_TYPES = [
|
|
411
496
|
"text",
|
|
412
497
|
"richtext",
|
|
@@ -427,16 +512,95 @@ var COMPONENT_PROP_TYPES = [
|
|
|
427
512
|
"color",
|
|
428
513
|
"file",
|
|
429
514
|
"icon",
|
|
430
|
-
"multiselect"
|
|
515
|
+
"multiselect",
|
|
516
|
+
// V2 schema language (F1.x). `object` is ONE nested value of named `config.fields`, stored as
|
|
517
|
+
// `{ [childKey]: value }` like the model field `object`; the six after it store what the
|
|
518
|
+
// same-named content-model FIELD stores (`location` is the V2 geopoint) and are judged by
|
|
519
|
+
// that field's validator.
|
|
520
|
+
"object",
|
|
521
|
+
"datetime",
|
|
522
|
+
"slug",
|
|
523
|
+
"email",
|
|
524
|
+
"phone",
|
|
525
|
+
"json",
|
|
526
|
+
"location",
|
|
527
|
+
// V2 leaves, each storing what the same-named content-model FIELD stores: a taxonomy string[],
|
|
528
|
+
// a rating number, a code value (`{ language?, code }` or a string).
|
|
529
|
+
"tags",
|
|
530
|
+
"rating",
|
|
531
|
+
"code",
|
|
532
|
+
// CMS items, as the same-named content-model FIELDS store them: an entry id (or a polymorphic
|
|
533
|
+
// `{ entryId, modelSlug }`), and a list of them. Delivery hydrates them like an entry's.
|
|
534
|
+
"reference",
|
|
535
|
+
"multi-reference",
|
|
536
|
+
// V2 chrome types, each storing what the same-named content-model FIELD stores: a video value,
|
|
537
|
+
// a primitive list (`config.itemType`), and multi-line text (the field lane's V2 `text`).
|
|
538
|
+
"video",
|
|
539
|
+
"array",
|
|
540
|
+
"longtext"
|
|
431
541
|
];
|
|
432
542
|
var SLOT_MAX_ITEMS = 12;
|
|
433
543
|
var SLOT_ITEMS_CAP = 50;
|
|
434
544
|
function slotItems(value) {
|
|
435
545
|
const list = Array.isArray(value) ? value : [value];
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
546
|
+
const out = [];
|
|
547
|
+
for (const item of list) {
|
|
548
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
549
|
+
const o = item;
|
|
550
|
+
if (typeof o.componentId === "string" && o.componentId !== "") out.push(item);
|
|
551
|
+
else if (typeof o.componentId !== "string" && typeof o._type === "string" && o._type !== "") out.push({ componentId: o._type, overrides: o });
|
|
552
|
+
}
|
|
553
|
+
return out;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/slot-child.ts
|
|
557
|
+
var SLOT_CHILD_KEYS = ["_key", "_type", "_variant", "_settings"];
|
|
558
|
+
var RESERVED = new Set(SLOT_CHILD_KEYS);
|
|
559
|
+
var isRecord2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
560
|
+
var isSlotChild = (v) => isRecord2(v) && typeof v._type === "string" && v._type !== "";
|
|
561
|
+
function slotChildValues(child) {
|
|
562
|
+
const out = {};
|
|
563
|
+
for (const [k, v] of Object.entries(child)) if (!RESERVED.has(k)) out[k] = v;
|
|
564
|
+
return out;
|
|
565
|
+
}
|
|
566
|
+
function slotShapeProblems(value, opts = {}) {
|
|
567
|
+
const label = opts.label ?? "slot";
|
|
568
|
+
const noun = opts.typeNoun ?? "a block type";
|
|
569
|
+
if (!Array.isArray(value)) return [{ path: [], code: "slot_value_invalid", message: `slot_value_invalid: ${label} must be a list of blocks ({ _key, _type, ...values })` }];
|
|
570
|
+
const out = [];
|
|
571
|
+
if (opts.max !== void 0 && value.length > opts.max) out.push({ path: [], code: "slot_too_many", message: `slot_too_many: ${label} holds at most ${opts.max} blocks, got ${value.length}` });
|
|
572
|
+
if (opts.min !== void 0 && value.length < opts.min) out.push({ path: [], code: "slot_too_few", message: `slot_too_few: ${label} needs at least ${opts.min} blocks, got ${value.length}` });
|
|
573
|
+
const keys = /* @__PURE__ */ new Set();
|
|
574
|
+
value.forEach((child, i) => {
|
|
575
|
+
if (!isRecord2(child)) {
|
|
576
|
+
out.push({ path: [i], code: "slot_value_invalid", message: `slot_value_invalid: ${label}: block ${i + 1} must be an object { _key, _type, ...values }` });
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (typeof child._key !== "string" || child._key.trim() === "") {
|
|
580
|
+
out.push({ path: [i, "_key"], code: "slot_child_key_required", message: `slot_child_key_required: ${label}: block ${i + 1} needs a _key (a non-empty string)` });
|
|
581
|
+
} else if (keys.has(child._key)) {
|
|
582
|
+
out.push({ path: [i, "_key"], code: "slot_child_key_duplicate", message: `slot_child_key_duplicate: ${label}: _key '${child._key}' is used by two blocks` });
|
|
583
|
+
} else keys.add(child._key);
|
|
584
|
+
if (typeof child._type !== "string" || child._type.trim() === "") {
|
|
585
|
+
out.push({ path: [i, "_type"], code: "slot_child_type_required", message: `slot_child_type_required: ${label}: block ${i + 1} needs a _type (${noun})` });
|
|
586
|
+
}
|
|
587
|
+
if (child._variant !== void 0) {
|
|
588
|
+
if (!opts.variants) out.push({ path: [i, "_variant"], code: "slot_variant_unsupported", message: `slot_variant_unsupported: ${label}: a component variant is its own component; name it in _type` });
|
|
589
|
+
else if (typeof child._variant !== "string" || child._variant === "") out.push({ path: [i, "_variant"], code: "slot_value_invalid", message: `slot_value_invalid: ${label}: _variant must be a variant name` });
|
|
590
|
+
}
|
|
591
|
+
if (child._settings !== void 0) {
|
|
592
|
+
if (!opts.settings) out.push({ path: [i, "_settings"], code: "slot_settings_undeclared", message: `slot_settings_undeclared: ${label} declares no slotSettings, so a block holds no _settings` });
|
|
593
|
+
else if (!isRecord2(child._settings)) out.push({ path: [i, "_settings"], code: "slot_value_invalid", message: `slot_value_invalid: ${label}: _settings must be an object keyed by the slot's slotSettings` });
|
|
594
|
+
}
|
|
595
|
+
for (const k of Object.keys(child)) {
|
|
596
|
+
if (k.startsWith("_") && !RESERVED.has(k)) {
|
|
597
|
+
out.push({ path: [i, k], code: "slot_child_key_unknown", message: `slot_child_key_unknown: ${label}: '${k}' is not a slot key (${SLOT_CHILD_KEYS.join(", ")}); a block's values have no leading underscore` });
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
return out;
|
|
439
602
|
}
|
|
603
|
+
var slotPathString = (path) => path.map((s, i) => typeof s === "number" ? `[${s}]` : i === 0 ? s : `.${s}`).join("");
|
|
440
604
|
|
|
441
605
|
// src/layout-lucide-icons.ts
|
|
442
606
|
var LAYOUT_SECTION_ICONS = Object.freeze([
|
|
@@ -2437,6 +2601,7 @@ function visibleDeliveredLayout(layout2, now) {
|
|
|
2437
2601
|
|
|
2438
2602
|
// src/dock-icon-names.ts
|
|
2439
2603
|
var DOCK_ICON_NAMES = [
|
|
2604
|
+
"abc",
|
|
2440
2605
|
"access-denied",
|
|
2441
2606
|
"activity",
|
|
2442
2607
|
"add",
|
|
@@ -2444,14 +2609,40 @@ var DOCK_ICON_NAMES = [
|
|
|
2444
2609
|
"add-comment",
|
|
2445
2610
|
"add-document",
|
|
2446
2611
|
"add-user",
|
|
2612
|
+
"address-book",
|
|
2613
|
+
"adjustments",
|
|
2614
|
+
"adjustments-horizontal",
|
|
2615
|
+
"alarm",
|
|
2616
|
+
"alert-triangle",
|
|
2617
|
+
"align-left",
|
|
2447
2618
|
"api",
|
|
2619
|
+
"app-window",
|
|
2620
|
+
"apple",
|
|
2621
|
+
"apps",
|
|
2448
2622
|
"archive",
|
|
2623
|
+
"arrow-back-up",
|
|
2449
2624
|
"arrow-down",
|
|
2625
|
+
"arrow-guide",
|
|
2450
2626
|
"arrow-left",
|
|
2451
2627
|
"arrow-right",
|
|
2452
2628
|
"arrow-top-right",
|
|
2453
2629
|
"arrow-up",
|
|
2630
|
+
"arrows-diagonal",
|
|
2631
|
+
"arrows-down",
|
|
2632
|
+
"arrows-exchange",
|
|
2633
|
+
"arrows-horizontal",
|
|
2634
|
+
"arrows-left-right",
|
|
2635
|
+
"arrows-shuffle",
|
|
2636
|
+
"arrows-sort",
|
|
2637
|
+
"article",
|
|
2638
|
+
"aspect-ratio",
|
|
2454
2639
|
"asterisk",
|
|
2640
|
+
"award",
|
|
2641
|
+
"axis-x",
|
|
2642
|
+
"backpack",
|
|
2643
|
+
"badge",
|
|
2644
|
+
"badge-cc",
|
|
2645
|
+
"badges",
|
|
2455
2646
|
"bar-chart",
|
|
2456
2647
|
"basket",
|
|
2457
2648
|
"bell",
|
|
@@ -2463,25 +2654,76 @@ var DOCK_ICON_NAMES = [
|
|
|
2463
2654
|
"bold",
|
|
2464
2655
|
"bolt",
|
|
2465
2656
|
"book",
|
|
2657
|
+
"book-2",
|
|
2466
2658
|
"bookmark",
|
|
2467
2659
|
"bookmark-filled",
|
|
2660
|
+
"books",
|
|
2468
2661
|
"bottle",
|
|
2662
|
+
"box",
|
|
2663
|
+
"brand-dribbble",
|
|
2664
|
+
"brand-facebook",
|
|
2665
|
+
"brand-instagram",
|
|
2666
|
+
"brand-linkedin",
|
|
2667
|
+
"brand-pinterest",
|
|
2668
|
+
"brand-tiktok",
|
|
2669
|
+
"brand-x",
|
|
2670
|
+
"brand-youtube",
|
|
2671
|
+
"briefcase",
|
|
2672
|
+
"broadcast",
|
|
2673
|
+
"browser",
|
|
2469
2674
|
"bug",
|
|
2675
|
+
"building",
|
|
2676
|
+
"building-skyscraper",
|
|
2677
|
+
"building-store",
|
|
2470
2678
|
"bulb-filled",
|
|
2471
2679
|
"bulb-outline",
|
|
2680
|
+
"calculator",
|
|
2472
2681
|
"calendar",
|
|
2682
|
+
"calendar-dollar",
|
|
2683
|
+
"calendar-event",
|
|
2684
|
+
"calendar-exclamation",
|
|
2685
|
+
"calendar-off",
|
|
2686
|
+
"calendar-plus",
|
|
2687
|
+
"calendar-star",
|
|
2688
|
+
"calendar-time",
|
|
2689
|
+
"camera",
|
|
2690
|
+
"campfire",
|
|
2691
|
+
"carousel-horizontal",
|
|
2473
2692
|
"case",
|
|
2693
|
+
"cash",
|
|
2694
|
+
"category",
|
|
2695
|
+
"category-2",
|
|
2696
|
+
"chart-area",
|
|
2697
|
+
"chart-arrows",
|
|
2698
|
+
"chart-bar",
|
|
2699
|
+
"chart-bar-popular",
|
|
2700
|
+
"chart-donut",
|
|
2701
|
+
"chart-dots",
|
|
2702
|
+
"chart-line",
|
|
2474
2703
|
"chart-upward",
|
|
2704
|
+
"check",
|
|
2705
|
+
"checkbox",
|
|
2706
|
+
"checklist",
|
|
2475
2707
|
"checkmark",
|
|
2476
2708
|
"checkmark-circle",
|
|
2709
|
+
"chef-hat",
|
|
2477
2710
|
"chevron-down",
|
|
2478
2711
|
"chevron-left",
|
|
2479
2712
|
"chevron-right",
|
|
2480
2713
|
"chevron-up",
|
|
2714
|
+
"chevrons-right",
|
|
2481
2715
|
"circle",
|
|
2716
|
+
"circle-check",
|
|
2717
|
+
"circle-dot",
|
|
2718
|
+
"circle-number-1",
|
|
2719
|
+
"circle-plus",
|
|
2720
|
+
"click",
|
|
2482
2721
|
"clipboard",
|
|
2483
2722
|
"clipboard-image",
|
|
2723
|
+
"clipboard-text",
|
|
2484
2724
|
"clock",
|
|
2725
|
+
"clock-check",
|
|
2726
|
+
"clock-hour-4",
|
|
2485
2727
|
"close",
|
|
2486
2728
|
"close-circle",
|
|
2487
2729
|
"code",
|
|
@@ -2489,20 +2731,31 @@ var DOCK_ICON_NAMES = [
|
|
|
2489
2731
|
"cog",
|
|
2490
2732
|
"collapse",
|
|
2491
2733
|
"color-wheel",
|
|
2734
|
+
"columns",
|
|
2735
|
+
"columns-2",
|
|
2736
|
+
"columns-3",
|
|
2492
2737
|
"comment",
|
|
2493
2738
|
"component",
|
|
2494
2739
|
"compose",
|
|
2495
2740
|
"compose-sparkles",
|
|
2496
2741
|
"confetti",
|
|
2497
2742
|
"controls",
|
|
2743
|
+
"cookie",
|
|
2498
2744
|
"copy",
|
|
2499
2745
|
"credit-card",
|
|
2500
2746
|
"crop",
|
|
2501
2747
|
"cube",
|
|
2748
|
+
"currency-dollar",
|
|
2502
2749
|
"dashboard",
|
|
2503
2750
|
"database",
|
|
2751
|
+
"database-search",
|
|
2504
2752
|
"desktop",
|
|
2753
|
+
"device-desktop",
|
|
2754
|
+
"device-mobile",
|
|
2755
|
+
"device-tablet",
|
|
2756
|
+
"devices",
|
|
2505
2757
|
"diamond",
|
|
2758
|
+
"discount",
|
|
2506
2759
|
"document",
|
|
2507
2760
|
"document-pdf",
|
|
2508
2761
|
"document-remove",
|
|
@@ -2512,6 +2765,7 @@ var DOCK_ICON_NAMES = [
|
|
|
2512
2765
|
"document-word",
|
|
2513
2766
|
"document-zip",
|
|
2514
2767
|
"documents",
|
|
2768
|
+
"door-exit",
|
|
2515
2769
|
"dot",
|
|
2516
2770
|
"double-chevron-down",
|
|
2517
2771
|
"double-chevron-left",
|
|
@@ -2521,9 +2775,12 @@ var DOCK_ICON_NAMES = [
|
|
|
2521
2775
|
"download",
|
|
2522
2776
|
"drag-handle",
|
|
2523
2777
|
"drop",
|
|
2778
|
+
"droplet",
|
|
2779
|
+
"droplet-half",
|
|
2524
2780
|
"earth-americas",
|
|
2525
2781
|
"earth-globe",
|
|
2526
2782
|
"edit",
|
|
2783
|
+
"egg",
|
|
2527
2784
|
"ellipsis-horizontal",
|
|
2528
2785
|
"ellipsis-vertical",
|
|
2529
2786
|
"empty",
|
|
@@ -2536,77 +2793,169 @@ var DOCK_ICON_NAMES = [
|
|
|
2536
2793
|
"error-screen",
|
|
2537
2794
|
"expand",
|
|
2538
2795
|
"eye-closed",
|
|
2796
|
+
"eye-off",
|
|
2539
2797
|
"eye-open",
|
|
2540
2798
|
"face-happy",
|
|
2541
2799
|
"face-indifferent",
|
|
2542
2800
|
"face-sad",
|
|
2801
|
+
"feather",
|
|
2543
2802
|
"feedback",
|
|
2803
|
+
"file",
|
|
2804
|
+
"file-certificate",
|
|
2805
|
+
"file-code",
|
|
2806
|
+
"file-download",
|
|
2807
|
+
"file-text",
|
|
2544
2808
|
"filter",
|
|
2809
|
+
"flag",
|
|
2810
|
+
"flame",
|
|
2811
|
+
"flask",
|
|
2812
|
+
"focus-2",
|
|
2545
2813
|
"folder",
|
|
2814
|
+
"folders",
|
|
2815
|
+
"forms",
|
|
2816
|
+
"frame",
|
|
2546
2817
|
"generate",
|
|
2818
|
+
"gift",
|
|
2819
|
+
"git-branch",
|
|
2547
2820
|
"github",
|
|
2821
|
+
"grid-dots",
|
|
2548
2822
|
"groq",
|
|
2823
|
+
"hammer",
|
|
2824
|
+
"hand-finger",
|
|
2825
|
+
"hanger",
|
|
2549
2826
|
"hash",
|
|
2827
|
+
"headphones",
|
|
2828
|
+
"headset",
|
|
2550
2829
|
"heart",
|
|
2551
2830
|
"heart-filled",
|
|
2831
|
+
"heart-handshake",
|
|
2552
2832
|
"help-circle",
|
|
2553
2833
|
"highlight",
|
|
2834
|
+
"history",
|
|
2554
2835
|
"home",
|
|
2836
|
+
"hourglass",
|
|
2555
2837
|
"ice-cream",
|
|
2838
|
+
"id-badge",
|
|
2839
|
+
"id-badge-2",
|
|
2556
2840
|
"image",
|
|
2557
2841
|
"image-remove",
|
|
2558
2842
|
"images",
|
|
2559
2843
|
"inbox",
|
|
2844
|
+
"infinity",
|
|
2845
|
+
"info-circle",
|
|
2560
2846
|
"info-filled",
|
|
2561
2847
|
"info-outline",
|
|
2848
|
+
"info-square-rounded",
|
|
2562
2849
|
"inline",
|
|
2563
2850
|
"inline-element",
|
|
2851
|
+
"input-search",
|
|
2564
2852
|
"insert-above",
|
|
2565
2853
|
"insert-below",
|
|
2566
2854
|
"italic",
|
|
2567
2855
|
"joystick",
|
|
2568
2856
|
"json",
|
|
2857
|
+
"language",
|
|
2569
2858
|
"launch",
|
|
2859
|
+
"layout",
|
|
2860
|
+
"layout-align-center",
|
|
2861
|
+
"layout-board",
|
|
2862
|
+
"layout-board-split",
|
|
2863
|
+
"layout-bottombar",
|
|
2864
|
+
"layout-bottombar-expand",
|
|
2865
|
+
"layout-cards",
|
|
2866
|
+
"layout-columns",
|
|
2867
|
+
"layout-dashboard",
|
|
2868
|
+
"layout-grid",
|
|
2869
|
+
"layout-list",
|
|
2870
|
+
"layout-navbar",
|
|
2871
|
+
"layout-rows",
|
|
2872
|
+
"layout-sidebar",
|
|
2873
|
+
"layout-sidebar-right",
|
|
2874
|
+
"leaf",
|
|
2570
2875
|
"leave",
|
|
2571
2876
|
"lemon",
|
|
2877
|
+
"library-photo",
|
|
2878
|
+
"lifebuoy",
|
|
2572
2879
|
"link",
|
|
2573
2880
|
"link-removed",
|
|
2574
2881
|
"linkedin",
|
|
2575
2882
|
"list",
|
|
2883
|
+
"list-check",
|
|
2884
|
+
"list-details",
|
|
2885
|
+
"list-numbers",
|
|
2886
|
+
"list-tree",
|
|
2576
2887
|
"lock",
|
|
2577
2888
|
"logo-js",
|
|
2578
2889
|
"logo-ts",
|
|
2890
|
+
"mail",
|
|
2891
|
+
"map-2",
|
|
2892
|
+
"map-pin",
|
|
2893
|
+
"map-pins",
|
|
2579
2894
|
"marker",
|
|
2580
2895
|
"marker-removed",
|
|
2581
2896
|
"master-detail",
|
|
2897
|
+
"math-function",
|
|
2898
|
+
"meat",
|
|
2582
2899
|
"menu",
|
|
2900
|
+
"message",
|
|
2901
|
+
"message-2",
|
|
2902
|
+
"message-check",
|
|
2903
|
+
"message-circle",
|
|
2904
|
+
"message-circle-2",
|
|
2905
|
+
"message-plus",
|
|
2906
|
+
"message-star",
|
|
2583
2907
|
"microphone",
|
|
2584
2908
|
"microphone-slash",
|
|
2585
2909
|
"mobile-device",
|
|
2910
|
+
"mood-empty",
|
|
2586
2911
|
"moon",
|
|
2912
|
+
"movie",
|
|
2913
|
+
"news",
|
|
2914
|
+
"notes",
|
|
2587
2915
|
"number",
|
|
2588
2916
|
"ok-hand",
|
|
2589
2917
|
"olist",
|
|
2590
2918
|
"overage",
|
|
2591
2919
|
"package",
|
|
2920
|
+
"packages",
|
|
2921
|
+
"palette",
|
|
2592
2922
|
"panel-left",
|
|
2593
2923
|
"panel-right",
|
|
2924
|
+
"paperclip",
|
|
2594
2925
|
"pause",
|
|
2926
|
+
"pencil",
|
|
2927
|
+
"phone",
|
|
2928
|
+
"photo",
|
|
2929
|
+
"photo-scan",
|
|
2930
|
+
"photo-search",
|
|
2931
|
+
"pig-money",
|
|
2595
2932
|
"pin",
|
|
2596
2933
|
"pin-filled",
|
|
2597
2934
|
"pin-removed",
|
|
2598
2935
|
"play",
|
|
2936
|
+
"player-play",
|
|
2937
|
+
"player-track-next",
|
|
2938
|
+
"playlist-add",
|
|
2599
2939
|
"plug",
|
|
2940
|
+
"plug-connected",
|
|
2941
|
+
"plus-minus",
|
|
2942
|
+
"point",
|
|
2600
2943
|
"presentation",
|
|
2944
|
+
"printer",
|
|
2601
2945
|
"progress-50",
|
|
2602
2946
|
"progress-75",
|
|
2603
2947
|
"projects",
|
|
2604
2948
|
"publish",
|
|
2949
|
+
"qrcode",
|
|
2950
|
+
"quote",
|
|
2605
2951
|
"read-only",
|
|
2952
|
+
"receipt",
|
|
2953
|
+
"recycle",
|
|
2606
2954
|
"redo",
|
|
2607
2955
|
"refresh",
|
|
2608
2956
|
"remove",
|
|
2609
2957
|
"remove-circle",
|
|
2958
|
+
"repeat",
|
|
2610
2959
|
"reset",
|
|
2611
2960
|
"restore",
|
|
2612
2961
|
"retrieve",
|
|
@@ -2614,49 +2963,90 @@ var DOCK_ICON_NAMES = [
|
|
|
2614
2963
|
"revert",
|
|
2615
2964
|
"robot",
|
|
2616
2965
|
"rocket",
|
|
2966
|
+
"rosette",
|
|
2967
|
+
"route",
|
|
2968
|
+
"ruler",
|
|
2969
|
+
"ruler-measure",
|
|
2970
|
+
"salad",
|
|
2971
|
+
"scale",
|
|
2617
2972
|
"schema",
|
|
2973
|
+
"school",
|
|
2618
2974
|
"search",
|
|
2975
|
+
"section",
|
|
2619
2976
|
"select",
|
|
2977
|
+
"send",
|
|
2978
|
+
"server",
|
|
2979
|
+
"settings",
|
|
2620
2980
|
"share",
|
|
2981
|
+
"shield-check",
|
|
2982
|
+
"shield-lock",
|
|
2983
|
+
"shopping-bag",
|
|
2984
|
+
"shopping-cart",
|
|
2985
|
+
"shopping-cart-plus",
|
|
2986
|
+
"signature",
|
|
2987
|
+
"sitemap",
|
|
2988
|
+
"slideshow",
|
|
2621
2989
|
"sort",
|
|
2990
|
+
"sort-descending",
|
|
2991
|
+
"spacing-vertical",
|
|
2622
2992
|
"sparkle",
|
|
2623
2993
|
"sparkles",
|
|
2994
|
+
"speakerphone",
|
|
2624
2995
|
"spinner",
|
|
2625
2996
|
"split-horizontal",
|
|
2626
2997
|
"split-vertical",
|
|
2627
2998
|
"square",
|
|
2628
2999
|
"stack",
|
|
3000
|
+
"stack-2",
|
|
2629
3001
|
"stack-compact",
|
|
3002
|
+
"stairs",
|
|
3003
|
+
"stairs-up",
|
|
2630
3004
|
"star",
|
|
2631
3005
|
"star-filled",
|
|
3006
|
+
"stars",
|
|
2632
3007
|
"stop",
|
|
2633
3008
|
"strikethrough",
|
|
2634
3009
|
"string",
|
|
3010
|
+
"subtask",
|
|
2635
3011
|
"sun",
|
|
3012
|
+
"superscript",
|
|
2636
3013
|
"sync",
|
|
3014
|
+
"table",
|
|
3015
|
+
"table-options",
|
|
2637
3016
|
"tablet-device",
|
|
2638
3017
|
"tag",
|
|
2639
3018
|
"tags",
|
|
2640
3019
|
"target",
|
|
2641
3020
|
"task",
|
|
3021
|
+
"template",
|
|
3022
|
+
"tent",
|
|
2642
3023
|
"terminal",
|
|
3024
|
+
"terminal-2",
|
|
2643
3025
|
"text",
|
|
2644
3026
|
"th-large",
|
|
2645
3027
|
"th-list",
|
|
3028
|
+
"thumb-up",
|
|
2646
3029
|
"thumbs-down",
|
|
2647
3030
|
"thumbs-up",
|
|
3031
|
+
"ticket",
|
|
2648
3032
|
"tiers",
|
|
2649
3033
|
"timeline",
|
|
2650
3034
|
"toggle-arrow-right",
|
|
3035
|
+
"toggle-right",
|
|
2651
3036
|
"token",
|
|
3037
|
+
"tool",
|
|
3038
|
+
"tools-kitchen-2",
|
|
2652
3039
|
"transfer",
|
|
2653
3040
|
"translate",
|
|
2654
3041
|
"trash",
|
|
2655
3042
|
"trend-upward",
|
|
2656
3043
|
"triangle-outline",
|
|
2657
3044
|
"trolley",
|
|
3045
|
+
"truck",
|
|
3046
|
+
"truck-delivery",
|
|
2658
3047
|
"truncate",
|
|
2659
3048
|
"twitter",
|
|
3049
|
+
"typography",
|
|
2660
3050
|
"ulist",
|
|
2661
3051
|
"unarchive",
|
|
2662
3052
|
"underline",
|
|
@@ -2667,12 +3057,22 @@ var DOCK_ICON_NAMES = [
|
|
|
2667
3057
|
"unpublish",
|
|
2668
3058
|
"upload",
|
|
2669
3059
|
"user",
|
|
3060
|
+
"user-circle",
|
|
3061
|
+
"user-plus",
|
|
3062
|
+
"user-search",
|
|
3063
|
+
"user-star",
|
|
2670
3064
|
"users",
|
|
3065
|
+
"users-group",
|
|
2671
3066
|
"versions",
|
|
2672
3067
|
"video",
|
|
3068
|
+
"vocabulary",
|
|
3069
|
+
"vs",
|
|
2673
3070
|
"warning-filled",
|
|
2674
3071
|
"warning-outline",
|
|
2675
|
-
"
|
|
3072
|
+
"weight",
|
|
3073
|
+
"world",
|
|
3074
|
+
"wrench",
|
|
3075
|
+
"zoom-question"
|
|
2676
3076
|
];
|
|
2677
3077
|
var DOCK_ICON_NAME_SET = new Set(DOCK_ICON_NAMES);
|
|
2678
3078
|
|
|
@@ -2683,12 +3083,12 @@ function listItems(f) {
|
|
|
2683
3083
|
if (f.type === "repeater") return f.fields ?? [];
|
|
2684
3084
|
if (f.type === "table") return c.fields ?? f.fields ?? [];
|
|
2685
3085
|
if (f.type === "array" && c.zones?.repeatable) return c.zones.repeatable.fields ?? [];
|
|
2686
|
-
if (f.type === "modular") return null;
|
|
3086
|
+
if (f.type === "modular" || f.type === "slot") return null;
|
|
2687
3087
|
return void 0;
|
|
2688
3088
|
}
|
|
2689
3089
|
function zoneFields(f) {
|
|
2690
3090
|
const c = f.config ?? {};
|
|
2691
|
-
if (f.type === "group") return c.fields ?? f.fields ?? [];
|
|
3091
|
+
if (f.type === "group" || f.type === "object") return c.fields ?? f.fields ?? [];
|
|
2692
3092
|
if (f.type === "array" && c.zones?.nonRepeatable) return c.zones.nonRepeatable;
|
|
2693
3093
|
return void 0;
|
|
2694
3094
|
}
|
|
@@ -2699,7 +3099,7 @@ function layout(fields, fieldsets, root) {
|
|
|
2699
3099
|
const nonEmpty = new Set(
|
|
2700
3100
|
fields.map((f) => tabOf(byId.get(f.fieldsetId ?? ""))).filter((t) => t && tabIds.includes(t))
|
|
2701
3101
|
);
|
|
2702
|
-
const
|
|
3102
|
+
const strip2 = nonEmpty.size >= 2;
|
|
2703
3103
|
const branches = /* @__PURE__ */ new Map();
|
|
2704
3104
|
const nodeFor = (f, path) => {
|
|
2705
3105
|
const key = `${path}${f.key}`;
|
|
@@ -2720,12 +3120,12 @@ function layout(fields, fieldsets, root) {
|
|
|
2720
3120
|
return { kind: "leaf", key };
|
|
2721
3121
|
};
|
|
2722
3122
|
const pinned = [];
|
|
2723
|
-
const panels = new Map(
|
|
3123
|
+
const panels = new Map(strip2 ? tabIds.filter((t) => nonEmpty.has(t)).map((t) => [t, []]) : []);
|
|
2724
3124
|
const containers = /* @__PURE__ */ new Map();
|
|
2725
3125
|
for (const f of fields) {
|
|
2726
3126
|
const node = nodeFor(f, "");
|
|
2727
3127
|
const set = byId.get(f.fieldsetId ?? "");
|
|
2728
|
-
const target =
|
|
3128
|
+
const target = strip2 && panels.get(tabOf(set) ?? "") || pinned;
|
|
2729
3129
|
if (set?.kind !== "group" && set?.kind !== "row") {
|
|
2730
3130
|
target.push(node);
|
|
2731
3131
|
continue;
|
|
@@ -2927,12 +3327,101 @@ function buttonLinkPairs(candidates) {
|
|
|
2927
3327
|
|
|
2928
3328
|
// src/button-look.ts
|
|
2929
3329
|
import { ICON_MARKUP, iconSvg } from "@bettercms-ai/icons/markup";
|
|
3330
|
+
|
|
3331
|
+
// src/link-value.ts
|
|
3332
|
+
var LINK_RELS = ["nofollow", "sponsored", "ugc"];
|
|
3333
|
+
var LINK_VALUE_TYPES = ["url", "page", "entry", "email", "phone", "anchor", "section", "file"];
|
|
3334
|
+
var isPlainObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
3335
|
+
function isLinkObject(value) {
|
|
3336
|
+
return isPlainObject(value) && typeof value.href === "string";
|
|
3337
|
+
}
|
|
3338
|
+
function linkHref(value) {
|
|
3339
|
+
if (typeof value === "string") return value;
|
|
3340
|
+
return isLinkObject(value) ? value.href : null;
|
|
3341
|
+
}
|
|
3342
|
+
function linkRefOf(value) {
|
|
3343
|
+
if (!isLinkObject(value) || value.type !== "page" && value.type !== "entry") return null;
|
|
3344
|
+
const id = value.ref ?? (value.type === "page" ? value.pageId : value.entryId);
|
|
3345
|
+
return typeof id === "string" && id.length > 0 ? { kind: value.type, id } : null;
|
|
3346
|
+
}
|
|
3347
|
+
var isScriptHref = (href) => /^(javascript|data|vbscript):/i.test(href.replace(/[\u0000- \u007F]/g, ""));
|
|
3348
|
+
var newTabOf = (v) => v.newTab === true || v.newTab === void 0 && v.target === "_blank";
|
|
3349
|
+
function linkValueProblem(value) {
|
|
3350
|
+
if (!isPlainObject(value)) return "link_value_invalid: must be an href string or a link object { type, href, \u2026 }";
|
|
3351
|
+
const v = value;
|
|
3352
|
+
if (typeof v.href !== "string") return "link_value_invalid: needs an href string";
|
|
3353
|
+
if (isScriptHref(v.href)) return "link_value_invalid: script links (javascript:, data:, vbscript:) aren't allowed";
|
|
3354
|
+
if (v.type !== void 0 && !LINK_VALUE_TYPES.includes(v.type)) {
|
|
3355
|
+
return `link_value_invalid: unknown type "${String(v.type)}" (${LINK_VALUE_TYPES.join(", ")})`;
|
|
3356
|
+
}
|
|
3357
|
+
for (const key of ["title", "section", "ref"]) {
|
|
3358
|
+
if (v[key] !== void 0 && typeof v[key] !== "string") return `link_value_invalid: ${key} must be a string`;
|
|
3359
|
+
}
|
|
3360
|
+
if (v.ref !== void 0 && v.type !== "page" && v.type !== "entry") return "link_value_invalid: ref is the target of a page or entry link";
|
|
3361
|
+
if (v.newTab !== void 0 && typeof v.newTab !== "boolean") return "link_value_invalid: newTab must be true or false";
|
|
3362
|
+
if (v.target !== void 0 && v.target !== "_self" && v.target !== "_blank") return 'link_value_invalid: target must be "_self" or "_blank"';
|
|
3363
|
+
if (typeof v.newTab === "boolean" && v.target !== void 0 && v.newTab !== (v.target === "_blank")) {
|
|
3364
|
+
return "link_value_invalid: newTab and target disagree";
|
|
3365
|
+
}
|
|
3366
|
+
if (v.rel !== void 0 && !LINK_RELS.includes(v.rel)) {
|
|
3367
|
+
return `link_value_invalid: unknown rel "${String(v.rel)}" (nofollow, sponsored or ugc)`;
|
|
3368
|
+
}
|
|
3369
|
+
return null;
|
|
3370
|
+
}
|
|
3371
|
+
function normalizeLinkValue(value) {
|
|
3372
|
+
if (!isLinkObject(value)) return value;
|
|
3373
|
+
const out = { ...value };
|
|
3374
|
+
delete out.resolved;
|
|
3375
|
+
if (out.rel === "follow") delete out.rel;
|
|
3376
|
+
if (out.newTab === void 0 && out.target !== void 0) out.newTab = out.target === "_blank";
|
|
3377
|
+
const ref = linkRefOf(out);
|
|
3378
|
+
if (ref && out.ref === void 0) out.ref = ref.id;
|
|
3379
|
+
return out;
|
|
3380
|
+
}
|
|
3381
|
+
function linkAnchorOf(value) {
|
|
3382
|
+
if (typeof value === "string") return { href: value, newTab: false };
|
|
3383
|
+
if (!isLinkObject(value)) return null;
|
|
3384
|
+
return {
|
|
3385
|
+
href: value.href,
|
|
3386
|
+
newTab: newTabOf(value),
|
|
3387
|
+
...LINK_RELS.includes(value.rel) ? { rel: value.rel } : {}
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
// src/button-look.ts
|
|
2930
3392
|
var BUTTON_VARIANTS = ["primary", "secondary", "tertiary", "brand", "dark", "light", "outline", "link"];
|
|
2931
3393
|
var BUTTON_ICONS = ICON_MARKUP;
|
|
2932
3394
|
var BUTTON_ICON_NAMES = DOCK_ICON_NAMES;
|
|
2933
3395
|
function buttonVariantOf(value) {
|
|
2934
3396
|
return BUTTON_VARIANTS.includes(value) ? value : "primary";
|
|
2935
3397
|
}
|
|
3398
|
+
var SITE_CLASS = /^[^\s"'<>`=]{1,64}$/;
|
|
3399
|
+
var LOOK_VALUE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
3400
|
+
function buttonSiteLookOf(value) {
|
|
3401
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
3402
|
+
const v = value;
|
|
3403
|
+
const tokens = (list) => Array.isArray(list) ? list.filter((c) => typeof c === "string" && SITE_CLASS.test(c)) : [];
|
|
3404
|
+
const variant = v.attrs?.["data-variant"];
|
|
3405
|
+
const classes = tokens(v.classes);
|
|
3406
|
+
const also = tokens(v.also);
|
|
3407
|
+
const size = tokens(v.size);
|
|
3408
|
+
const sizeAlso = tokens(v.sizeAlso);
|
|
3409
|
+
const attrs = typeof variant === "string" && LOOK_VALUE.test(variant) ? { "data-variant": variant } : void 0;
|
|
3410
|
+
if (classes.length === 0 && !attrs) return null;
|
|
3411
|
+
return {
|
|
3412
|
+
classes,
|
|
3413
|
+
...attrs ? { attrs } : {},
|
|
3414
|
+
...also.length ? { also } : {},
|
|
3415
|
+
...size.length ? { size } : {},
|
|
3416
|
+
...sizeAlso.length ? { sizeAlso } : {}
|
|
3417
|
+
};
|
|
3418
|
+
}
|
|
3419
|
+
function buttonLookValue(slug, siteLook) {
|
|
3420
|
+
return siteLook?.attrs?.["data-variant"] ?? slug;
|
|
3421
|
+
}
|
|
3422
|
+
function isButtonLookValue(value) {
|
|
3423
|
+
return typeof value === "string" && LOOK_VALUE.test(value);
|
|
3424
|
+
}
|
|
2936
3425
|
function buttonIconOf(value) {
|
|
2937
3426
|
return typeof value === "string" && DOCK_ICON_NAME_SET.has(value) ? value : null;
|
|
2938
3427
|
}
|
|
@@ -2940,14 +3429,15 @@ function buttonIconSvg(name, cls) {
|
|
|
2940
3429
|
return iconSvg(name, cls);
|
|
2941
3430
|
}
|
|
2942
3431
|
var BUTTON_SIZES = ["sm", "md", "lg"];
|
|
2943
|
-
var LINK_RELS = ["nofollow", "sponsored", "ugc"];
|
|
2944
3432
|
function anchorTargetRel(newTab, rel) {
|
|
2945
3433
|
const tokens = [...newTab ? ["noopener", "noreferrer"] : [], ...LINK_RELS.includes(rel) ? [rel] : []];
|
|
2946
3434
|
return `${newTab ? ' target="_blank"' : ""}${tokens.length ? ` rel="${tokens.join(" ")}"` : ""}`;
|
|
2947
3435
|
}
|
|
2948
|
-
var hrefOf =
|
|
3436
|
+
var hrefOf = linkHref;
|
|
2949
3437
|
function readButtonValue(value) {
|
|
2950
3438
|
const v = value && typeof value === "object" ? value : {};
|
|
3439
|
+
const link = isLinkObject(v.href) ? linkAnchorOf(v.href) : null;
|
|
3440
|
+
const rel = v.rel ?? link?.rel;
|
|
2951
3441
|
return {
|
|
2952
3442
|
label: typeof v.label === "string" ? v.label : "",
|
|
2953
3443
|
href: hrefOf(v.href) ?? "",
|
|
@@ -2955,16 +3445,22 @@ function readButtonValue(value) {
|
|
|
2955
3445
|
size: BUTTON_SIZES.includes(v.size) ? v.size : "md",
|
|
2956
3446
|
arrow: v.arrow === true,
|
|
2957
3447
|
icon: buttonIconOf(v.icon),
|
|
2958
|
-
newTab: v.newTab === true,
|
|
2959
|
-
...LINK_RELS.includes(
|
|
3448
|
+
newTab: v.newTab === void 0 ? link?.newTab === true : v.newTab === true,
|
|
3449
|
+
...LINK_RELS.includes(rel) ? { rel } : {}
|
|
2960
3450
|
};
|
|
2961
3451
|
}
|
|
2962
|
-
function buttonValueProblem(value) {
|
|
3452
|
+
function buttonValueProblem(value, looks) {
|
|
2963
3453
|
if (!value || typeof value !== "object" || Array.isArray(value)) return "must be a { label, href } button";
|
|
2964
3454
|
const v = value;
|
|
2965
3455
|
if (typeof v.label !== "string") return "needs a label";
|
|
2966
3456
|
if (hrefOf(v.href) === null) return "needs an href";
|
|
2967
|
-
if (
|
|
3457
|
+
if (isLinkObject(v.href)) {
|
|
3458
|
+
const problem = linkValueProblem(v.href);
|
|
3459
|
+
if (problem) return `href ${problem}`;
|
|
3460
|
+
}
|
|
3461
|
+
if (v.variant !== void 0 && !BUTTON_VARIANTS.includes(v.variant) && !(typeof v.variant === "string" && looks?.has(v.variant))) {
|
|
3462
|
+
return `has an unknown variant "${String(v.variant)}"${looks?.size ? ` (this project's looks: ${[...looks].join(", ")})` : ""}`;
|
|
3463
|
+
}
|
|
2968
3464
|
if (v.size !== void 0 && !BUTTON_SIZES.includes(v.size)) return `has an unknown size "${String(v.size)}"`;
|
|
2969
3465
|
if (v.arrow !== void 0 && typeof v.arrow !== "boolean") return "arrow must be true or false";
|
|
2970
3466
|
if (v.newTab !== void 0 && typeof v.newTab !== "boolean") return "newTab must be true or false";
|
|
@@ -2979,7 +3475,14 @@ function buttonBlockProps(value) {
|
|
|
2979
3475
|
const out = {};
|
|
2980
3476
|
if (typeof v.label === "string") out.text = v.label;
|
|
2981
3477
|
const href = hrefOf(v.href);
|
|
2982
|
-
|
|
3478
|
+
const script = href !== null && isScriptHref(href);
|
|
3479
|
+
if (href !== null) out.href = script ? "#" : href;
|
|
3480
|
+
if (isLinkObject(v.href) && !script) {
|
|
3481
|
+
const link = linkAnchorOf(v.href);
|
|
3482
|
+
out.newTab = link.newTab;
|
|
3483
|
+
out.rel = link.rel;
|
|
3484
|
+
}
|
|
3485
|
+
if (v.rel !== void 0) out.rel = v.rel;
|
|
2983
3486
|
for (const key of BUTTON_LOOK_KEYS) if (key in v) out[key] = v[key];
|
|
2984
3487
|
return out;
|
|
2985
3488
|
}
|
|
@@ -3032,6 +3535,537 @@ function buttonValueOfInstance(def, overrides) {
|
|
|
3032
3535
|
return readButtonValue({ ...props, label: props.text });
|
|
3033
3536
|
}
|
|
3034
3537
|
|
|
3538
|
+
// src/video-value.ts
|
|
3539
|
+
var VIDEO_VALUE_KEYS = ["url", "poster", "autoplay", "loop", "transcript"];
|
|
3540
|
+
var VIDEO_TRANSCRIPT_MAX = 1e5;
|
|
3541
|
+
var isPlainObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3542
|
+
var isVideoUrl = (s) => s === "" || /^https?:\/\/[^\s]+$/i.test(s.trim()) || /^\/(?!\/)\S*$/.test(s.trim());
|
|
3543
|
+
function videoUrlOf(value) {
|
|
3544
|
+
const url = typeof value === "string" ? value : isPlainObject2(value) ? value.url : void 0;
|
|
3545
|
+
return typeof url === "string" && url.trim() !== "" ? url.trim() : null;
|
|
3546
|
+
}
|
|
3547
|
+
function videoValueProblem(value) {
|
|
3548
|
+
if (typeof value === "string") return isVideoUrl(value) ? null : "video_value_invalid: url must be an http(s) URL or a site path";
|
|
3549
|
+
if (!isPlainObject2(value)) return "video_value_invalid: must be a URL string or { url, poster?, autoplay?, loop?, transcript? }";
|
|
3550
|
+
const unknown = Object.keys(value).find((k) => !VIDEO_VALUE_KEYS.includes(k));
|
|
3551
|
+
if (unknown !== void 0) return `video_value_invalid: unknown key "${unknown}" (${VIDEO_VALUE_KEYS.join(", ")})`;
|
|
3552
|
+
if (typeof value.url !== "string") return "video_value_invalid: needs a url string";
|
|
3553
|
+
if (!isVideoUrl(value.url)) return "video_value_invalid: url must be an http(s) URL or a site path";
|
|
3554
|
+
if (value.poster !== void 0 && (typeof value.poster !== "string" || !isVideoUrl(value.poster))) {
|
|
3555
|
+
return "video_value_invalid: poster must be an http(s) URL or a site path";
|
|
3556
|
+
}
|
|
3557
|
+
for (const key of ["autoplay", "loop"]) {
|
|
3558
|
+
if (value[key] !== void 0 && typeof value[key] !== "boolean") return `video_value_invalid: ${key} must be true or false`;
|
|
3559
|
+
}
|
|
3560
|
+
if (value.transcript !== void 0 && (typeof value.transcript !== "string" || value.transcript.length > VIDEO_TRANSCRIPT_MAX)) {
|
|
3561
|
+
return `video_value_invalid: transcript must be a string of ${VIDEO_TRANSCRIPT_MAX} characters or fewer`;
|
|
3562
|
+
}
|
|
3563
|
+
return null;
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
// src/table-value.ts
|
|
3567
|
+
var TABLE_COLUMN_TYPES = ["text", "number", "money", "boolean", "rating", "link", "richtext-inline", "reference"];
|
|
3568
|
+
var TABLE_RESPONSIVE = ["scroll", "stack", "cards"];
|
|
3569
|
+
var TABLE_JSONLD_TYPES = ["none", "ItemList", "Dataset", "Offers"];
|
|
3570
|
+
var TABLE_LIMITS = { columns: 50, rows: 1e3, cellChars: 5e3, captionChars: 300, notesChars: 2e3, keyChars: 64, labelChars: 300 };
|
|
3571
|
+
var isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
3572
|
+
var isTableValue = (v) => isObj(v) && Array.isArray(v.columns) && Array.isArray(v.rows);
|
|
3573
|
+
var isTableCellObject = (c) => isObj(c) && "value" in c;
|
|
3574
|
+
var isTableRef = (v) => isObj(v) && "ref" in v;
|
|
3575
|
+
var isTableLink = (v) => isObj(v) && !("ref" in v) && "href" in v;
|
|
3576
|
+
var tableCellValue = (c) => c === void 0 ? null : isTableCellObject(c) ? c.value : c;
|
|
3577
|
+
var tableCellType = (col, row, c) => isTableCellObject(c) && c.type || row?.type || col.type;
|
|
3578
|
+
var TOP_KEYS = /* @__PURE__ */ new Set(["id", "caption", "notes", "headerRow", "headerColumn", "columns", "columnGroups", "rowGroups", "rows", "footnotes", "filterable", "design", "jsonLd"]);
|
|
3579
|
+
var COLUMN_KEYS = /* @__PURE__ */ new Set(["_key", "label", "type", "description", "unit", "currency", "decimals", "align", "sortable", "group", "trueLabel", "falseLabel", "max", "refTo", "highlight"]);
|
|
3580
|
+
var ROW_KEYS = /* @__PURE__ */ new Set(["_key", "cells", "group", "footer", "type", "highlight"]);
|
|
3581
|
+
var GROUP_KEYS = /* @__PURE__ */ new Set(["_key", "label"]);
|
|
3582
|
+
var FOOTNOTE_KEYS = /* @__PURE__ */ new Set(["_key", "text"]);
|
|
3583
|
+
var DESIGN_KEYS = /* @__PURE__ */ new Set(["responsive", "sticky", "striped", "density", "hideCaption"]);
|
|
3584
|
+
var CELL_KEYS = /* @__PURE__ */ new Set(["value", "type", "note"]);
|
|
3585
|
+
var LINK_KEYS = /* @__PURE__ */ new Set(["label", "href", "newTab", "rel"]);
|
|
3586
|
+
var REF_KEYS = /* @__PURE__ */ new Set(["ref", "label", "href", "resolved"]);
|
|
3587
|
+
var TABLE_LINK_RELS = ["follow", ...LINK_RELS];
|
|
3588
|
+
function tableValueProblems(value, opts = {}) {
|
|
3589
|
+
const out = [];
|
|
3590
|
+
const bad = (path, message, code = "table_value_invalid") => out.push({ path, code, message: `${code}: ${message}` });
|
|
3591
|
+
if (!isTableValue(value)) {
|
|
3592
|
+
bad([], "a table is { caption, headerRow, headerColumn, columns: [{ _key, label, type }], rows: [{ _key, cells }] }");
|
|
3593
|
+
return out;
|
|
3594
|
+
}
|
|
3595
|
+
const t = value;
|
|
3596
|
+
const unknownKeys = (o, allowed, at) => {
|
|
3597
|
+
for (const k of Object.keys(o)) if (!allowed.has(k)) bad([...at, k], `unknown key "${k}" (${[...allowed].join(", ")})`);
|
|
3598
|
+
};
|
|
3599
|
+
const str = (v, at, what, max, required = false) => {
|
|
3600
|
+
if (v === void 0 && !required) return;
|
|
3601
|
+
if (typeof v !== "string") bad(at, `${what} must be a string`);
|
|
3602
|
+
else if (v.length > max) bad(at, `${what} is ${v.length} characters; at most ${max}`, "table_limit_exceeded");
|
|
3603
|
+
};
|
|
3604
|
+
const bool = (v, at, what) => {
|
|
3605
|
+
if (v !== void 0 && typeof v !== "boolean") bad(at, `${what} must be true or false`);
|
|
3606
|
+
};
|
|
3607
|
+
const oneOf = (v, at, what, allowed) => {
|
|
3608
|
+
if (v !== void 0 && !allowed.includes(v)) bad(at, `${what} must be one of ${allowed.join(", ")}`);
|
|
3609
|
+
};
|
|
3610
|
+
unknownKeys(t, TOP_KEYS, []);
|
|
3611
|
+
str(t.id, ["id"], "id", TABLE_LIMITS.keyChars);
|
|
3612
|
+
str(t.caption, ["caption"], "caption", TABLE_LIMITS.captionChars, true);
|
|
3613
|
+
if (typeof t.caption === "string" && t.caption.trim() === "" && opts.requireCaption !== false) {
|
|
3614
|
+
bad(["caption"], "give the table a caption that says what it compares (it names the table for screen readers and search)", "table_caption_required");
|
|
3615
|
+
}
|
|
3616
|
+
str(t.notes, ["notes"], "notes", TABLE_LIMITS.notesChars);
|
|
3617
|
+
if (t.notes !== void 0 && opts.allowNote === false && typeof t.notes === "string" && t.notes.trim() !== "") bad(["notes"], "this table takes no note (config.allowNote is false)");
|
|
3618
|
+
bool(t.headerRow, ["headerRow"], "headerRow");
|
|
3619
|
+
bool(t.headerColumn, ["headerColumn"], "headerColumn");
|
|
3620
|
+
bool(t.filterable, ["filterable"], "filterable");
|
|
3621
|
+
if (t.design !== void 0) {
|
|
3622
|
+
if (!isObj(t.design)) bad(["design"], "design must be an object");
|
|
3623
|
+
else {
|
|
3624
|
+
const d = t.design;
|
|
3625
|
+
unknownKeys(d, DESIGN_KEYS, ["design"]);
|
|
3626
|
+
oneOf(d.responsive, ["design", "responsive"], "design.responsive", TABLE_RESPONSIVE);
|
|
3627
|
+
oneOf(d.density, ["design", "density"], "design.density", ["compact", "comfortable"]);
|
|
3628
|
+
for (const k of ["sticky", "striped", "hideCaption"]) bool(d[k], ["design", k], `design.${k}`);
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
const keyed = (list, at, keys, textKey, allowed) => {
|
|
3632
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3633
|
+
if (list === void 0) return seen;
|
|
3634
|
+
if (!Array.isArray(list)) {
|
|
3635
|
+
bad([at], `${at} must be an array`);
|
|
3636
|
+
return seen;
|
|
3637
|
+
}
|
|
3638
|
+
if (!allowed && list.length > 0) bad([at], `this table takes no ${at === "footnotes" ? "footnotes (config.allowFootnotes is false)" : "row groups (config.allowGroups is false)"}`);
|
|
3639
|
+
list.forEach((g, i) => {
|
|
3640
|
+
if (!isObj(g)) return bad([at, i], `${at}[${i}] must be an object`);
|
|
3641
|
+
unknownKeys(g, keys, [at, i]);
|
|
3642
|
+
if (typeof g._key !== "string" || g._key === "" || g._key.length > TABLE_LIMITS.keyChars) bad([at, i, "_key"], `needs a _key string of 1 to ${TABLE_LIMITS.keyChars} characters`);
|
|
3643
|
+
else if (seen.has(g._key)) bad([at, i, "_key"], `_key "${g._key}" is used twice`);
|
|
3644
|
+
else seen.add(g._key);
|
|
3645
|
+
str(g[textKey], [at, i, textKey], textKey, textKey === "text" ? TABLE_LIMITS.notesChars : TABLE_LIMITS.labelChars, true);
|
|
3646
|
+
});
|
|
3647
|
+
return seen;
|
|
3648
|
+
};
|
|
3649
|
+
const colGroups = keyed(t.columnGroups, "columnGroups", GROUP_KEYS, "label", true);
|
|
3650
|
+
const rowGroups = keyed(t.rowGroups, "rowGroups", GROUP_KEYS, "label", opts.allowGroups !== false);
|
|
3651
|
+
const notes = keyed(t.footnotes, "footnotes", FOOTNOTE_KEYS, "text", opts.allowFootnotes !== false);
|
|
3652
|
+
const columns = t.columns;
|
|
3653
|
+
const maxColumns = Math.min(opts.maxColumns ?? TABLE_LIMITS.columns, TABLE_LIMITS.columns);
|
|
3654
|
+
if (columns.length > maxColumns) bad(["columns"], `${columns.length} columns; at most ${maxColumns}`, "table_limit_exceeded");
|
|
3655
|
+
const cols = /* @__PURE__ */ new Map();
|
|
3656
|
+
columns.forEach((c, i) => {
|
|
3657
|
+
const at = ["columns", i];
|
|
3658
|
+
if (!isObj(c)) return bad(at, `columns[${i}] must be an object`);
|
|
3659
|
+
unknownKeys(c, COLUMN_KEYS, at);
|
|
3660
|
+
if (typeof c._key !== "string" || c._key === "" || c._key.length > TABLE_LIMITS.keyChars) bad([...at, "_key"], `needs a _key string of 1 to ${TABLE_LIMITS.keyChars} characters`);
|
|
3661
|
+
else if (cols.has(c._key)) bad([...at, "_key"], `_key "${c._key}" is used twice`);
|
|
3662
|
+
str(c.label, [...at, "label"], "label", TABLE_LIMITS.labelChars, true);
|
|
3663
|
+
if (!TABLE_COLUMN_TYPES.includes(c.type)) bad([...at, "type"], `type must be one of ${TABLE_COLUMN_TYPES.join(", ")}`);
|
|
3664
|
+
else if (opts.columnTypes && !opts.columnTypes.includes(c.type)) {
|
|
3665
|
+
bad([...at, "type"], `a ${String(c.type)} column is not offered here (config.columnTypes: ${opts.columnTypes.join(", ")})`, "table_column_type_not_allowed");
|
|
3666
|
+
}
|
|
3667
|
+
str(c.description, [...at, "description"], "description", TABLE_LIMITS.notesChars);
|
|
3668
|
+
str(c.unit, [...at, "unit"], "unit", 16);
|
|
3669
|
+
if (c.currency !== void 0 && (typeof c.currency !== "string" || !/^[A-Z]{3}$/.test(c.currency))) bad([...at, "currency"], "currency must be an ISO 4217 code such as USD");
|
|
3670
|
+
if (c.decimals !== void 0 && (!Number.isInteger(c.decimals) || c.decimals < 0 || c.decimals > 10)) bad([...at, "decimals"], "decimals must be a whole number from 0 to 10");
|
|
3671
|
+
if (c.max !== void 0 && (!Number.isInteger(c.max) || c.max < 1 || c.max > 10)) bad([...at, "max"], "max must be a whole number from 1 to 10");
|
|
3672
|
+
oneOf(c.align, [...at, "align"], "align", ["start", "center", "end"]);
|
|
3673
|
+
for (const k of ["sortable", "highlight"]) bool(c[k], [...at, k], k);
|
|
3674
|
+
str(c.trueLabel, [...at, "trueLabel"], "trueLabel", TABLE_LIMITS.labelChars);
|
|
3675
|
+
str(c.falseLabel, [...at, "falseLabel"], "falseLabel", TABLE_LIMITS.labelChars);
|
|
3676
|
+
if (c.group !== void 0 && (typeof c.group !== "string" || !colGroups.has(c.group))) bad([...at, "group"], `group "${String(c.group)}" is not a key of columnGroups`);
|
|
3677
|
+
if (c.refTo !== void 0 && (!Array.isArray(c.refTo) || c.refTo.some((r) => typeof r !== "string" || r === ""))) bad([...at, "refTo"], 'refTo must be a list of collection ids or "page"');
|
|
3678
|
+
if (typeof c._key === "string" && c._key !== "" && !cols.has(c._key)) cols.set(c._key, c);
|
|
3679
|
+
});
|
|
3680
|
+
if (opts.lockColumns && opts.lockedColumns) {
|
|
3681
|
+
const want = opts.lockedColumns.map((c) => `${c._key}:${c.type}`).join(",");
|
|
3682
|
+
const got = columns.map((c) => isObj(c) ? `${String(c._key)}:${String(c.type)}` : "?").join(",");
|
|
3683
|
+
if (want !== got) bad(["columns"], `this table's columns are set by its developer (config.lockColumns): keep ${opts.lockedColumns.map((c) => c._key).join(", ")}, in that order, with their types`, "table_columns_locked");
|
|
3684
|
+
}
|
|
3685
|
+
const rows = t.rows;
|
|
3686
|
+
const maxRows = Math.min(opts.maxRows ?? TABLE_LIMITS.rows, TABLE_LIMITS.rows);
|
|
3687
|
+
const body = rows.filter((r) => !(isObj(r) && r.footer === true)).length;
|
|
3688
|
+
if (rows.length > TABLE_LIMITS.rows || body > maxRows) bad(["rows"], `${body} rows; at most ${maxRows}`, "table_limit_exceeded");
|
|
3689
|
+
if (opts.minRows !== void 0 && body < opts.minRows) bad(["rows"], `${body} rows; at least ${opts.minRows}`, "table_rows_min");
|
|
3690
|
+
const rowKeys = /* @__PURE__ */ new Set();
|
|
3691
|
+
rows.forEach((r, i) => {
|
|
3692
|
+
const at = ["rows", i];
|
|
3693
|
+
if (!isObj(r)) return bad(at, `rows[${i}] must be an object`);
|
|
3694
|
+
unknownKeys(r, ROW_KEYS, at);
|
|
3695
|
+
if (typeof r._key !== "string" || r._key === "" || r._key.length > TABLE_LIMITS.keyChars) bad([...at, "_key"], `needs a _key string of 1 to ${TABLE_LIMITS.keyChars} characters`);
|
|
3696
|
+
else if (rowKeys.has(r._key)) bad([...at, "_key"], `_key "${r._key}" is used twice`);
|
|
3697
|
+
else rowKeys.add(r._key);
|
|
3698
|
+
if (r.group !== void 0 && (typeof r.group !== "string" || !rowGroups.has(r.group))) bad([...at, "group"], `group "${String(r.group)}" is not a key of rowGroups`);
|
|
3699
|
+
bool(r.footer, [...at, "footer"], "footer");
|
|
3700
|
+
bool(r.highlight, [...at, "highlight"], "highlight");
|
|
3701
|
+
oneOf(r.type, [...at, "type"], "type", TABLE_COLUMN_TYPES);
|
|
3702
|
+
if (!isObj(r.cells)) return bad([...at, "cells"], "cells must be an object keyed by column _key");
|
|
3703
|
+
for (const [k, cell] of Object.entries(r.cells)) {
|
|
3704
|
+
const cAt = [...at, "cells", k];
|
|
3705
|
+
const col = cols.get(k);
|
|
3706
|
+
if (!col) {
|
|
3707
|
+
bad(cAt, `no column has _key "${k}"`);
|
|
3708
|
+
continue;
|
|
3709
|
+
}
|
|
3710
|
+
cellProblems(cell, col, r, cAt, notes, opts, bad);
|
|
3711
|
+
}
|
|
3712
|
+
});
|
|
3713
|
+
return out;
|
|
3714
|
+
}
|
|
3715
|
+
function cellProblems(cell, col, row, at, notes, opts, bad) {
|
|
3716
|
+
if (cell === void 0 || cell === null) return;
|
|
3717
|
+
let v = cell;
|
|
3718
|
+
let vAt = at;
|
|
3719
|
+
if (isTableCellObject(cell)) {
|
|
3720
|
+
for (const k of Object.keys(cell)) if (!CELL_KEYS.has(k)) bad([...at, k], `unknown key "${k}" (value, type, note)`);
|
|
3721
|
+
if (cell.type !== void 0 && !TABLE_COLUMN_TYPES.includes(cell.type)) bad([...at, "type"], `type must be one of ${TABLE_COLUMN_TYPES.join(", ")}`);
|
|
3722
|
+
if (cell.note !== void 0 && (typeof cell.note !== "string" || !notes.has(cell.note))) bad([...at, "note"], `note "${String(cell.note)}" is not a key of footnotes`);
|
|
3723
|
+
v = cell.value;
|
|
3724
|
+
vAt = [...at, "value"];
|
|
3725
|
+
if (v === null) return;
|
|
3726
|
+
}
|
|
3727
|
+
const type = tableCellType(col, row, cell);
|
|
3728
|
+
if (typeof v === "string") {
|
|
3729
|
+
if (v.length > TABLE_LIMITS.cellChars) bad(vAt, `a cell holds at most ${TABLE_LIMITS.cellChars} characters`, "table_limit_exceeded");
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3732
|
+
if (typeof v === "number") {
|
|
3733
|
+
if (!Number.isFinite(v)) bad(vAt, "a number cell must be finite");
|
|
3734
|
+
return;
|
|
3735
|
+
}
|
|
3736
|
+
if (typeof v === "boolean") return;
|
|
3737
|
+
if (!isObj(v)) return bad(vAt, "a cell holds text, a number, true / false, a link { label, href } or a reference { ref }");
|
|
3738
|
+
if ("ref" in v) {
|
|
3739
|
+
if (type !== "reference") return bad(vAt, `a reference { ref } belongs in a reference cell; this cell is ${type}`);
|
|
3740
|
+
for (const k of Object.keys(v)) if (!REF_KEYS.has(k)) bad([...vAt, k], `unknown key "${k}" (ref, label, href)`);
|
|
3741
|
+
if (typeof v.ref !== "string" || v.ref === "") bad([...vAt, "ref"], "ref must be the id of a page or an entry");
|
|
3742
|
+
if (v.label !== void 0 && typeof v.label !== "string") bad([...vAt, "label"], "label must be a string");
|
|
3743
|
+
if (v.href !== void 0 && (typeof v.href !== "string" || isScriptHref(v.href))) bad([...vAt, "href"], "href must be a link the site can hold (no script links)", "link_value_invalid");
|
|
3744
|
+
return;
|
|
3745
|
+
}
|
|
3746
|
+
if (type !== "link") return bad(vAt, `a link { label, href } belongs in a link cell; this cell is ${type}`);
|
|
3747
|
+
for (const k of Object.keys(v)) if (!LINK_KEYS.has(k)) bad([...vAt, k], `unknown key "${k}" (label, href, newTab, rel)`);
|
|
3748
|
+
if (typeof v.label !== "string" && v.label !== void 0) bad([...vAt, "label"], "label must be a string");
|
|
3749
|
+
if (typeof v.href !== "string") return bad([...vAt, "href"], "needs an href string", "link_value_invalid");
|
|
3750
|
+
if (isScriptHref(v.href)) return bad([...vAt, "href"], "script links (javascript:, data:, vbscript:) aren't allowed", "link_value_invalid");
|
|
3751
|
+
const grammar = v.href !== "" ? opts.hrefProblem?.(v.href) : null;
|
|
3752
|
+
if (grammar) bad([...vAt, "href"], grammar, "link_value_invalid");
|
|
3753
|
+
if (v.newTab !== void 0 && typeof v.newTab !== "boolean") bad([...vAt, "newTab"], "newTab must be true or false", "link_value_invalid");
|
|
3754
|
+
if (v.rel !== void 0 && !TABLE_LINK_RELS.includes(v.rel)) bad([...vAt, "rel"], `rel must be one of ${TABLE_LINK_RELS.join(", ")}`, "link_value_invalid");
|
|
3755
|
+
}
|
|
3756
|
+
function tablePathString(path) {
|
|
3757
|
+
return path.map((s, i) => typeof s === "number" ? `[${s}]` : i === 0 ? s : `.${s}`).join("");
|
|
3758
|
+
}
|
|
3759
|
+
function normalizeTableValue(value) {
|
|
3760
|
+
if (!isTableValue(value)) return value;
|
|
3761
|
+
const t = structuredClone(value);
|
|
3762
|
+
t.headerRow = t.headerRow !== false;
|
|
3763
|
+
t.headerColumn = t.headerColumn === true;
|
|
3764
|
+
for (const r of t.rows) {
|
|
3765
|
+
if (!isObj(r) || !isObj(r.cells)) continue;
|
|
3766
|
+
for (const c of Object.values(r.cells)) {
|
|
3767
|
+
const v = isTableCellObject(c) ? c.value : c;
|
|
3768
|
+
if (isTableRef(v)) delete v.resolved;
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
return t;
|
|
3772
|
+
}
|
|
3773
|
+
var readTable = (v) => ({ ...v, headerRow: v.headerRow !== false, headerColumn: v.headerColumn === true });
|
|
3774
|
+
function tableRefCells(value) {
|
|
3775
|
+
if (!isTableValue(value)) return [];
|
|
3776
|
+
const cols = new Map(value.columns.filter(isObj).map((c) => [c._key, c]));
|
|
3777
|
+
const out = [];
|
|
3778
|
+
value.rows.forEach((r, i) => {
|
|
3779
|
+
if (!isObj(r) || !isObj(r.cells)) return;
|
|
3780
|
+
for (const [k, c] of Object.entries(r.cells)) {
|
|
3781
|
+
const col = cols.get(k);
|
|
3782
|
+
if (!col) continue;
|
|
3783
|
+
const v = isTableCellObject(c) ? c.value : c;
|
|
3784
|
+
if (!isTableRef(v) || typeof v.ref !== "string" || v.ref === "" || tableCellType(col, r, c) !== "reference") continue;
|
|
3785
|
+
out.push({ cell: v, id: v.ref, path: ["rows", i, "cells", k, ...isTableCellObject(c) ? ["value"] : []], refTo: Array.isArray(col.refTo) && col.refTo.length ? col.refTo : null });
|
|
3786
|
+
}
|
|
3787
|
+
});
|
|
3788
|
+
return out;
|
|
3789
|
+
}
|
|
3790
|
+
var strip = (s) => s.replace(/<[^>]*>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
3791
|
+
var TRUE_WORDS = /* @__PURE__ */ new Set(["\u2713", "\u2714", "\u2714\uFE0F", "\u2611", "\u2705", "yes", "y", "true", "included", "available", "supported", "check", "\u25CF"]);
|
|
3792
|
+
var FALSE_WORDS = /* @__PURE__ */ new Set(["\u2717", "\u2715", "\u2716", "\u274C", "\xD7", "x", "no", "n", "false", "not included", "unavailable", "not available", "unsupported", "\u2014", "\u2013", "-", "\u25CB"]);
|
|
3793
|
+
function toBool(v) {
|
|
3794
|
+
if (typeof v === "boolean") return v;
|
|
3795
|
+
if (typeof v !== "string") return null;
|
|
3796
|
+
const s = strip(v).trim().toLowerCase();
|
|
3797
|
+
return TRUE_WORDS.has(s) ? true : FALSE_WORDS.has(s) ? false : null;
|
|
3798
|
+
}
|
|
3799
|
+
function toNumber(v) {
|
|
3800
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
3801
|
+
if (typeof v !== "string") return null;
|
|
3802
|
+
const s = v.replace(/[,\s]/g, "").replace(/[^\d.+-]/g, "");
|
|
3803
|
+
return s && /^[+-]?\d*\.?\d+$/.test(s) ? Number(s) : null;
|
|
3804
|
+
}
|
|
3805
|
+
var formatNumber2 = (n, decimals) => new Intl.NumberFormat("en-US", decimals != null ? { minimumFractionDigits: decimals, maximumFractionDigits: decimals } : { maximumFractionDigits: 6 }).format(n);
|
|
3806
|
+
function formatMoney(n, currency = "USD", decimals) {
|
|
3807
|
+
try {
|
|
3808
|
+
const whole = Number.isInteger(n) && decimals == null;
|
|
3809
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency, minimumFractionDigits: whole ? 0 : decimals ?? 2, maximumFractionDigits: whole ? 0 : decimals ?? 2 }).format(n);
|
|
3810
|
+
} catch {
|
|
3811
|
+
return `${formatNumber2(n, decimals)} ${currency}`;
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
var isEmptyCell = (v) => v == null || v === "" || isObj(v) && !(isTableRef(v) ? v.ref : v.label || v.href);
|
|
3815
|
+
var refLabel = (r) => r.resolved?.title || r.label || r.ref;
|
|
3816
|
+
var refHref = (r) => r.resolved?.url ?? r.href;
|
|
3817
|
+
function tableCellText(col, row, c) {
|
|
3818
|
+
const v = tableCellValue(c);
|
|
3819
|
+
if (isEmptyCell(v)) return "";
|
|
3820
|
+
switch (tableCellType(col, row, c)) {
|
|
3821
|
+
case "boolean": {
|
|
3822
|
+
const b = toBool(v);
|
|
3823
|
+
return b == null ? strip(String(v)) : b ? col.trueLabel || "Included" : col.falseLabel || "Not included";
|
|
3824
|
+
}
|
|
3825
|
+
case "number": {
|
|
3826
|
+
const n = toNumber(v);
|
|
3827
|
+
return n == null ? strip(String(v)) : `${formatNumber2(n, col.decimals)}${col.unit ? /^[%°]/.test(col.unit) ? col.unit : ` ${col.unit}` : ""}`;
|
|
3828
|
+
}
|
|
3829
|
+
case "money": {
|
|
3830
|
+
const n = toNumber(v);
|
|
3831
|
+
return n == null ? strip(String(v)) : formatMoney(n, col.currency || "USD", col.decimals);
|
|
3832
|
+
}
|
|
3833
|
+
case "rating": {
|
|
3834
|
+
const n = toNumber(v);
|
|
3835
|
+
return n == null ? strip(String(v)) : `${formatNumber2(n, 1).replace(/\.0$/, "")} out of ${col.max ?? 5}`;
|
|
3836
|
+
}
|
|
3837
|
+
case "link":
|
|
3838
|
+
return isObj(v) ? String(v.label || v.href || "") : strip(String(v));
|
|
3839
|
+
case "reference":
|
|
3840
|
+
return isTableRef(v) ? refLabel(v) : strip(String(v));
|
|
3841
|
+
default:
|
|
3842
|
+
return isObj(v) ? String(v.label ?? "") : strip(String(v)).replace(/\s+/g, " ").trim();
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
var esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3846
|
+
var escAttr = (s) => esc(s).replace(/"/g, """);
|
|
3847
|
+
var safeHref = (h) => isScriptHref(h) ? "#" : h;
|
|
3848
|
+
var INLINE_TAGS = /* @__PURE__ */ new Set(["strong", "b", "em", "i", "u", "s", "del", "code", "a", "br", "sup", "sub", "mark", "small", "abbr"]);
|
|
3849
|
+
function sanitizeInline(html) {
|
|
3850
|
+
return html.replace(/<(script|style|iframe|object|embed|template)\b[\s\S]*?<\/\1>/gi, "").replace(/<\/?([a-z][a-z0-9]*)\b([^>]*)>/gi, (m, tag, attrs) => {
|
|
3851
|
+
const t = tag.toLowerCase();
|
|
3852
|
+
if (!INLINE_TAGS.has(t)) return "";
|
|
3853
|
+
const name = t === "b" ? "strong" : t === "i" ? "em" : t;
|
|
3854
|
+
if (m.startsWith("</")) return `</${name}>`;
|
|
3855
|
+
if (t === "a") {
|
|
3856
|
+
const href = attrs.match(/href\s*=\s*("([^"]*)"|'([^']*)')/i);
|
|
3857
|
+
return `<a href="${escAttr(safeHref(href ? href[2] ?? href[3] ?? "" : ""))}">`;
|
|
3858
|
+
}
|
|
3859
|
+
if (t === "abbr") {
|
|
3860
|
+
const title = attrs.match(/title\s*=\s*"([^"]*)"/i);
|
|
3861
|
+
return title ? `<abbr title="${escAttr(title[1])}">` : "<abbr>";
|
|
3862
|
+
}
|
|
3863
|
+
return `<${name}>`;
|
|
3864
|
+
});
|
|
3865
|
+
}
|
|
3866
|
+
var alignOf = (col, t) => col.align ?? (t === "number" || t === "money" || t === "rating" ? "end" : "start");
|
|
3867
|
+
function orderedRows(t) {
|
|
3868
|
+
const groups = (t.rowGroups ?? []).filter(isObj);
|
|
3869
|
+
const known = new Set(groups.map((g) => g._key));
|
|
3870
|
+
const body = t.rows.filter((r) => isObj(r) && !r.footer);
|
|
3871
|
+
const loose = body.filter((r) => !r.group || !known.has(r.group));
|
|
3872
|
+
const bodies = [];
|
|
3873
|
+
if (loose.length || !groups.length) bodies.push({ rows: loose });
|
|
3874
|
+
for (const g of groups) {
|
|
3875
|
+
const rows = body.filter((r) => r.group === g._key);
|
|
3876
|
+
if (rows.length) bodies.push({ group: g, rows });
|
|
3877
|
+
}
|
|
3878
|
+
return { bodies, foot: t.rows.filter((r) => isObj(r) && r.footer) };
|
|
3879
|
+
}
|
|
3880
|
+
function cellHtml(t, col, row, c, fn, asHeader) {
|
|
3881
|
+
const v = tableCellValue(c);
|
|
3882
|
+
const type = tableCellType(col, row, c);
|
|
3883
|
+
const note = isTableCellObject(c) ? c.note : void 0;
|
|
3884
|
+
const n = note ? fn.get(note) : void 0;
|
|
3885
|
+
const sup = n ? `<sup><a href="#${escAttr(t.id ?? "tbl")}-fn-${n}" aria-label="Note ${n}">${n}</a></sup>` : "";
|
|
3886
|
+
let inner;
|
|
3887
|
+
if (isEmptyCell(v)) inner = asHeader ? "" : `<span aria-hidden="true" class="bt-dash">\u2014</span><span class="bt-sr">No value</span>`;
|
|
3888
|
+
else
|
|
3889
|
+
switch (type) {
|
|
3890
|
+
case "boolean": {
|
|
3891
|
+
const b = toBool(v);
|
|
3892
|
+
inner = b == null ? esc(strip(String(v))) : `<span aria-hidden="true" class="bt-bool" data-bool="${b}">${b ? "\u2713" : "\u2013"}</span><span class="bt-sr">${esc(b ? col.trueLabel || "Included" : col.falseLabel || "Not included")}</span>`;
|
|
3893
|
+
break;
|
|
3894
|
+
}
|
|
3895
|
+
case "rating": {
|
|
3896
|
+
const num = toNumber(v);
|
|
3897
|
+
inner = num == null ? esc(strip(String(v))) : `<span aria-hidden="true" class="bt-star">\u2605</span> ${esc(formatNumber2(num, 1).replace(/\.0$/, ""))}<span class="bt-dim"> / ${col.max ?? 5}</span>`;
|
|
3898
|
+
break;
|
|
3899
|
+
}
|
|
3900
|
+
case "link": {
|
|
3901
|
+
const l = isObj(v) ? v : { label: String(v), href: "" };
|
|
3902
|
+
const rel = [l.newTab ? "noopener noreferrer" : "", l.rel && l.rel !== "follow" ? l.rel : ""].filter(Boolean).join(" ");
|
|
3903
|
+
inner = l.href ? `<a href="${escAttr(safeHref(l.href))}"${l.newTab ? ' target="_blank"' : ""}${rel ? ` rel="${rel}"` : ""}>${esc(l.label || l.href)}</a>` : esc(l.label ?? "");
|
|
3904
|
+
break;
|
|
3905
|
+
}
|
|
3906
|
+
case "reference": {
|
|
3907
|
+
if (!isTableRef(v)) {
|
|
3908
|
+
inner = esc(strip(String(v)));
|
|
3909
|
+
break;
|
|
3910
|
+
}
|
|
3911
|
+
const href = refHref(v);
|
|
3912
|
+
inner = href ? `<a href="${escAttr(safeHref(href))}">${esc(refLabel(v))}</a>` : esc(refLabel(v));
|
|
3913
|
+
break;
|
|
3914
|
+
}
|
|
3915
|
+
case "richtext-inline":
|
|
3916
|
+
inner = sanitizeInline(String(isObj(v) ? v.label ?? "" : v));
|
|
3917
|
+
break;
|
|
3918
|
+
default:
|
|
3919
|
+
inner = esc(type === "number" || type === "money" ? tableCellText(col, row, c) : isObj(v) ? String(v.label ?? "") : strip(String(v)));
|
|
3920
|
+
}
|
|
3921
|
+
return { inner: inner + sup, align: alignOf(col, type) };
|
|
3922
|
+
}
|
|
3923
|
+
var TABLE_CSS = '.bt-table .bt-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.bt-table .bt-scroll{overflow-x:auto}.bt-table table{border-collapse:collapse;width:100%}.bt-table th,.bt-table td{padding:8px 12px;border-bottom:1px solid #e5e5e5;text-align:start;vertical-align:top}.bt-table [data-align="end"]{text-align:end}.bt-table [data-align="center"]{text-align:center}.bt-table caption{text-align:start;font-weight:600;padding-bottom:8px}.bt-table .bt-notes{font-size:.9em;margin-top:8px}';
|
|
3924
|
+
function renderTableHtml(input) {
|
|
3925
|
+
const t = readTable(input);
|
|
3926
|
+
const id = (t.id ?? "tbl").replace(/[^\w-]/g, "") || "tbl";
|
|
3927
|
+
const cols = t.columns.filter(isObj);
|
|
3928
|
+
const n = cols.length;
|
|
3929
|
+
if (!n) return "";
|
|
3930
|
+
const fn = new Map((t.footnotes ?? []).filter(isObj).map((f, i) => [f._key, i + 1]));
|
|
3931
|
+
const groups = (t.columnGroups ?? []).filter((g) => isObj(g) && cols.some((c) => c.group === g._key));
|
|
3932
|
+
const hasNotes = !!t.notes || fn.size > 0;
|
|
3933
|
+
const responsive = t.design?.responsive ?? "scroll";
|
|
3934
|
+
const fig = [];
|
|
3935
|
+
const attrs = [
|
|
3936
|
+
`class="bt-table"`,
|
|
3937
|
+
`data-responsive="${escAttr(responsive)}"`,
|
|
3938
|
+
t.design?.sticky ? `data-sticky="true"` : "",
|
|
3939
|
+
t.design?.striped ? `data-striped="true"` : "",
|
|
3940
|
+
t.design?.density === "compact" ? `data-density="compact"` : ""
|
|
3941
|
+
].filter(Boolean);
|
|
3942
|
+
fig.push(`<figure ${attrs.join(" ")}><style>${TABLE_CSS}</style>`);
|
|
3943
|
+
fig.push(`<div class="bt-scroll" role="region" tabindex="0" aria-labelledby="${id}-caption">`);
|
|
3944
|
+
fig.push(`<table${hasNotes ? ` aria-describedby="${id}-notes"` : ""}>`);
|
|
3945
|
+
fig.push(`<caption id="${id}-caption"${t.design?.hideCaption ? ` class="bt-sr"` : ""}>${esc(t.caption || "Table")}</caption>`);
|
|
3946
|
+
if (t.headerRow) {
|
|
3947
|
+
fig.push("<thead>");
|
|
3948
|
+
if (groups.length) {
|
|
3949
|
+
const cells = [];
|
|
3950
|
+
for (let i = 0; i < n; ) {
|
|
3951
|
+
const g = cols[i].group ? groups.find((x) => x._key === cols[i].group) : void 0;
|
|
3952
|
+
if (!g) {
|
|
3953
|
+
cells.push("<td></td>");
|
|
3954
|
+
i++;
|
|
3955
|
+
continue;
|
|
3956
|
+
}
|
|
3957
|
+
let span = 1;
|
|
3958
|
+
while (i + span < n && cols[i + span].group === g._key) span++;
|
|
3959
|
+
cells.push(`<th scope="colgroup"${span > 1 ? ` colspan="${span}"` : ""}>${esc(g.label)}</th>`);
|
|
3960
|
+
i += span;
|
|
3961
|
+
}
|
|
3962
|
+
fig.push(`<tr>${cells.join("")}</tr>`);
|
|
3963
|
+
}
|
|
3964
|
+
fig.push(
|
|
3965
|
+
`<tr>${cols.map((c, i) => {
|
|
3966
|
+
if (i === 0 && t.headerColumn && !String(c.label ?? "").trim()) return "<td></td>";
|
|
3967
|
+
const align = alignOf(c, c.type);
|
|
3968
|
+
return `<th scope="col"${align !== "start" ? ` data-align="${align}"` : ""}${c.highlight ? ` data-highlight="true"` : ""}${c.description ? ` title="${escAttr(c.description)}"` : ""}>${esc(c.label || `Column ${i + 1}`)}</th>`;
|
|
3969
|
+
}).join("")}</tr>`
|
|
3970
|
+
);
|
|
3971
|
+
fig.push("</thead>");
|
|
3972
|
+
}
|
|
3973
|
+
const rowHtml = (r) => {
|
|
3974
|
+
const cells = cols.map((c, i) => {
|
|
3975
|
+
const asTh = t.headerColumn && i === 0;
|
|
3976
|
+
const { inner, align } = cellHtml(t, c, r, isObj(r.cells) ? r.cells[c._key] : void 0, fn, asTh);
|
|
3977
|
+
const a = `${align !== "start" ? ` data-align="${align}"` : ""}${c.highlight ? ` data-highlight="true"` : ""}`;
|
|
3978
|
+
return asTh ? `<th scope="row"${a}>${inner}</th>` : `<td data-label="${escAttr(c.label || `Column ${i + 1}`)}"${a}>${inner}</td>`;
|
|
3979
|
+
});
|
|
3980
|
+
return `<tr${r.highlight ? ` data-highlight="true"` : ""}>${cells.join("")}</tr>`;
|
|
3981
|
+
};
|
|
3982
|
+
const { bodies, foot } = orderedRows(t);
|
|
3983
|
+
for (const b of bodies) {
|
|
3984
|
+
fig.push("<tbody>");
|
|
3985
|
+
if (b.group) fig.push(`<tr class="bt-group"><th scope="rowgroup" colspan="${n}">${esc(b.group.label)}</th></tr>`);
|
|
3986
|
+
fig.push(b.rows.map(rowHtml).join(""));
|
|
3987
|
+
fig.push("</tbody>");
|
|
3988
|
+
}
|
|
3989
|
+
if (foot.length) fig.push(`<tfoot>${foot.map(rowHtml).join("")}</tfoot>`);
|
|
3990
|
+
fig.push("</table></div>");
|
|
3991
|
+
if (hasNotes) {
|
|
3992
|
+
fig.push(`<div class="bt-notes" id="${id}-notes">`);
|
|
3993
|
+
if (t.notes) fig.push(`<p>${esc(t.notes)}</p>`);
|
|
3994
|
+
if (fn.size) fig.push(`<ol>${(t.footnotes ?? []).filter(isObj).map((f, i) => `<li id="${id}-fn-${i + 1}">${esc(f.text)}</li>`).join("")}</ol>`);
|
|
3995
|
+
fig.push("</div>");
|
|
3996
|
+
}
|
|
3997
|
+
fig.push("</figure>");
|
|
3998
|
+
const html = fig.join("");
|
|
3999
|
+
return responsive === "stack" || responsive === "cards" ? html.replace(/<table(?=[\s>])/, '<table role="table"').replace(/<(thead|tbody|tfoot)(?=[\s>])/g, '<$1 role="rowgroup"').replace(/<tr(?=[\s>])/g, '<tr role="row"').replace(/<th scope="(col|colgroup)"/g, '<th role="columnheader" scope="$1"').replace(/<th scope="(row|rowgroup)"/g, '<th role="rowheader" scope="$1"').replace(/<td(?=[\s>])/g, '<td role="cell"') : html;
|
|
4000
|
+
}
|
|
4001
|
+
var mdSafe = (s) => s.replace(/(\]\([^)]*\))|[<>]/g, (m, link) => link ?? (m === "<" ? "<" : ">"));
|
|
4002
|
+
var mdCell = (s) => mdSafe(s).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ").trim();
|
|
4003
|
+
var inlineMd = (html) => strip(
|
|
4004
|
+
sanitizeInline(html).replace(/<(strong)>([\s\S]*?)<\/\1>/g, "**$2**").replace(/<(em)>([\s\S]*?)<\/\1>/g, "_$2_").replace(/<code>([\s\S]*?)<\/code>/g, "`$1`").replace(/<a href="([^"]*)">([\s\S]*?)<\/a>/g, (_m, h, l) => `[${l}](${h.replace(/&/g, "&")})`).replace(/<br>/g, " ")
|
|
4005
|
+
);
|
|
4006
|
+
function tableToMarkdown(input) {
|
|
4007
|
+
const t = readTable(input);
|
|
4008
|
+
const cols = t.columns.filter(isObj);
|
|
4009
|
+
if (!cols.length) return "";
|
|
4010
|
+
const groups = new Map((t.columnGroups ?? []).filter(isObj).map((g) => [g._key, g.label]));
|
|
4011
|
+
const fn = new Map((t.footnotes ?? []).filter(isObj).map((f, i) => [f._key, i + 1]));
|
|
4012
|
+
const pfx = (t.id ?? "t").replace(/[^\w-]/g, "") || "t";
|
|
4013
|
+
const line = (cells) => `| ${cells.join(" | ")} |`;
|
|
4014
|
+
const head = cols.map((c, i) => mdCell([c.group && groups.get(c.group), c.label || (i === 0 && t.headerColumn ? "" : `Column ${i + 1}`)].filter(Boolean).join(" \xB7 ")) || " ");
|
|
4015
|
+
const sep = cols.map((c) => alignOf(c, c.type) === "end" ? "---:" : alignOf(c, c.type) === "center" ? ":---:" : "---");
|
|
4016
|
+
const row = (r) => line(
|
|
4017
|
+
cols.map((c, i) => {
|
|
4018
|
+
const cell = isObj(r.cells) ? r.cells[c._key] : void 0;
|
|
4019
|
+
const v = tableCellValue(cell);
|
|
4020
|
+
const ty = tableCellType(c, r, cell);
|
|
4021
|
+
let s = ty === "richtext-inline" && typeof v === "string" ? inlineMd(v) : tableCellText(c, r, cell);
|
|
4022
|
+
if (ty === "link" && isTableLink(v) && v.href && !isScriptHref(v.href)) s = `[${s}](${v.href})`;
|
|
4023
|
+
if (ty === "reference" && isTableRef(v)) {
|
|
4024
|
+
const href = refHref(v);
|
|
4025
|
+
if (href && !isScriptHref(href)) s = `[${s}](${href})`;
|
|
4026
|
+
}
|
|
4027
|
+
if (!s && !(t.headerColumn && i === 0)) s = "\u2014";
|
|
4028
|
+
if (t.headerColumn && i === 0 && s) s = `**${s}**`;
|
|
4029
|
+
const note = isTableCellObject(cell) ? cell.note : void 0;
|
|
4030
|
+
if (note && fn.get(note)) s += `[^${pfx}-${fn.get(note)}]`;
|
|
4031
|
+
return mdCell(s) || " ";
|
|
4032
|
+
})
|
|
4033
|
+
);
|
|
4034
|
+
const out = [`**${mdCell(t.caption || "Table")}**`, "", line(head), line(sep)];
|
|
4035
|
+
const { bodies, foot } = orderedRows(t);
|
|
4036
|
+
for (const b of bodies) {
|
|
4037
|
+
if (b.group) out.push(line([`**${mdCell(b.group.label)}**`, ...cols.slice(1).map(() => " ")]));
|
|
4038
|
+
b.rows.forEach((r) => out.push(row(r)));
|
|
4039
|
+
}
|
|
4040
|
+
foot.forEach((r) => out.push(row(r)));
|
|
4041
|
+
const after = [];
|
|
4042
|
+
if (t.notes) after.push(mdSafe(t.notes));
|
|
4043
|
+
const described = cols.filter((c) => c.description);
|
|
4044
|
+
if (described.length) after.push(mdSafe(described.map((c) => `${c.label}: ${c.description}`).join(". ") + "."));
|
|
4045
|
+
if (fn.size) after.push((t.footnotes ?? []).filter(isObj).map((f, i) => `[^${pfx}-${i + 1}]: ${mdSafe(f.text)}`).join("\n"));
|
|
4046
|
+
return [out.join("\n"), ...after].join("\n\n");
|
|
4047
|
+
}
|
|
4048
|
+
function tableSearchText(input) {
|
|
4049
|
+
if (!isTableValue(input)) return "";
|
|
4050
|
+
const t = input;
|
|
4051
|
+
const cols = t.columns.filter(isObj);
|
|
4052
|
+
const parts = [t.caption, t.notes ?? ""];
|
|
4053
|
+
for (const c of cols) parts.push(c.label, c.description ?? "");
|
|
4054
|
+
for (const g of [...t.columnGroups ?? [], ...t.rowGroups ?? []]) if (isObj(g)) parts.push(g.label);
|
|
4055
|
+
for (const r of t.rows) {
|
|
4056
|
+
if (!isObj(r) || !isObj(r.cells)) continue;
|
|
4057
|
+
for (const c of cols) {
|
|
4058
|
+
const cell = r.cells[c._key];
|
|
4059
|
+
parts.push(tableCellType(c, r, cell) === "richtext-inline" ? strip(String(tableCellValue(cell) ?? "")) : tableCellText(c, r, cell));
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
for (const f of t.footnotes ?? []) if (isObj(f)) parts.push(f.text);
|
|
4063
|
+
return parts.filter((p) => typeof p === "string" && p.trim() !== "").map((p) => p.trim()).join(" ");
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
// src/content-model.ts
|
|
4067
|
+
var OPTION_DESCRIPTION_MAX = 140;
|
|
4068
|
+
|
|
3035
4069
|
// src/locator.ts
|
|
3036
4070
|
var LANDMARK_SELECTOR = "header,nav,footer";
|
|
3037
4071
|
var LANDMARK_TAGS = /* @__PURE__ */ new Set(["HEADER", "NAV", "FOOTER"]);
|
|
@@ -3092,6 +4126,96 @@ function regionOf(regions, el) {
|
|
|
3092
4126
|
for (const r of regions) if (r.el.contains(el)) slot = r.slot;
|
|
3093
4127
|
return slot;
|
|
3094
4128
|
}
|
|
4129
|
+
|
|
4130
|
+
// src/sections.ts
|
|
4131
|
+
var TAG_HEADS = /* @__PURE__ */ new Set([
|
|
4132
|
+
"h1",
|
|
4133
|
+
"h2",
|
|
4134
|
+
"h3",
|
|
4135
|
+
"h4",
|
|
4136
|
+
"h5",
|
|
4137
|
+
"h6",
|
|
4138
|
+
"p",
|
|
4139
|
+
"a",
|
|
4140
|
+
"img",
|
|
4141
|
+
"li",
|
|
4142
|
+
"list",
|
|
4143
|
+
"blockquote",
|
|
4144
|
+
"figcaption",
|
|
4145
|
+
"button",
|
|
4146
|
+
"summary",
|
|
4147
|
+
"dt",
|
|
4148
|
+
"dd",
|
|
4149
|
+
"th",
|
|
4150
|
+
"td",
|
|
4151
|
+
"pre",
|
|
4152
|
+
"label",
|
|
4153
|
+
"span",
|
|
4154
|
+
"div",
|
|
4155
|
+
"time",
|
|
4156
|
+
"strong",
|
|
4157
|
+
"em",
|
|
4158
|
+
"small",
|
|
4159
|
+
"b",
|
|
4160
|
+
"i",
|
|
4161
|
+
"code",
|
|
4162
|
+
"icon",
|
|
4163
|
+
"bg"
|
|
4164
|
+
]);
|
|
4165
|
+
var topFieldKeyOf = (path) => path.split(/[.[]/, 1)[0] ?? path;
|
|
4166
|
+
function fieldGroupOf(topKey, flat = false) {
|
|
4167
|
+
if (topKey.startsWith("group-")) return { groupKey: topKey, rooted: true };
|
|
4168
|
+
const camel = flat ? /^([a-z][a-z0-9]*)[A-Z]/.exec(topKey)?.[1] : void 0;
|
|
4169
|
+
if (camel) return TAG_HEADS.has(camel) ? { groupKey: topKey, rooted: false } : { groupKey: camel, rooted: true };
|
|
4170
|
+
const head = topKey.split(/[-_]/, 1)[0] || topKey;
|
|
4171
|
+
if (TAG_HEADS.has(head)) return { groupKey: topKey, rooted: false };
|
|
4172
|
+
return { groupKey: head, rooted: true };
|
|
4173
|
+
}
|
|
4174
|
+
function placementBindOf(block) {
|
|
4175
|
+
if (!block || typeof block !== "object" || block.type !== "component") return null;
|
|
4176
|
+
const props = block.props;
|
|
4177
|
+
const bind = props?.bind;
|
|
4178
|
+
if (typeof bind === "string" && bind) return bind;
|
|
4179
|
+
const source = props?.source;
|
|
4180
|
+
if (!source || typeof source !== "object") return null;
|
|
4181
|
+
const groups = Object.values(source).filter((path) => typeof path === "string" && path.length > 0).map((path) => ({ top: topFieldKeyOf(path), group: fieldGroupOf(topFieldKeyOf(path), path === topFieldKeyOf(path)).groupKey }));
|
|
4182
|
+
return (groups.find((g) => g.group !== g.top) ?? groups[0])?.group ?? null;
|
|
4183
|
+
}
|
|
4184
|
+
var CODE_SLOT_PREFIX = "code:";
|
|
4185
|
+
var slotBindOf = (slot) => slot.startsWith(CODE_SLOT_PREFIX) ? slot.slice(CODE_SLOT_PREFIX.length) : slot;
|
|
4186
|
+
function planSections(slots, blocks) {
|
|
4187
|
+
if (!Array.isArray(blocks)) return slots.map((slot) => ({ slot, block: null }));
|
|
4188
|
+
const byBind = /* @__PURE__ */ new Map();
|
|
4189
|
+
for (const slot of slots) if (!byBind.has(slotBindOf(slot))) byBind.set(slotBindOf(slot), slot);
|
|
4190
|
+
const out = [];
|
|
4191
|
+
const named = /* @__PURE__ */ new Set();
|
|
4192
|
+
for (const block of blocks) {
|
|
4193
|
+
const bind = placementBindOf(block);
|
|
4194
|
+
if (bind === null) {
|
|
4195
|
+
out.push({ slot: null, block });
|
|
4196
|
+
continue;
|
|
4197
|
+
}
|
|
4198
|
+
const slot = byBind.get(bind);
|
|
4199
|
+
if (slot === void 0 || named.has(slot)) continue;
|
|
4200
|
+
named.add(slot);
|
|
4201
|
+
out.push({ slot, block });
|
|
4202
|
+
}
|
|
4203
|
+
const held = named.size > 0;
|
|
4204
|
+
slots.forEach((slot, n) => {
|
|
4205
|
+
if (named.has(slot) || out.some((item) => item.slot === slot)) return;
|
|
4206
|
+
if (held && !slot.startsWith(CODE_SLOT_PREFIX)) return;
|
|
4207
|
+
let at = 0;
|
|
4208
|
+
for (let before = n - 1; before >= 0; before--) {
|
|
4209
|
+
const found = out.findIndex((item) => item.slot === slots[before]);
|
|
4210
|
+
if (found >= 0) {
|
|
4211
|
+
at = found + 1;
|
|
4212
|
+
break;
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
out.splice(at, 0, { slot, block: null });
|
|
4216
|
+
});
|
|
4217
|
+
return out;
|
|
4218
|
+
}
|
|
3095
4219
|
export {
|
|
3096
4220
|
BLOCK_BINDINGS,
|
|
3097
4221
|
BREAKPOINTS,
|
|
@@ -3101,6 +4225,7 @@ export {
|
|
|
3101
4225
|
BUTTON_PLACEMENT_KEYS,
|
|
3102
4226
|
BUTTON_SIZES,
|
|
3103
4227
|
BUTTON_VARIANTS,
|
|
4228
|
+
CODE_SLOT_PREFIX,
|
|
3104
4229
|
COMPONENT_PROP_TYPES,
|
|
3105
4230
|
DIALOG_SELECTOR,
|
|
3106
4231
|
DOCK_BUDGET,
|
|
@@ -3113,16 +4238,26 @@ export {
|
|
|
3113
4238
|
LAYOUT_SECTION_ICONS,
|
|
3114
4239
|
LAYOUT_SECTION_ICON_SET,
|
|
3115
4240
|
LINK_RELS,
|
|
4241
|
+
LINK_VALUE_TYPES,
|
|
3116
4242
|
MAIN_SELECTOR,
|
|
3117
4243
|
NAVIGATION_SECTION_ID,
|
|
4244
|
+
OPTION_DESCRIPTION_MAX,
|
|
3118
4245
|
PAGE_CONTENT_ANCHOR_ID,
|
|
3119
4246
|
RESPONSIVE_CSS,
|
|
3120
4247
|
SECTION_DOCTRINE,
|
|
3121
4248
|
SECTION_SPACE_TOKENS,
|
|
4249
|
+
SLOT_CHILD_KEYS,
|
|
3122
4250
|
SLOT_ITEMS_CAP,
|
|
3123
4251
|
SLOT_MAX_ITEMS,
|
|
3124
4252
|
SPACE_BOTTOM_TOKENS,
|
|
3125
4253
|
SPACE_STEP_TOKENS,
|
|
4254
|
+
TABLE_COLUMN_TYPES,
|
|
4255
|
+
TABLE_JSONLD_TYPES,
|
|
4256
|
+
TABLE_LIMITS,
|
|
4257
|
+
TABLE_RESPONSIVE,
|
|
4258
|
+
TAG_HEADS,
|
|
4259
|
+
VIDEO_TRANSCRIPT_MAX,
|
|
4260
|
+
VIDEO_VALUE_KEYS,
|
|
3126
4261
|
anchorTargetRel,
|
|
3127
4262
|
blockStyleToColumnCss,
|
|
3128
4263
|
blockStyleToCss,
|
|
@@ -3131,6 +4266,8 @@ export {
|
|
|
3131
4266
|
buttonIconOf,
|
|
3132
4267
|
buttonIconSvg,
|
|
3133
4268
|
buttonLinkPairs,
|
|
4269
|
+
buttonLookValue,
|
|
4270
|
+
buttonSiteLookOf,
|
|
3134
4271
|
buttonValueOfInstance,
|
|
3135
4272
|
buttonValueProblem,
|
|
3136
4273
|
buttonVariantOf,
|
|
@@ -3141,29 +4278,60 @@ export {
|
|
|
3141
4278
|
dockBudget,
|
|
3142
4279
|
dockScreens,
|
|
3143
4280
|
expandBlockBindings,
|
|
4281
|
+
fieldGroupOf,
|
|
3144
4282
|
fieldStyleCssText,
|
|
3145
4283
|
fieldStyleToCss,
|
|
4284
|
+
formatNumber,
|
|
3146
4285
|
formatPropsAttribute,
|
|
3147
4286
|
getBlockType,
|
|
3148
4287
|
headingPaintCss,
|
|
3149
4288
|
hideOnAttr,
|
|
3150
4289
|
inWindow,
|
|
3151
4290
|
isBlock,
|
|
4291
|
+
isButtonLookValue,
|
|
3152
4292
|
isChromeLandmark,
|
|
4293
|
+
isLinkObject,
|
|
4294
|
+
isScriptHref,
|
|
4295
|
+
isSlotChild,
|
|
4296
|
+
isTableValue,
|
|
3153
4297
|
keyWords,
|
|
3154
4298
|
landmarkRegions,
|
|
4299
|
+
linkAnchorOf,
|
|
4300
|
+
linkHref,
|
|
4301
|
+
linkRefOf,
|
|
4302
|
+
linkValueProblem,
|
|
3155
4303
|
locatorFor,
|
|
4304
|
+
normalizeLinkValue,
|
|
4305
|
+
normalizeTableValue,
|
|
3156
4306
|
normalizeWindow,
|
|
4307
|
+
parseNumberText,
|
|
3157
4308
|
parsePropsAttribute,
|
|
3158
4309
|
pickFieldStyle,
|
|
4310
|
+
placementBindOf,
|
|
4311
|
+
planSections,
|
|
3159
4312
|
readButtonValue,
|
|
4313
|
+
readNumberFormat,
|
|
3160
4314
|
readPath,
|
|
3161
4315
|
regionOf,
|
|
4316
|
+
renderTableHtml,
|
|
3162
4317
|
safeCssUrl,
|
|
4318
|
+
slotBindOf,
|
|
4319
|
+
slotChildValues,
|
|
3163
4320
|
slotItems,
|
|
4321
|
+
slotPathString,
|
|
4322
|
+
slotShapeProblems,
|
|
3164
4323
|
spaceTokenCss,
|
|
3165
4324
|
splitButtonStyle,
|
|
4325
|
+
tableCellText,
|
|
4326
|
+
tablePathString,
|
|
4327
|
+
tableRefCells,
|
|
4328
|
+
tableSearchText,
|
|
4329
|
+
tableToMarkdown,
|
|
4330
|
+
tableValueProblems,
|
|
4331
|
+
topFieldKeyOf,
|
|
3166
4332
|
usesHideOn,
|
|
4333
|
+
videoUrlOf,
|
|
4334
|
+
videoValueProblem,
|
|
3167
4335
|
visibleBlocks,
|
|
3168
4336
|
visibleDeliveredLayout,
|
|
3169
4337
|
visibleLayoutStructure,
|