@wix/web5-core 1.57.2 → 1.59.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/dist/cjs/client/applyThemeOverrides.js +89 -0
- package/dist/cjs/client/applyThemeOverrides.js.map +1 -0
- package/dist/cjs/client/clientBundleUrl.js +83 -0
- package/dist/cjs/client/clientBundleUrl.js.map +1 -0
- package/dist/cjs/client/mergeClientConfig.js +59 -0
- package/dist/cjs/client/mergeClientConfig.js.map +1 -0
- package/dist/cjs/component/componentDefinitions/EntityCollectionSectionDefinition.js +159 -0
- package/dist/cjs/component/componentDefinitions/EntityCollectionSectionDefinition.js.map +1 -0
- package/dist/cjs/component/componentDefinitions/HeroEntitySectionDefinition.js +5 -2
- package/dist/cjs/component/componentDefinitions/HeroEntitySectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/HeroSectionDefinition.js +4 -1
- package/dist/cjs/component/componentDefinitions/HeroSectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/NextStepsSectionDefinition.js +35 -2
- package/dist/cjs/component/componentDefinitions/NextStepsSectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/TextBlockSectionDefinition.js +27 -1
- package/dist/cjs/component/componentDefinitions/TextBlockSectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/index.js +14 -1
- package/dist/cjs/component/componentDefinitions/index.js.map +1 -1
- package/dist/cjs/component/componentParser.js +38 -1
- package/dist/cjs/component/componentParser.js.map +1 -1
- package/dist/cjs/component/types.js.map +1 -1
- package/dist/cjs/components/ui/SearchSection.css +66 -9
- package/dist/cjs/components/ui/SearchSection.js +23 -19
- package/dist/cjs/components/ui/SearchSection.js.map +1 -1
- package/dist/cjs/hostScope.js.map +1 -1
- package/dist/cjs/index.js +23 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/styles/prompt-input-tokens.css +41 -34
- package/dist/cjs/utils/matchDebug.js +93 -0
- package/dist/cjs/utils/matchDebug.js.map +1 -0
- package/dist/esm/client/applyThemeOverrides.js +85 -0
- package/dist/esm/client/applyThemeOverrides.js.map +1 -0
- package/dist/esm/client/clientBundleUrl.js +75 -0
- package/dist/esm/client/clientBundleUrl.js.map +1 -0
- package/dist/esm/client/mergeClientConfig.js +55 -0
- package/dist/esm/client/mergeClientConfig.js.map +1 -0
- package/dist/esm/component/componentDefinitions/EntityCollectionSectionDefinition.js +154 -0
- package/dist/esm/component/componentDefinitions/EntityCollectionSectionDefinition.js.map +1 -0
- package/dist/esm/component/componentDefinitions/HeroEntitySectionDefinition.js +6 -3
- package/dist/esm/component/componentDefinitions/HeroEntitySectionDefinition.js.map +1 -1
- package/dist/esm/component/componentDefinitions/HeroSectionDefinition.js +5 -2
- package/dist/esm/component/componentDefinitions/HeroSectionDefinition.js.map +1 -1
- package/dist/esm/component/componentDefinitions/NextStepsSectionDefinition.js +36 -3
- package/dist/esm/component/componentDefinitions/NextStepsSectionDefinition.js.map +1 -1
- package/dist/esm/component/componentDefinitions/TextBlockSectionDefinition.js +27 -1
- package/dist/esm/component/componentDefinitions/TextBlockSectionDefinition.js.map +1 -1
- package/dist/esm/component/componentDefinitions/index.js +14 -1
- package/dist/esm/component/componentDefinitions/index.js.map +1 -1
- package/dist/esm/component/componentParser.js +38 -1
- package/dist/esm/component/componentParser.js.map +1 -1
- package/dist/esm/component/types.js.map +1 -1
- package/dist/esm/components/ui/SearchSection.css +66 -9
- package/dist/esm/components/ui/SearchSection.js +5 -1
- package/dist/esm/components/ui/SearchSection.js.map +1 -1
- package/dist/esm/hostScope.js.map +1 -1
- package/dist/esm/index.js +9 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/styles/prompt-input-tokens.css +41 -34
- package/dist/esm/utils/matchDebug.js +85 -0
- package/dist/esm/utils/matchDebug.js.map +1 -0
- package/dist/types/client/applyThemeOverrides.d.ts +19 -0
- package/dist/types/client/applyThemeOverrides.d.ts.map +1 -0
- package/dist/types/client/clientBundleUrl.d.ts +39 -0
- package/dist/types/client/clientBundleUrl.d.ts.map +1 -0
- package/dist/types/client/mergeClientConfig.d.ts +28 -0
- package/dist/types/client/mergeClientConfig.d.ts.map +1 -0
- package/dist/types/component/componentDefinitions/EntityCollectionSectionDefinition.d.ts +51 -0
- package/dist/types/component/componentDefinitions/EntityCollectionSectionDefinition.d.ts.map +1 -0
- package/dist/types/component/componentDefinitions/HeroEntitySectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentDefinitions/HeroSectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentDefinitions/NextStepsSectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentDefinitions/TextBlockSectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentDefinitions/index.d.ts +1 -0
- package/dist/types/component/componentDefinitions/index.d.ts.map +1 -1
- package/dist/types/component/componentParser.d.ts.map +1 -1
- package/dist/types/component/types.d.ts +7 -0
- package/dist/types/component/types.d.ts.map +1 -1
- package/dist/types/components/ui/SearchSection.d.ts.map +1 -1
- package/dist/types/hostScope.d.ts +11 -0
- package/dist/types/hostScope.d.ts.map +1 -1
- package/dist/types/index.d.ts +5 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/utils/matchDebug.d.ts +38 -0
- package/dist/types/utils/matchDebug.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/components/ui/SearchSection.css +66 -9
- package/src/styles/prompt-input-tokens.css +41 -34
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.THEME_OVERRIDE_TOKENS = void 0;
|
|
5
|
+
exports.applyThemeOverrides = applyThemeOverrides;
|
|
6
|
+
var _hostScope = require("../hostScope");
|
|
7
|
+
/**
|
|
8
|
+
* Runtime theme token injection (DL #131).
|
|
9
|
+
*
|
|
10
|
+
* `Configuration.themeOverrides` carries per-store CSS custom-property
|
|
11
|
+
* overrides (`--primary`, `--radius`, `--heading`, ...) computed from the
|
|
12
|
+
* store's own theme (e.g. the Shopify `settings_data.json` extractor). This
|
|
13
|
+
* applies them to the page so they win over the bundle's baked `theme.css`:
|
|
14
|
+
*
|
|
15
|
+
* - **Scoped, not `:root`**: the rule targets `WEB5_SCOPE`, the same
|
|
16
|
+
* `:is(#root, .w5-root, [data-web5-placement], .debug-view)` selector every
|
|
17
|
+
* compiled bundle stylesheet uses — host-page styling is never touched, and
|
|
18
|
+
* equal specificity + later document order makes the override win. Callers
|
|
19
|
+
* must therefore apply AFTER the client bundle's CSS is in the document.
|
|
20
|
+
* - **Open token set**: any sanely-shaped custom property is applied, so a
|
|
21
|
+
* template can grow new tokens without an FE or BE release. The mandatory
|
|
22
|
+
* `--` prefix means an override can only define custom properties — it can
|
|
23
|
+
* never set a real CSS property inside the scope. The server enforces the
|
|
24
|
+
* same shape (plus value sanitation) at write time.
|
|
25
|
+
* - **Values are inert**: entries are written with
|
|
26
|
+
* `CSSStyleDeclaration.setProperty`, never string-concatenated into CSS, so
|
|
27
|
+
* a hostile value cannot terminate the declaration or open a new rule.
|
|
28
|
+
* - **Idempotent**: one marker element per document, replaced wholesale on
|
|
29
|
+
* re-apply; empty/absent input removes it.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** A themeOverrides key: always a CSS custom property. */
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Writer-side shape for a theme override map. The catalog below documents
|
|
36
|
+
* the tokens the seed consumes today; the type stays open so new tokens
|
|
37
|
+
* flow end-to-end with no release anywhere but the seed.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The known token catalog — the CSS custom properties the seed's Tailwind
|
|
42
|
+
* bridge consumes today. GUIDANCE, NOT A GATE: writers (the Shopify theme
|
|
43
|
+
* extractor, admin tooling) should stick to these to produce visible effect,
|
|
44
|
+
* but `applyThemeOverrides` deliberately applies any well-formed custom
|
|
45
|
+
* property so the vocabulary can grow from the seed alone (matching the
|
|
46
|
+
* open-set validation on the server).
|
|
47
|
+
*/
|
|
48
|
+
const THEME_OVERRIDE_TOKENS = exports.THEME_OVERRIDE_TOKENS = new Set(['--background', '--foreground', '--card', '--card-foreground', '--popover', '--popover-foreground', '--primary', '--primary-foreground', '--secondary', '--secondary-foreground', '--muted', '--muted-foreground', '--accent', '--accent-foreground', '--border', '--input', '--ring', '--radius', '--font-sans', '--font-display', '--heading']);
|
|
49
|
+
|
|
50
|
+
/** Mirrors the server's Consts.ThemeOverrideKeyPattern. */
|
|
51
|
+
const KEY_PATTERN = /^--[a-z][a-z0-9-]{0,62}$/;
|
|
52
|
+
const MARKER_ATTR = 'data-web5-theme-overrides';
|
|
53
|
+
function applyThemeOverrides(overrides) {
|
|
54
|
+
if (typeof document === 'undefined') return;
|
|
55
|
+
const existing = document.head.querySelector(`style[${MARKER_ATTR}]`);
|
|
56
|
+
const entries = Object.entries(overrides ?? {}).filter(([key]) => {
|
|
57
|
+
const wellFormed = KEY_PATTERN.test(key);
|
|
58
|
+
if (!wellFormed) {
|
|
59
|
+
console.warn(`[web5-theme] Skipping malformed theme override key: ${key}`);
|
|
60
|
+
}
|
|
61
|
+
return wellFormed;
|
|
62
|
+
});
|
|
63
|
+
if (entries.length === 0) {
|
|
64
|
+
existing == null || existing.remove();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const style = document.createElement('style');
|
|
68
|
+
style.setAttribute(MARKER_ATTR, '');
|
|
69
|
+
document.head.appendChild(style);
|
|
70
|
+
const sheet = style.sheet;
|
|
71
|
+
if (!sheet) {
|
|
72
|
+
style.remove();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
sheet.insertRule(`${_hostScope.WEB5_SCOPE} {}`, 0);
|
|
76
|
+
const rule = sheet.cssRules[0];
|
|
77
|
+
for (const [key, value] of entries) {
|
|
78
|
+
try {
|
|
79
|
+
rule.style.setProperty(key, value);
|
|
80
|
+
} catch {
|
|
81
|
+
// An engine that rejects the value leaves the token at its baked
|
|
82
|
+
// default — degraded theming, never broken CSS.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Replace-on-reapply: the fresh element is appended (so it stays last in
|
|
86
|
+
// document order) before the stale one is dropped.
|
|
87
|
+
existing == null || existing.remove();
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=applyThemeOverrides.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_hostScope","require","THEME_OVERRIDE_TOKENS","exports","Set","KEY_PATTERN","MARKER_ATTR","applyThemeOverrides","overrides","document","existing","head","querySelector","entries","Object","filter","key","wellFormed","test","console","warn","length","remove","style","createElement","setAttribute","appendChild","sheet","insertRule","WEB5_SCOPE","rule","cssRules","value","setProperty"],"sources":["../../../src/client/applyThemeOverrides.ts"],"sourcesContent":["/**\n * Runtime theme token injection (DL #131).\n *\n * `Configuration.themeOverrides` carries per-store CSS custom-property\n * overrides (`--primary`, `--radius`, `--heading`, ...) computed from the\n * store's own theme (e.g. the Shopify `settings_data.json` extractor). This\n * applies them to the page so they win over the bundle's baked `theme.css`:\n *\n * - **Scoped, not `:root`**: the rule targets `WEB5_SCOPE`, the same\n * `:is(#root, .w5-root, [data-web5-placement], .debug-view)` selector every\n * compiled bundle stylesheet uses — host-page styling is never touched, and\n * equal specificity + later document order makes the override win. Callers\n * must therefore apply AFTER the client bundle's CSS is in the document.\n * - **Open token set**: any sanely-shaped custom property is applied, so a\n * template can grow new tokens without an FE or BE release. The mandatory\n * `--` prefix means an override can only define custom properties — it can\n * never set a real CSS property inside the scope. The server enforces the\n * same shape (plus value sanitation) at write time.\n * - **Values are inert**: entries are written with\n * `CSSStyleDeclaration.setProperty`, never string-concatenated into CSS, so\n * a hostile value cannot terminate the declaration or open a new rule.\n * - **Idempotent**: one marker element per document, replaced wholesale on\n * re-apply; empty/absent input removes it.\n */\nimport { WEB5_SCOPE } from '../hostScope';\n\n/** A themeOverrides key: always a CSS custom property. */\nexport type ThemeOverrideKey = `--${string}`;\n\n/**\n * Writer-side shape for a theme override map. The catalog below documents\n * the tokens the seed consumes today; the type stays open so new tokens\n * flow end-to-end with no release anywhere but the seed.\n */\nexport type ThemeOverrides = Record<ThemeOverrideKey, string>;\n\n/**\n * The known token catalog — the CSS custom properties the seed's Tailwind\n * bridge consumes today. GUIDANCE, NOT A GATE: writers (the Shopify theme\n * extractor, admin tooling) should stick to these to produce visible effect,\n * but `applyThemeOverrides` deliberately applies any well-formed custom\n * property so the vocabulary can grow from the seed alone (matching the\n * open-set validation on the server).\n */\nexport const THEME_OVERRIDE_TOKENS: ReadonlySet<string> = new Set([\n '--background', '--foreground',\n '--card', '--card-foreground',\n '--popover', '--popover-foreground',\n '--primary', '--primary-foreground',\n '--secondary', '--secondary-foreground',\n '--muted', '--muted-foreground',\n '--accent', '--accent-foreground',\n '--border', '--input', '--ring',\n '--radius',\n '--font-sans', '--font-display',\n '--heading',\n]);\n\n/** Mirrors the server's Consts.ThemeOverrideKeyPattern. */\nconst KEY_PATTERN = /^--[a-z][a-z0-9-]{0,62}$/;\n\nconst MARKER_ATTR = 'data-web5-theme-overrides';\n\nexport function applyThemeOverrides(\n overrides?: Record<string, string> | null,\n): void {\n if (typeof document === 'undefined') return;\n\n const existing = document.head.querySelector(`style[${MARKER_ATTR}]`);\n const entries = Object.entries(overrides ?? {}).filter(([key]) => {\n const wellFormed = KEY_PATTERN.test(key);\n if (!wellFormed) {\n console.warn(`[web5-theme] Skipping malformed theme override key: ${key}`);\n }\n return wellFormed;\n });\n\n if (entries.length === 0) {\n existing?.remove();\n return;\n }\n\n const style = document.createElement('style');\n style.setAttribute(MARKER_ATTR, '');\n document.head.appendChild(style);\n const sheet = style.sheet;\n if (!sheet) {\n style.remove();\n return;\n }\n sheet.insertRule(`${WEB5_SCOPE} {}`, 0);\n const rule = sheet.cssRules[0] as CSSStyleRule;\n for (const [key, value] of entries) {\n try {\n rule.style.setProperty(key, value);\n } catch {\n // An engine that rejects the value leaves the token at its baked\n // default — degraded theming, never broken CSS.\n }\n }\n // Replace-on-reapply: the fresh element is appended (so it stays last in\n // document order) before the stale one is dropped.\n existing?.remove();\n}\n"],"mappings":";;;;;AAwBA,IAAAA,UAAA,GAAAC,OAAA;AAxBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;;AAGA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMC,qBAA0C,GAAAC,OAAA,CAAAD,qBAAA,GAAG,IAAIE,GAAG,CAAC,CAChE,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,mBAAmB,EAC7B,WAAW,EAAE,sBAAsB,EACnC,WAAW,EAAE,sBAAsB,EACnC,aAAa,EAAE,wBAAwB,EACvC,SAAS,EAAE,oBAAoB,EAC/B,UAAU,EAAE,qBAAqB,EACjC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAC/B,UAAU,EACV,aAAa,EAAE,gBAAgB,EAC/B,WAAW,CACZ,CAAC;;AAEF;AACA,MAAMC,WAAW,GAAG,0BAA0B;AAE9C,MAAMC,WAAW,GAAG,2BAA2B;AAExC,SAASC,mBAAmBA,CACjCC,SAAyC,EACnC;EACN,IAAI,OAAOC,QAAQ,KAAK,WAAW,EAAE;EAErC,MAAMC,QAAQ,GAAGD,QAAQ,CAACE,IAAI,CAACC,aAAa,CAAC,SAASN,WAAW,GAAG,CAAC;EACrE,MAAMO,OAAO,GAAGC,MAAM,CAACD,OAAO,CAACL,SAAS,IAAI,CAAC,CAAC,CAAC,CAACO,MAAM,CAAC,CAAC,CAACC,GAAG,CAAC,KAAK;IAChE,MAAMC,UAAU,GAAGZ,WAAW,CAACa,IAAI,CAACF,GAAG,CAAC;IACxC,IAAI,CAACC,UAAU,EAAE;MACfE,OAAO,CAACC,IAAI,CAAC,uDAAuDJ,GAAG,EAAE,CAAC;IAC5E;IACA,OAAOC,UAAU;EACnB,CAAC,CAAC;EAEF,IAAIJ,OAAO,CAACQ,MAAM,KAAK,CAAC,EAAE;IACxBX,QAAQ,YAARA,QAAQ,CAAEY,MAAM,CAAC,CAAC;IAClB;EACF;EAEA,MAAMC,KAAK,GAAGd,QAAQ,CAACe,aAAa,CAAC,OAAO,CAAC;EAC7CD,KAAK,CAACE,YAAY,CAACnB,WAAW,EAAE,EAAE,CAAC;EACnCG,QAAQ,CAACE,IAAI,CAACe,WAAW,CAACH,KAAK,CAAC;EAChC,MAAMI,KAAK,GAAGJ,KAAK,CAACI,KAAK;EACzB,IAAI,CAACA,KAAK,EAAE;IACVJ,KAAK,CAACD,MAAM,CAAC,CAAC;IACd;EACF;EACAK,KAAK,CAACC,UAAU,CAAC,GAAGC,qBAAU,KAAK,EAAE,CAAC,CAAC;EACvC,MAAMC,IAAI,GAAGH,KAAK,CAACI,QAAQ,CAAC,CAAC,CAAiB;EAC9C,KAAK,MAAM,CAACf,GAAG,EAAEgB,KAAK,CAAC,IAAInB,OAAO,EAAE;IAClC,IAAI;MACFiB,IAAI,CAACP,KAAK,CAACU,WAAW,CAACjB,GAAG,EAAEgB,KAAK,CAAC;IACpC,CAAC,CAAC,MAAM;MACN;MACA;IAAA;EAEJ;EACA;EACA;EACAtB,QAAQ,YAARA,QAAQ,CAAEY,MAAM,CAAC,CAAC;AACpB","ignoreList":[]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.TEMPLATES_MANIFEST_URL = exports.TEMPLATES_CDN_BASE = void 0;
|
|
5
|
+
exports.getTemplateOverride = getTemplateOverride;
|
|
6
|
+
exports.isTemplatePickerRequested = isTemplatePickerRequested;
|
|
7
|
+
exports.isValidTemplateId = isValidTemplateId;
|
|
8
|
+
exports.resolveClientBundleUrl = resolveClientBundleUrl;
|
|
9
|
+
/**
|
|
10
|
+
* Client-UMD URL resolution, shared between `web50-server-ui` (the main app)
|
|
11
|
+
* and `embed-placement` (the storefront widget) — DL #094 (per-msid) + DL #129
|
|
12
|
+
* (per-template). Centralized here for the same reason as
|
|
13
|
+
* `clientBundleOverride.ts`: both callers must derive identical URLs, and
|
|
14
|
+
* divergence on which bundle a tenant executes would be a real bug.
|
|
15
|
+
*
|
|
16
|
+
* Two tiers (DL #129):
|
|
17
|
+
* - `templateId` present (DIY store) → the shared per-template bundle,
|
|
18
|
+
* `<cdn>/templates/<id>/production/…`. Every store on a template runs the
|
|
19
|
+
* same artifact; `production` is the promoted channel.
|
|
20
|
+
* - `templateId` absent (customized tier: feature-com, circana, smallflower)
|
|
21
|
+
* → the per-msid bundle at `<cdn>/<msid>/latest/…`, byte-identical to the
|
|
22
|
+
* pre-template behavior.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const CLIENT_BUNDLE_CDN = 'https://d13cktzsgh0qls.cloudfront.net';
|
|
26
|
+
const CLIENT_BUNDLE_FILENAME = 'w5-components.umd.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The template catalog the onboarding gallery and the preview picker render —
|
|
30
|
+
* published by web5-seed-1's CI (`templates/manifest.json` + previews),
|
|
31
|
+
* CORS-open. Thumbnail paths inside are relative to `TEMPLATES_CDN_BASE`.
|
|
32
|
+
*/
|
|
33
|
+
const TEMPLATES_CDN_BASE = exports.TEMPLATES_CDN_BASE = `${CLIENT_BUNDLE_CDN}/templates`;
|
|
34
|
+
const TEMPLATES_MANIFEST_URL = exports.TEMPLATES_MANIFEST_URL = `${TEMPLATES_CDN_BASE}/manifest.json`;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Template ids are kebab-case slugs from `templates/manifest.json`
|
|
38
|
+
* (`web5-seed-1`). The id becomes a CDN path segment, so anything else is a
|
|
39
|
+
* config error — resolve falls back to the per-msid path (fail-soft, and the
|
|
40
|
+
* store keeps rendering) rather than fetching a malformed URL.
|
|
41
|
+
*/
|
|
42
|
+
const TEMPLATE_ID_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
43
|
+
function isValidTemplateId(id) {
|
|
44
|
+
return id.length <= 64 && TEMPLATE_ID_PATTERN.test(id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `?web5Template=<id>` — client-side template override (DL #129 Q10), the
|
|
49
|
+
* mechanism behind the live-preview picker: reload with the param and the page
|
|
50
|
+
* renders under that template's shared bundle, no Configuration write.
|
|
51
|
+
*
|
|
52
|
+
* Strictly narrower than `?clientBundleUrl=`: the value is a kebab-validated
|
|
53
|
+
* id interpolated into the fixed CDN template prefix, so it can only ever
|
|
54
|
+
* select one of our own template bundles — purely presentational, harmless if
|
|
55
|
+
* a visitor sets it by hand. Precedence at the call sites:
|
|
56
|
+
* `clientBundleUrl` override > this > `Configuration.templateId`.
|
|
57
|
+
*/
|
|
58
|
+
function getTemplateOverride(search) {
|
|
59
|
+
const value = new URLSearchParams(search).get('web5Template');
|
|
60
|
+
if (!value) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (!isValidTemplateId(value)) {
|
|
64
|
+
console.warn(`[web5-core] Ignoring malformed ?web5Template= override "${value}".`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** `?web5TemplatePicker=1` — should the preview picker overlay render? */
|
|
71
|
+
function isTemplatePickerRequested(search) {
|
|
72
|
+
return new URLSearchParams(search).get('web5TemplatePicker') === '1';
|
|
73
|
+
}
|
|
74
|
+
function resolveClientBundleUrl(msid, templateId) {
|
|
75
|
+
if (templateId) {
|
|
76
|
+
if (TEMPLATE_ID_PATTERN.test(templateId)) {
|
|
77
|
+
return `${CLIENT_BUNDLE_CDN}/templates/${templateId}/production/${CLIENT_BUNDLE_FILENAME}`;
|
|
78
|
+
}
|
|
79
|
+
console.warn(`[web5-core] Ignoring malformed templateId "${templateId}" — falling back to the per-msid bundle.`);
|
|
80
|
+
}
|
|
81
|
+
return `${CLIENT_BUNDLE_CDN}/${msid}/latest/${CLIENT_BUNDLE_FILENAME}`;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=clientBundleUrl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["CLIENT_BUNDLE_CDN","CLIENT_BUNDLE_FILENAME","TEMPLATES_CDN_BASE","exports","TEMPLATES_MANIFEST_URL","TEMPLATE_ID_PATTERN","isValidTemplateId","id","length","test","getTemplateOverride","search","value","URLSearchParams","get","console","warn","isTemplatePickerRequested","resolveClientBundleUrl","msid","templateId"],"sources":["../../../src/client/clientBundleUrl.ts"],"sourcesContent":["/**\n * Client-UMD URL resolution, shared between `web50-server-ui` (the main app)\n * and `embed-placement` (the storefront widget) — DL #094 (per-msid) + DL #129\n * (per-template). Centralized here for the same reason as\n * `clientBundleOverride.ts`: both callers must derive identical URLs, and\n * divergence on which bundle a tenant executes would be a real bug.\n *\n * Two tiers (DL #129):\n * - `templateId` present (DIY store) → the shared per-template bundle,\n * `<cdn>/templates/<id>/production/…`. Every store on a template runs the\n * same artifact; `production` is the promoted channel.\n * - `templateId` absent (customized tier: feature-com, circana, smallflower)\n * → the per-msid bundle at `<cdn>/<msid>/latest/…`, byte-identical to the\n * pre-template behavior.\n */\n\nconst CLIENT_BUNDLE_CDN = 'https://d13cktzsgh0qls.cloudfront.net';\nconst CLIENT_BUNDLE_FILENAME = 'w5-components.umd.js';\n\n/**\n * The template catalog the onboarding gallery and the preview picker render —\n * published by web5-seed-1's CI (`templates/manifest.json` + previews),\n * CORS-open. Thumbnail paths inside are relative to `TEMPLATES_CDN_BASE`.\n */\nexport const TEMPLATES_CDN_BASE = `${CLIENT_BUNDLE_CDN}/templates`;\nexport const TEMPLATES_MANIFEST_URL = `${TEMPLATES_CDN_BASE}/manifest.json`;\n\n/**\n * Template ids are kebab-case slugs from `templates/manifest.json`\n * (`web5-seed-1`). The id becomes a CDN path segment, so anything else is a\n * config error — resolve falls back to the per-msid path (fail-soft, and the\n * store keeps rendering) rather than fetching a malformed URL.\n */\nconst TEMPLATE_ID_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\nexport function isValidTemplateId(id: string): boolean {\n return id.length <= 64 && TEMPLATE_ID_PATTERN.test(id);\n}\n\n/**\n * `?web5Template=<id>` — client-side template override (DL #129 Q10), the\n * mechanism behind the live-preview picker: reload with the param and the page\n * renders under that template's shared bundle, no Configuration write.\n *\n * Strictly narrower than `?clientBundleUrl=`: the value is a kebab-validated\n * id interpolated into the fixed CDN template prefix, so it can only ever\n * select one of our own template bundles — purely presentational, harmless if\n * a visitor sets it by hand. Precedence at the call sites:\n * `clientBundleUrl` override > this > `Configuration.templateId`.\n */\nexport function getTemplateOverride(search: string): string | null {\n const value = new URLSearchParams(search).get('web5Template');\n if (!value) {\n return null;\n }\n if (!isValidTemplateId(value)) {\n console.warn(\n `[web5-core] Ignoring malformed ?web5Template= override \"${value}\".`,\n );\n return null;\n }\n return value;\n}\n\n/** `?web5TemplatePicker=1` — should the preview picker overlay render? */\nexport function isTemplatePickerRequested(search: string): boolean {\n return new URLSearchParams(search).get('web5TemplatePicker') === '1';\n}\n\nexport function resolveClientBundleUrl(\n msid: string,\n templateId?: string | null,\n): string {\n if (templateId) {\n if (TEMPLATE_ID_PATTERN.test(templateId)) {\n return `${CLIENT_BUNDLE_CDN}/templates/${templateId}/production/${CLIENT_BUNDLE_FILENAME}`;\n }\n console.warn(\n `[web5-core] Ignoring malformed templateId \"${templateId}\" — falling back to the per-msid bundle.`,\n );\n }\n return `${CLIENT_BUNDLE_CDN}/${msid}/latest/${CLIENT_BUNDLE_FILENAME}`;\n}\n"],"mappings":";;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,MAAMA,iBAAiB,GAAG,uCAAuC;AACjE,MAAMC,sBAAsB,GAAG,sBAAsB;;AAErD;AACA;AACA;AACA;AACA;AACO,MAAMC,kBAAkB,GAAAC,OAAA,CAAAD,kBAAA,GAAG,GAAGF,iBAAiB,YAAY;AAC3D,MAAMI,sBAAsB,GAAAD,OAAA,CAAAC,sBAAA,GAAG,GAAGF,kBAAkB,gBAAgB;;AAE3E;AACA;AACA;AACA;AACA;AACA;AACA,MAAMG,mBAAmB,GAAG,0BAA0B;AAE/C,SAASC,iBAAiBA,CAACC,EAAU,EAAW;EACrD,OAAOA,EAAE,CAACC,MAAM,IAAI,EAAE,IAAIH,mBAAmB,CAACI,IAAI,CAACF,EAAE,CAAC;AACxD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASG,mBAAmBA,CAACC,MAAc,EAAiB;EACjE,MAAMC,KAAK,GAAG,IAAIC,eAAe,CAACF,MAAM,CAAC,CAACG,GAAG,CAAC,cAAc,CAAC;EAC7D,IAAI,CAACF,KAAK,EAAE;IACV,OAAO,IAAI;EACb;EACA,IAAI,CAACN,iBAAiB,CAACM,KAAK,CAAC,EAAE;IAC7BG,OAAO,CAACC,IAAI,CACV,2DAA2DJ,KAAK,IAClE,CAAC;IACD,OAAO,IAAI;EACb;EACA,OAAOA,KAAK;AACd;;AAEA;AACO,SAASK,yBAAyBA,CAACN,MAAc,EAAW;EACjE,OAAO,IAAIE,eAAe,CAACF,MAAM,CAAC,CAACG,GAAG,CAAC,oBAAoB,CAAC,KAAK,GAAG;AACtE;AAEO,SAASI,sBAAsBA,CACpCC,IAAY,EACZC,UAA0B,EAClB;EACR,IAAIA,UAAU,EAAE;IACd,IAAIf,mBAAmB,CAACI,IAAI,CAACW,UAAU,CAAC,EAAE;MACxC,OAAO,GAAGpB,iBAAiB,cAAcoB,UAAU,eAAenB,sBAAsB,EAAE;IAC5F;IACAc,OAAO,CAACC,IAAI,CACV,8CAA8CI,UAAU,0CAC1D,CAAC;EACH;EACA,OAAO,GAAGpB,iBAAiB,IAAImB,IAAI,WAAWlB,sBAAsB,EAAE;AACxE","ignoreList":[]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.mergeClientConfig = mergeClientConfig;
|
|
5
|
+
/**
|
|
6
|
+
* Universal client-config merge (DL #129 Q3/Q7): the loaded client bundle's
|
|
7
|
+
* `clientConfig` is the default; the backend `Configuration.clientConfig`
|
|
8
|
+
* overrides it **per field, only where a field was actually returned**.
|
|
9
|
+
*
|
|
10
|
+
* One rule for both tiers, shared between `web50-server-ui` and
|
|
11
|
+
* `embed-placement` (the two places that load `w5-components.umd.js`):
|
|
12
|
+
* - Customized-tier clients (feature-com, circana): the backend returns no
|
|
13
|
+
* `clientConfig`, so the merge is identity — the bundle's baked config
|
|
14
|
+
* passes through byte-identical (same reference, see spec).
|
|
15
|
+
* - DIY/template stores: the backend supplies per-store deltas that grow
|
|
16
|
+
* field-by-field as the client-config port (Phase 3b) lands; each ported
|
|
17
|
+
* field takes effect here with no FE change.
|
|
18
|
+
*
|
|
19
|
+
* Semantics — presence, not truthiness:
|
|
20
|
+
* - The wire omits unset proto fields, so key-absence means "not returned"
|
|
21
|
+
* → bundle value kept. A key that IS present wins even when falsy
|
|
22
|
+
* (`''`, `false`, `0`) — it was explicitly set.
|
|
23
|
+
* - `undefined`/`null` values are treated as absent (proto3 JSON never
|
|
24
|
+
* emits null for wrapper types; defensive against hand-written configs).
|
|
25
|
+
* - Plain objects merge per leaf; arrays replace wholesale; class
|
|
26
|
+
* instances / functions replace wholesale.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
function isPlainObject(value) {
|
|
30
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
31
|
+
}
|
|
32
|
+
function mergeClientConfig(bundleConfig,
|
|
33
|
+
// `Record<string, unknown>` admitted alongside `DeepPartial<T>`: the wire
|
|
34
|
+
// value is untyped JSON (`Configuration.clientConfig`), so callers hold it
|
|
35
|
+
// as a plain record — the runtime presence checks do the validation.
|
|
36
|
+
backendConfig) {
|
|
37
|
+
if (backendConfig === undefined || backendConfig === null) {
|
|
38
|
+
return bundleConfig;
|
|
39
|
+
}
|
|
40
|
+
if (!isPlainObject(backendConfig)) {
|
|
41
|
+
return bundleConfig;
|
|
42
|
+
}
|
|
43
|
+
const keys = Object.keys(backendConfig).filter(key => backendConfig[key] !== undefined && backendConfig[key] !== null);
|
|
44
|
+
// Empty delta → identity, same reference: the byte-identical passthrough
|
|
45
|
+
// guarantee for clients whose Configuration carries no clientConfig.
|
|
46
|
+
if (keys.length === 0) {
|
|
47
|
+
return bundleConfig;
|
|
48
|
+
}
|
|
49
|
+
const result = {
|
|
50
|
+
...bundleConfig
|
|
51
|
+
};
|
|
52
|
+
for (const key of keys) {
|
|
53
|
+
const backendValue = backendConfig[key];
|
|
54
|
+
const bundleValue = bundleConfig[key];
|
|
55
|
+
result[key] = isPlainObject(backendValue) && isPlainObject(bundleValue) ? mergeClientConfig(bundleValue, backendValue) : backendValue;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=mergeClientConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["isPlainObject","value","Array","isArray","Object","getPrototypeOf","prototype","mergeClientConfig","bundleConfig","backendConfig","undefined","keys","filter","key","length","result","backendValue","bundleValue"],"sources":["../../../src/client/mergeClientConfig.ts"],"sourcesContent":["/**\n * Universal client-config merge (DL #129 Q3/Q7): the loaded client bundle's\n * `clientConfig` is the default; the backend `Configuration.clientConfig`\n * overrides it **per field, only where a field was actually returned**.\n *\n * One rule for both tiers, shared between `web50-server-ui` and\n * `embed-placement` (the two places that load `w5-components.umd.js`):\n * - Customized-tier clients (feature-com, circana): the backend returns no\n * `clientConfig`, so the merge is identity — the bundle's baked config\n * passes through byte-identical (same reference, see spec).\n * - DIY/template stores: the backend supplies per-store deltas that grow\n * field-by-field as the client-config port (Phase 3b) lands; each ported\n * field takes effect here with no FE change.\n *\n * Semantics — presence, not truthiness:\n * - The wire omits unset proto fields, so key-absence means \"not returned\"\n * → bundle value kept. A key that IS present wins even when falsy\n * (`''`, `false`, `0`) — it was explicitly set.\n * - `undefined`/`null` values are treated as absent (proto3 JSON never\n * emits null for wrapper types; defensive against hand-written configs).\n * - Plain objects merge per leaf; arrays replace wholesale; class\n * instances / functions replace wholesale.\n */\n\nexport type DeepPartial<T> = T extends (infer U)[]\n ? U[]\n : T extends object\n ? { [K in keyof T]?: DeepPartial<T[K]> }\n : T;\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n (Object.getPrototypeOf(value) === Object.prototype ||\n Object.getPrototypeOf(value) === null)\n );\n}\n\nexport function mergeClientConfig<T extends object>(\n bundleConfig: T,\n // `Record<string, unknown>` admitted alongside `DeepPartial<T>`: the wire\n // value is untyped JSON (`Configuration.clientConfig`), so callers hold it\n // as a plain record — the runtime presence checks do the validation.\n backendConfig?: DeepPartial<T> | Record<string, unknown> | null,\n): T {\n if (backendConfig === undefined || backendConfig === null) {\n return bundleConfig;\n }\n if (!isPlainObject(backendConfig)) {\n return bundleConfig;\n }\n const keys = Object.keys(backendConfig).filter(\n (key) => (backendConfig as Record<string, unknown>)[key] !== undefined &&\n (backendConfig as Record<string, unknown>)[key] !== null,\n );\n // Empty delta → identity, same reference: the byte-identical passthrough\n // guarantee for clients whose Configuration carries no clientConfig.\n if (keys.length === 0) {\n return bundleConfig;\n }\n\n const result: Record<string, unknown> = {\n ...(bundleConfig as Record<string, unknown>),\n };\n for (const key of keys) {\n const backendValue = (backendConfig as Record<string, unknown>)[key];\n const bundleValue = (bundleConfig as Record<string, unknown>)[key];\n result[key] =\n isPlainObject(backendValue) && isPlainObject(bundleValue)\n ? mergeClientConfig(bundleValue, backendValue)\n : backendValue;\n }\n return result as T;\n}\n"],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAQA,SAASA,aAAaA,CAACC,KAAc,EAAoC;EACvE,OACE,OAAOA,KAAK,KAAK,QAAQ,IACzBA,KAAK,KAAK,IAAI,IACd,CAACC,KAAK,CAACC,OAAO,CAACF,KAAK,CAAC,KACpBG,MAAM,CAACC,cAAc,CAACJ,KAAK,CAAC,KAAKG,MAAM,CAACE,SAAS,IAChDF,MAAM,CAACC,cAAc,CAACJ,KAAK,CAAC,KAAK,IAAI,CAAC;AAE5C;AAEO,SAASM,iBAAiBA,CAC/BC,YAAe;AACf;AACA;AACA;AACAC,aAA+D,EAC5D;EACH,IAAIA,aAAa,KAAKC,SAAS,IAAID,aAAa,KAAK,IAAI,EAAE;IACzD,OAAOD,YAAY;EACrB;EACA,IAAI,CAACR,aAAa,CAACS,aAAa,CAAC,EAAE;IACjC,OAAOD,YAAY;EACrB;EACA,MAAMG,IAAI,GAAGP,MAAM,CAACO,IAAI,CAACF,aAAa,CAAC,CAACG,MAAM,CAC3CC,GAAG,IAAMJ,aAAa,CAA6BI,GAAG,CAAC,KAAKH,SAAS,IACnED,aAAa,CAA6BI,GAAG,CAAC,KAAK,IACxD,CAAC;EACD;EACA;EACA,IAAIF,IAAI,CAACG,MAAM,KAAK,CAAC,EAAE;IACrB,OAAON,YAAY;EACrB;EAEA,MAAMO,MAA+B,GAAG;IACtC,GAAIP;EACN,CAAC;EACD,KAAK,MAAMK,GAAG,IAAIF,IAAI,EAAE;IACtB,MAAMK,YAAY,GAAIP,aAAa,CAA6BI,GAAG,CAAC;IACpE,MAAMI,WAAW,GAAIT,YAAY,CAA6BK,GAAG,CAAC;IAClEE,MAAM,CAACF,GAAG,CAAC,GACTb,aAAa,CAACgB,YAAY,CAAC,IAAIhB,aAAa,CAACiB,WAAW,CAAC,GACrDV,iBAAiB,CAACU,WAAW,EAAED,YAAY,CAAC,GAC5CA,YAAY;EACpB;EACA,OAAOD,MAAM;AACf","ignoreList":[]}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
exports.__esModule = true;
|
|
5
|
+
exports.EntityCollectionSectionDefinition = void 0;
|
|
6
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
7
|
+
var _parseUtils = require("./parse-utils");
|
|
8
|
+
var _diagnosticTypes = require("../diagnosticTypes");
|
|
9
|
+
/**
|
|
10
|
+
* The semantic entity type this definition claims — the `<type>` segment of
|
|
11
|
+
* `web5://entity/<type>/<id>` (see the client `entityConfig.entityTypes` map).
|
|
12
|
+
*/
|
|
13
|
+
const COLLECTION_ENTITY_TYPE = 'collection';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Entity section restricted to **collection** entities
|
|
17
|
+
* (`web5://entity/collection/<id>`).
|
|
18
|
+
*
|
|
19
|
+
* Same markdown shapes as {@link EntitySectionDefinition} — an `h2`, optional
|
|
20
|
+
* intro/callout, then entity links either at the top level or as list items —
|
|
21
|
+
* but every entity link must be a collection. That makes it a strictly narrower
|
|
22
|
+
* match, so it MUST be registered **before** `EntitySectionDefinition` (which
|
|
23
|
+
* claims any `link[entityType]`, i.e. product and article sections too). Once
|
|
24
|
+
* `EntitySectionDefinition` matches, the block is consumed and no later
|
|
25
|
+
* definition is consulted.
|
|
26
|
+
*
|
|
27
|
+
* Mixed blocks (a collection next to a product or an article) fall through:
|
|
28
|
+
* the patterns only cover consecutive collection links, and `parse()` returns
|
|
29
|
+
* `null` when a non-collection entity slipped into the matched slice, handing
|
|
30
|
+
* the block to `EntitySectionDefinition`.
|
|
31
|
+
*
|
|
32
|
+
* Props are the shared {@link EntityCompProps} shape, so a client renders it
|
|
33
|
+
* with a dedicated `EntityCollectionSection` component while product/article
|
|
34
|
+
* sections keep using their own components under `EntitySectionDefinition`.
|
|
35
|
+
*/
|
|
36
|
+
class EntityCollectionSectionDefinition {
|
|
37
|
+
constructor() {
|
|
38
|
+
(0, _defineProperty2.default)(this, "sectionType", 'entityCollection');
|
|
39
|
+
/**
|
|
40
|
+
* One pattern, matching the shape the orchestrator actually emits for a
|
|
41
|
+
* collections block:
|
|
42
|
+
*
|
|
43
|
+
* <!-- section:entities, semantic:items -->
|
|
44
|
+
* ## Curated collections by theme
|
|
45
|
+
* - [](web5://entity/collection/<id>) description
|
|
46
|
+
*
|
|
47
|
+
* The head absorbs the section descriptor and any intro copy or ECHO
|
|
48
|
+
* callout; each list item is a collection link, an optional markdown image,
|
|
49
|
+
* then its description and optional per-item callout. A trailing
|
|
50
|
+
* section-level callout is intentionally left outside the pattern so it
|
|
51
|
+
* falls through to the downstream CalloutSection.
|
|
52
|
+
*
|
|
53
|
+
* Deliberately narrow: anything else (collection links at the top level
|
|
54
|
+
* rather than in a list, a block mixing entity types) falls through to
|
|
55
|
+
* `EntitySectionDefinition`, which is what handled it before this
|
|
56
|
+
* definition existed.
|
|
57
|
+
*/
|
|
58
|
+
(0, _defineProperty2.default)(this, "patterns", ['html_comment* > h2 > (html_comment | t | callout)* > list{item[1-]:link[entityType="collection"] > image? > (t | callout)*}']);
|
|
59
|
+
(0, _defineProperty2.default)(this, "defaults", {
|
|
60
|
+
title: 'Collections',
|
|
61
|
+
items: [{
|
|
62
|
+
entityId: 'example-1',
|
|
63
|
+
entityType: COLLECTION_ENTITY_TYPE,
|
|
64
|
+
entityUrl: 'web5://entity/collection/example-1',
|
|
65
|
+
linkText: 'Example collection'
|
|
66
|
+
}]
|
|
67
|
+
});
|
|
68
|
+
(0, _defineProperty2.default)(this, "fixtures", [{
|
|
69
|
+
markdown: '## Shop the collections\n\nCurated edits for the season.\n\n- [Running](web5://entity/collection/pUx3mVQ1TmyN2mVYQqQ8Jg) Shoes and kit built for the road\n- [Trail](web5://entity/collection/Rr8kQyO0S1uH2nQ0mQ2AqQ) Grip and protection off-road',
|
|
70
|
+
expected: {
|
|
71
|
+
title: 'Shop the collections',
|
|
72
|
+
description: 'Curated edits for the season.',
|
|
73
|
+
items: [{
|
|
74
|
+
entityId: 'pUx3mVQ1TmyN2mVYQqQ8Jg',
|
|
75
|
+
entityType: 'collection',
|
|
76
|
+
entityUrl: 'web5://entity/collection/pUx3mVQ1TmyN2mVYQqQ8Jg',
|
|
77
|
+
linkText: 'Running',
|
|
78
|
+
description: 'Shoes and kit built for the road'
|
|
79
|
+
}, {
|
|
80
|
+
entityId: 'Rr8kQyO0S1uH2nQ0mQ2AqQ',
|
|
81
|
+
entityType: 'collection',
|
|
82
|
+
entityUrl: 'web5://entity/collection/Rr8kQyO0S1uH2nQ0mQ2AqQ',
|
|
83
|
+
linkText: 'Trail',
|
|
84
|
+
description: 'Grip and protection off-road'
|
|
85
|
+
}]
|
|
86
|
+
}
|
|
87
|
+
}, {
|
|
88
|
+
// The shape the orchestrator emits: a section descriptor, empty link
|
|
89
|
+
// text, and a Shopify gid (which is itself a `://` url) as the entity id.
|
|
90
|
+
markdown: '<!-- section:entities, semantic:items -->\n## Curated collections by theme\n- [](web5://entity/collection/gid://shopify/Collection/347895267466) storewide assortment spanning the full product range\n- [](web5://entity/collection/gid://shopify/Collection/348001501322) artistic **Watercolor** candles with bright vessels',
|
|
91
|
+
expected: {
|
|
92
|
+
title: 'Curated collections by theme',
|
|
93
|
+
items: [{
|
|
94
|
+
entityId: 'gid://shopify/Collection/347895267466',
|
|
95
|
+
entityType: 'collection',
|
|
96
|
+
entityUrl: 'web5://entity/collection/gid://shopify/Collection/347895267466',
|
|
97
|
+
description: 'storewide assortment spanning the full product range'
|
|
98
|
+
}, {
|
|
99
|
+
entityId: 'gid://shopify/Collection/348001501322',
|
|
100
|
+
entityType: 'collection',
|
|
101
|
+
entityUrl: 'web5://entity/collection/gid://shopify/Collection/348001501322',
|
|
102
|
+
// Inline emphasis survives — descriptions keep their markdown.
|
|
103
|
+
description: 'artistic **Watercolor** candles with bright vessels'
|
|
104
|
+
}]
|
|
105
|
+
}
|
|
106
|
+
}, {
|
|
107
|
+
// Single collection — the full-width banner shape.
|
|
108
|
+
markdown: '## Winter essentials\n\n- [Shop winter](web5://entity/collection/aB1cD2eF3gH4iJ5kL6mN7o) Layers for the cold months',
|
|
109
|
+
expected: {
|
|
110
|
+
title: 'Winter essentials',
|
|
111
|
+
items: [{
|
|
112
|
+
entityId: 'aB1cD2eF3gH4iJ5kL6mN7o',
|
|
113
|
+
entityType: 'collection',
|
|
114
|
+
entityUrl: 'web5://entity/collection/aB1cD2eF3gH4iJ5kL6mN7o',
|
|
115
|
+
linkText: 'Shop winter',
|
|
116
|
+
description: 'Layers for the cold months'
|
|
117
|
+
}]
|
|
118
|
+
}
|
|
119
|
+
}]);
|
|
120
|
+
}
|
|
121
|
+
parse(parts, context) {
|
|
122
|
+
const entityRefs = (0, _parseUtils.extractEntityRefs)(parts);
|
|
123
|
+
if (entityRefs.length === 0) {
|
|
124
|
+
context == null || context.reportDiagnostic == null || context.reportDiagnostic(_diagnosticTypes.DIAGNOSTIC_TYPES.SECTION_REJECTED, 'entityCollection: no entity links found');
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Guard the pattern: anything that is not a collection means this block
|
|
129
|
+
// belongs to the generic entity section, not here.
|
|
130
|
+
if (entityRefs.some(e => e.entityType !== COLLECTION_ENTITY_TYPE)) {
|
|
131
|
+
context == null || context.reportDiagnostic == null || context.reportDiagnostic(_diagnosticTypes.DIAGNOSTIC_TYPES.SECTION_REJECTED, 'entityCollection: block mixes collections with other entity types');
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const title = (0, _parseUtils.headingText)(parts, 2) || 'Collections';
|
|
135
|
+
const description = (0, _parseUtils.entitySectionBodyText)(parts) || undefined;
|
|
136
|
+
|
|
137
|
+
// Every entity link lives inside the list, so any top-level callout is
|
|
138
|
+
// section-level by construction — per-item callouts are nested in the list
|
|
139
|
+
// items and are picked up by extractEntityRefs instead.
|
|
140
|
+
const callout = (0, _parseUtils.calloutFromParts)(parts);
|
|
141
|
+
const items = entityRefs.map(e => ({
|
|
142
|
+
entityId: e.entityId,
|
|
143
|
+
entityType: e.entityType,
|
|
144
|
+
entityUrl: e.entityUrl,
|
|
145
|
+
linkText: e.linkText || undefined,
|
|
146
|
+
description: e.description || undefined,
|
|
147
|
+
imageUrl: e.markdownImageUrl,
|
|
148
|
+
callout: e.callout
|
|
149
|
+
}));
|
|
150
|
+
return {
|
|
151
|
+
title,
|
|
152
|
+
description,
|
|
153
|
+
callout,
|
|
154
|
+
items
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
exports.EntityCollectionSectionDefinition = EntityCollectionSectionDefinition;
|
|
159
|
+
//# sourceMappingURL=EntityCollectionSectionDefinition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_parseUtils","require","_diagnosticTypes","COLLECTION_ENTITY_TYPE","EntityCollectionSectionDefinition","constructor","_defineProperty2","default","title","items","entityId","entityType","entityUrl","linkText","markdown","expected","description","parse","parts","context","entityRefs","extractEntityRefs","length","reportDiagnostic","DIAGNOSTIC_TYPES","SECTION_REJECTED","some","e","headingText","entitySectionBodyText","undefined","callout","calloutFromParts","map","imageUrl","markdownImageUrl","exports"],"sources":["../../../../src/component/componentDefinitions/EntityCollectionSectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n entitySectionBodyText,\n extractEntityRefs,\n calloutFromParts,\n} from './parse-utils';\nimport type { Part } from '../../parts/parts';\nimport type {\n EntityCompProps,\n EntityItem,\n EntitySectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition, ParseContext } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\n/**\n * The semantic entity type this definition claims — the `<type>` segment of\n * `web5://entity/<type>/<id>` (see the client `entityConfig.entityTypes` map).\n */\nconst COLLECTION_ENTITY_TYPE = 'collection';\n\n/**\n * Entity section restricted to **collection** entities\n * (`web5://entity/collection/<id>`).\n *\n * Same markdown shapes as {@link EntitySectionDefinition} — an `h2`, optional\n * intro/callout, then entity links either at the top level or as list items —\n * but every entity link must be a collection. That makes it a strictly narrower\n * match, so it MUST be registered **before** `EntitySectionDefinition` (which\n * claims any `link[entityType]`, i.e. product and article sections too). Once\n * `EntitySectionDefinition` matches, the block is consumed and no later\n * definition is consulted.\n *\n * Mixed blocks (a collection next to a product or an article) fall through:\n * the patterns only cover consecutive collection links, and `parse()` returns\n * `null` when a non-collection entity slipped into the matched slice, handing\n * the block to `EntitySectionDefinition`.\n *\n * Props are the shared {@link EntityCompProps} shape, so a client renders it\n * with a dedicated `EntityCollectionSection` component while product/article\n * sections keep using their own components under `EntitySectionDefinition`.\n */\nexport class EntityCollectionSectionDefinition\n implements SectionDefinition<EntitySectionComponent>\n{\n sectionType = 'entityCollection' as const;\n /**\n * One pattern, matching the shape the orchestrator actually emits for a\n * collections block:\n *\n * <!-- section:entities, semantic:items -->\n * ## Curated collections by theme\n * - [](web5://entity/collection/<id>) description\n *\n * The head absorbs the section descriptor and any intro copy or ECHO\n * callout; each list item is a collection link, an optional markdown image,\n * then its description and optional per-item callout. A trailing\n * section-level callout is intentionally left outside the pattern so it\n * falls through to the downstream CalloutSection.\n *\n * Deliberately narrow: anything else (collection links at the top level\n * rather than in a list, a block mixing entity types) falls through to\n * `EntitySectionDefinition`, which is what handled it before this\n * definition existed.\n */\n patterns = [\n 'html_comment* > h2 > (html_comment | t | callout)* > list{item[1-]:link[entityType=\"collection\"] > image? > (t | callout)*}',\n ];\n\n defaults: EntityCompProps = {\n title: 'Collections',\n items: [\n {\n entityId: 'example-1',\n entityType: COLLECTION_ENTITY_TYPE,\n entityUrl: 'web5://entity/collection/example-1',\n linkText: 'Example collection',\n },\n ],\n };\n\n fixtures: SectionFixture<EntityCompProps>[] = [\n {\n markdown:\n '## Shop the collections\\n\\nCurated edits for the season.\\n\\n- [Running](web5://entity/collection/pUx3mVQ1TmyN2mVYQqQ8Jg) Shoes and kit built for the road\\n- [Trail](web5://entity/collection/Rr8kQyO0S1uH2nQ0mQ2AqQ) Grip and protection off-road',\n expected: {\n title: 'Shop the collections',\n description: 'Curated edits for the season.',\n items: [\n {\n entityId: 'pUx3mVQ1TmyN2mVYQqQ8Jg',\n entityType: 'collection',\n entityUrl: 'web5://entity/collection/pUx3mVQ1TmyN2mVYQqQ8Jg',\n linkText: 'Running',\n description: 'Shoes and kit built for the road',\n },\n {\n entityId: 'Rr8kQyO0S1uH2nQ0mQ2AqQ',\n entityType: 'collection',\n entityUrl: 'web5://entity/collection/Rr8kQyO0S1uH2nQ0mQ2AqQ',\n linkText: 'Trail',\n description: 'Grip and protection off-road',\n },\n ],\n },\n },\n {\n // The shape the orchestrator emits: a section descriptor, empty link\n // text, and a Shopify gid (which is itself a `://` url) as the entity id.\n markdown:\n '<!-- section:entities, semantic:items -->\\n## Curated collections by theme\\n- [](web5://entity/collection/gid://shopify/Collection/347895267466) storewide assortment spanning the full product range\\n- [](web5://entity/collection/gid://shopify/Collection/348001501322) artistic **Watercolor** candles with bright vessels',\n expected: {\n title: 'Curated collections by theme',\n items: [\n {\n entityId: 'gid://shopify/Collection/347895267466',\n entityType: 'collection',\n entityUrl:\n 'web5://entity/collection/gid://shopify/Collection/347895267466',\n description: 'storewide assortment spanning the full product range',\n },\n {\n entityId: 'gid://shopify/Collection/348001501322',\n entityType: 'collection',\n entityUrl:\n 'web5://entity/collection/gid://shopify/Collection/348001501322',\n // Inline emphasis survives — descriptions keep their markdown.\n description: 'artistic **Watercolor** candles with bright vessels',\n },\n ],\n },\n },\n {\n // Single collection — the full-width banner shape.\n markdown:\n '## Winter essentials\\n\\n- [Shop winter](web5://entity/collection/aB1cD2eF3gH4iJ5kL6mN7o) Layers for the cold months',\n expected: {\n title: 'Winter essentials',\n items: [\n {\n entityId: 'aB1cD2eF3gH4iJ5kL6mN7o',\n entityType: 'collection',\n entityUrl: 'web5://entity/collection/aB1cD2eF3gH4iJ5kL6mN7o',\n linkText: 'Shop winter',\n description: 'Layers for the cold months',\n },\n ],\n },\n },\n ];\n\n parse(parts: Part[], context?: ParseContext): EntityCompProps | null {\n const entityRefs = extractEntityRefs(parts);\n\n if (entityRefs.length === 0) {\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'entityCollection: no entity links found',\n );\n return null;\n }\n\n // Guard the pattern: anything that is not a collection means this block\n // belongs to the generic entity section, not here.\n if (entityRefs.some((e) => e.entityType !== COLLECTION_ENTITY_TYPE)) {\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'entityCollection: block mixes collections with other entity types',\n );\n return null;\n }\n\n const title = headingText(parts, 2) || 'Collections';\n const description = entitySectionBodyText(parts) || undefined;\n\n // Every entity link lives inside the list, so any top-level callout is\n // section-level by construction — per-item callouts are nested in the list\n // items and are picked up by extractEntityRefs instead.\n const callout = calloutFromParts(parts);\n\n const items: EntityItem[] = entityRefs.map((e) => ({\n entityId: e.entityId,\n entityType: e.entityType,\n entityUrl: e.entityUrl,\n linkText: e.linkText || undefined,\n description: e.description || undefined,\n imageUrl: e.markdownImageUrl,\n callout: e.callout,\n }));\n\n return { title, description, callout, items };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -53,7 +53,7 @@ Nike offerings span performance and lifestyle lines.
|
|
|
53
53
|
* [Nike Life Cable Knit Turtleneck Sweater](web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw)
|
|
54
54
|
* [Nike x Jacquemus NRG HE Swoosh Hoodie](web5://entity/product/qH2lqINGneHWYVZzzGnXTA)`,
|
|
55
55
|
expected: {
|
|
56
|
-
headline: 'Nike collections',
|
|
56
|
+
headline: '**Nike** collections',
|
|
57
57
|
content: 'Nike offerings span performance and lifestyle lines.',
|
|
58
58
|
image: 'web5://image/Nike+Life+collection',
|
|
59
59
|
items: [{
|
|
@@ -116,7 +116,10 @@ Nike offerings span performance and lifestyle lines.
|
|
|
116
116
|
leadingQuote = (0, _parseUtils.parseCalloutTag)(raw).text || undefined;
|
|
117
117
|
}
|
|
118
118
|
return {
|
|
119
|
-
|
|
119
|
+
// Markdown-preserving (not headingText): the hero title now carries the
|
|
120
|
+
// bold+italic hook the ECHO callout used to own, so the FE needs the
|
|
121
|
+
// formatting, not just the plain text. See FeatureHeroEntity.tsx.
|
|
122
|
+
headline: (0, _parseUtils.headingMarkdown)(parts, 1),
|
|
120
123
|
badge: (0, _parseUtils.headingText)(parts, 3) || undefined,
|
|
121
124
|
content: (0, _parseUtils.bodyText)(parts) || undefined,
|
|
122
125
|
image: (0, _parseUtils.firstImageSrc)(parts),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_parseUtils","require","_parts","_diagnosticTypes","HeroEntitySectionDefinition","constructor","_defineProperty2","default","headline","content","items","entityId","entityType","entityUrl","linkText","markdown","expected","image","parse","parts","context","entityRefs","extractEntityRefs","length","reportDiagnostic","DIAGNOSTIC_TYPES","SECTION_REJECTED","map","e","undefined","description","imageUrl","markdownImageUrl","callout","h1Index","findIndex","p","_p$meta","type","meta","level","blockquotes","getPartsByType","leadingQuote","bq","_bq$meta","_bq$content","bqIndex","indexOf","raw","trim","parseCalloutTag","text","
|
|
1
|
+
{"version":3,"names":["_parseUtils","require","_parts","_diagnosticTypes","HeroEntitySectionDefinition","constructor","_defineProperty2","default","headline","content","items","entityId","entityType","entityUrl","linkText","markdown","expected","image","parse","parts","context","entityRefs","extractEntityRefs","length","reportDiagnostic","DIAGNOSTIC_TYPES","SECTION_REJECTED","map","e","undefined","description","imageUrl","markdownImageUrl","callout","h1Index","findIndex","p","_p$meta","type","meta","level","blockquotes","getPartsByType","leadingQuote","bq","_bq$meta","_bq$content","bqIndex","indexOf","raw","trim","parseCalloutTag","text","headingMarkdown","badge","headingText","bodyText","firstImageSrc","exports"],"sources":["../../../../src/component/componentDefinitions/HeroEntitySectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n headingMarkdown,\n bodyText,\n firstImageSrc,\n parseCalloutTag,\n extractEntityRefs,\n} from './parse-utils';\nimport type { Part } from '../../parts/parts';\nimport { getPartsByType } from '../../parts/parts';\nimport type {\n EntityItem,\n HeroEntityCompProps,\n HeroEntitySectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition, ParseContext } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\n/**\n * Hero section whose body carries one or more web5 entity links\n * (`web5://entity/...`). It mirrors the framing of {@link HeroSectionDefinition}\n * (leading callout, h1, image, intro text) but, instead of turning the links\n * into CTA buttons, surfaces them as resolvable entities.\n *\n * Registered ahead of `HeroSectionDefinition` so an entity-bearing hero is\n * claimed here first. The pattern requires at least one `link[entityType]`, and\n * `parse()` returns `null` when no entity refs are found, letting a plain hero\n * fall through to `HeroSectionDefinition`.\n */\nexport class HeroEntitySectionDefinition\n implements SectionDefinition<HeroEntitySectionComponent>\n{\n sectionType = 'hero-entity' as const;\n singleInstance = true;\n patterns = [\n // Leading slots mirror HeroSectionDefinition: section descriptor, then the\n // optional leading ECHO callout, then an optional h3 badge, then the h1.\n // Callout and badge are independent; the second `html_comment?` preserves the\n // legacy callout-before-descriptor ordering for pages saved before this change.\n // Entity links carried as list items (the canonical shape).\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t | link)* > list{item[1-]:link[entityType] > image? > (t | callout)*}',\n // Entity links sitting at the top level of the hero body.\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t)* > (link[entityType] > image? > (t | callout)*)[1-] > list?',\n ];\n\n defaults: HeroEntityCompProps = {\n headline: 'Featured collection',\n content: 'Explore the highlights from this collection.',\n items: [\n {\n entityId: 'example-1',\n entityType: 'product',\n entityUrl: 'web5://entity/product/example-1',\n linkText: 'Example product',\n },\n ],\n };\n\n fixtures: SectionFixture<HeroEntityCompProps>[] = [\n {\n markdown: `<!-- section:hero, id:hero, semantic:hero -->\n\n# **Nike** collections\n\n\nNike offerings span performance and lifestyle lines.\n\n* [Nike Throwback Future Collection](web5://entity/article/WAyuiC63OSjwpvnOyZcblQ)\n* [Nike Life Cable Knit Turtleneck Sweater](web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw)\n* [Nike x Jacquemus NRG HE Swoosh Hoodie](web5://entity/product/qH2lqINGneHWYVZzzGnXTA)`,\n expected: {\n headline: '**Nike** collections',\n content: 'Nike offerings span performance and lifestyle lines.',\n image: 'web5://image/Nike+Life+collection',\n items: [\n {\n entityId: 'WAyuiC63OSjwpvnOyZcblQ',\n entityType: 'article',\n entityUrl:\n 'web5://entity/article/WAyuiC63OSjwpvnOyZcblQ',\n linkText: 'Nike Throwback Future Collection',\n },\n {\n entityId: 'ZDvHxsa3IJA8CyGX8BNpdw',\n entityType: 'product',\n entityUrl:\n 'web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw',\n linkText: 'Nike Life Cable Knit Turtleneck Sweater',\n },\n {\n entityId: 'qH2lqINGneHWYVZzzGnXTA',\n entityType: 'product',\n entityUrl:\n 'web5://entity/product/qH2lqINGneHWYVZzzGnXTA',\n linkText: 'Nike x Jacquemus NRG HE Swoosh Hoodie',\n },\n ],\n },\n },\n ];\n\n parse(parts: Part[], context?: ParseContext): HeroEntityCompProps | null {\n const entityRefs = extractEntityRefs(parts);\n\n if (entityRefs.length === 0) {\n // Not an entity hero — let HeroSectionDefinition claim it.\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'hero-entity: no entity links found',\n );\n return null;\n }\n\n const items: EntityItem[] = entityRefs.map((e) => ({\n entityId: e.entityId,\n entityType: e.entityType,\n entityUrl: e.entityUrl,\n linkText: e.linkText || undefined,\n description: e.description || undefined,\n imageUrl: e.markdownImageUrl,\n callout: e.callout,\n }));\n\n // The leading callout before the h1 — the ECHO the orchestrator emits as the\n // hero's opening line, right after the section descriptor — is part of the\n // hero, mirroring HeroSectionDefinition. A trailing callout falls through to a\n // downstream callout section. (Per-item callouts inside the entity list are\n // handled by extractEntityRefs above and are unaffected.)\n const h1Index = parts.findIndex(\n (p) =>\n p.type === 'heading' && (p.meta as { level?: number })?.level === 1,\n );\n const blockquotes = getPartsByType<Part>(parts, 'callout');\n let leadingQuote: string | undefined;\n\n for (const bq of blockquotes) {\n const bqIndex = parts.indexOf(bq);\n if (h1Index >= 0 && bqIndex > h1Index) {\n continue;\n }\n const raw = (bq.meta?.markdown as string)?.trim() || bq.content?.trim();\n if (!raw) {\n continue;\n }\n leadingQuote = parseCalloutTag(raw).text || undefined;\n }\n\n return {\n // Markdown-preserving (not headingText): the hero title now carries the\n // bold+italic hook the ECHO callout used to own, so the FE needs the\n // formatting, not just the plain text. See FeatureHeroEntity.tsx.\n headline: headingMarkdown(parts, 1),\n badge: headingText(parts, 3) || undefined,\n content: bodyText(parts) || undefined,\n image: firstImageSrc(parts),\n leadingQuote,\n items,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -203,7 +203,10 @@ Get started today with our amazing features.
|
|
|
203
203
|
leadingQuote = (0, _parseUtils.parseCalloutTag)(raw).text || undefined;
|
|
204
204
|
}
|
|
205
205
|
return {
|
|
206
|
-
|
|
206
|
+
// Markdown-preserving (not headingText): the hero title now carries the
|
|
207
|
+
// bold+italic hook the ECHO callout used to own, so the FE needs the
|
|
208
|
+
// formatting, not just the plain text. See FeatureComparisonHero.tsx.
|
|
209
|
+
headline: (0, _parseUtils.headingMarkdown)(parts, 1),
|
|
207
210
|
badge: (0, _parseUtils.headingText)(parts, 3) || undefined,
|
|
208
211
|
content: (0, _parseUtils.bodyText)(parts) || undefined,
|
|
209
212
|
buttons: buttons.length > 0 ? buttons : undefined,
|