cms-renderer 0.6.11 → 0.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import React__default from 'react';
1
+ import react__default from 'react';
2
2
  import { BlockData, BlockComponentRegistry, ResolvedRouteParams } from './types.js';
3
3
  import '@repo/cms-schema/blocks';
4
4
 
@@ -12,12 +12,12 @@ import '@repo/cms-schema/blocks';
12
12
  type TextInfo = {
13
13
  value: string;
14
14
  path: Array<string | number>;
15
- parentType?: React__default.ElementType;
16
- key?: React__default.Key | null;
15
+ parentType?: react__default.ElementType;
16
+ key?: react__default.Key | null;
17
17
  inSvg?: boolean;
18
18
  };
19
19
  type ElementInfo = {
20
- element: React__default.ReactElement;
20
+ element: react__default.ReactElement;
21
21
  path: Array<string | number>;
22
22
  };
23
23
  type WalkVisitors = {
@@ -27,25 +27,25 @@ type WalkVisitors = {
27
27
  * - same string (or modified)
28
28
  * - a ReactNode (e.g. wrap in <span/>)
29
29
  */
30
- onText?: (info: TextInfo) => React__default.ReactNode;
30
+ onText?: (info: TextInfo) => react__default.ReactNode;
31
31
  /**
32
32
  * Called for every ReactElement encountered (after children are processed).
33
33
  * Return:
34
34
  * - same element
35
35
  * - a cloned/modified element
36
36
  */
37
- onElement?: (info: ElementInfo) => React__default.ReactElement;
37
+ onElement?: (info: ElementInfo) => react__default.ReactElement;
38
38
  };
39
39
  /**
40
40
  * Recursively maps a ReactNode tree, allowing transformations of text nodes and/or elements.
41
41
  * SSR-safe: does not touch DOM APIs.
42
42
  */
43
- declare function walkReactNode(node: React__default.ReactNode, visitors: WalkVisitors, ctx?: {
43
+ declare function walkReactNode(node: react__default.ReactNode, visitors: WalkVisitors, ctx?: {
44
44
  path?: Array<string | number>;
45
- parentType?: React__default.ElementType;
46
- key?: React__default.Key | null;
45
+ parentType?: react__default.ElementType;
46
+ key?: react__default.Key | null;
47
47
  inSvg?: boolean;
48
- }): React__default.ReactNode;
48
+ }): react__default.ReactNode;
49
49
  type ContentMatch = {
50
50
  contentPath: string;
51
51
  value: string;
@@ -63,7 +63,7 @@ declare function CmsEditableInit({ cmsUrl, cmsParentOrigin, }: {
63
63
  cmsUrl?: string;
64
64
  /** Admin window origin when proxied (from ?cms_parent_origin=). */
65
65
  cmsParentOrigin?: string;
66
- }): React__default.JSX.Element;
66
+ }): react__default.JSX.Element;
67
67
  interface BlockRendererProps {
68
68
  /**
69
69
  * The block data to render.
@@ -75,6 +75,10 @@ interface BlockRendererProps {
75
75
  * If true, renders the component without any tree walking or editable wrappers.
76
76
  */
77
77
  disableEditable?: boolean;
78
+ /**
79
+ * If true, wraps matched text nodes in contentEditable CMS spans.
80
+ */
81
+ enableContentEditable?: boolean;
78
82
  /**
79
83
  * Resolved route parameters from parametric routes.
80
84
  * Each key is a param name (e.g., "country") with its value, schema name, and full document.
@@ -112,6 +116,6 @@ declare function resolveComponent(registry: Partial<BlockComponentRegistry>, blo
112
116
  * CMS overlay script uses that sentinel to stamp attributes on the component's
113
117
  * root element without adding a layout-affecting wrapper.
114
118
  */
115
- declare function BlockRenderer({ block, registry, disableEditable, routeParams, path, }: BlockRendererProps): React__default.JSX.Element | null;
119
+ declare function BlockRenderer({ block, registry, disableEditable, enableContentEditable, routeParams, path, }: BlockRendererProps): react__default.JSX.Element | null;
116
120
 
117
121
  export { BlockRenderer, CmsEditableInit, extractContentValues, pathMatchesPattern, resolveComponent, walkReactNode };
@@ -13,11 +13,14 @@ function generateCmsOverlayScript(cmsParentOrigin) {
13
13
  var style = document.createElement('style');
14
14
  style.setAttribute('data-cms-overlay', '');
15
15
  style.textContent = \`
16
+ [data-cms-block],
16
17
  [data-cms-editable] {
17
18
  cursor: pointer;
19
+ }
20
+ [data-cms-editable] {
18
21
  border-radius: 2px;
19
22
  }
20
- [data-cms-editable]:hover {
23
+ [data-cms-editable]:not([data-cms-block]):hover {
21
24
  outline: 2px solid #3b82f6;
22
25
  outline-offset: 2px;
23
26
  }
@@ -30,8 +33,8 @@ function generateCmsOverlayScript(cmsParentOrigin) {
30
33
  pointer-events: none;
31
34
  z-index: 99998;
32
35
  }
33
- #cms-overlay-root > * {
34
- pointer-events: auto;
36
+ #cms-overlay-root > *:not(.cms-block-toolbar) {
37
+ pointer-events: none;
35
38
  }
36
39
  .cms-block-outline {
37
40
  position: fixed;
@@ -127,10 +130,69 @@ function generateCmsOverlayScript(cmsParentOrigin) {
127
130
  blockEl.setAttribute('data-cms-block', '');
128
131
  blockEl.setAttribute('data-block-id', blockId);
129
132
  blockEl.setAttribute('data-block-type', blockType);
130
- blockEl.setAttribute('data-cms-editable', '');
133
+
134
+ injectEditableSpans(
135
+ blockEl,
136
+ blockId,
137
+ blockType,
138
+ sentinel.getAttribute('data-content-entries')
139
+ );
131
140
  });
132
141
  }
133
142
 
143
+ function injectEditableSpans(blockEl, blockId, blockType, rawEntries) {
144
+ if (!rawEntries || blockEl.querySelector('[data-cms-editable][data-content-path]')) return;
145
+
146
+ var entries;
147
+ try {
148
+ entries = JSON.parse(rawEntries);
149
+ } catch (_) {
150
+ return;
151
+ }
152
+ if (!Array.isArray(entries) || entries.length === 0) return;
153
+
154
+ var used = {};
155
+ var walker = document.createTreeWalker(blockEl, NodeFilter.SHOW_TEXT, null);
156
+ var textNodes = [];
157
+ var node = walker.nextNode();
158
+ while (node !== null) {
159
+ if (node.nodeValue && node.nodeValue.trim()) {
160
+ textNodes.push(node);
161
+ }
162
+ node = walker.nextNode();
163
+ }
164
+
165
+ for (var i = 0; i < textNodes.length; i++) {
166
+ var textNode = textNodes[i];
167
+ var parentEl = textNode.parentElement;
168
+ if (!parentEl || parentEl.closest('svg') || parentEl.closest('[data-cms-editable]')) {
169
+ continue;
170
+ }
171
+
172
+ var text = textNode.nodeValue;
173
+ if (!text) continue;
174
+
175
+ for (var j = 0; j < entries.length; j++) {
176
+ var entry = entries[j];
177
+ if (!entry || typeof entry.v !== 'string' || typeof entry.p !== 'string' || used[entry.p]) {
178
+ continue;
179
+ }
180
+ if (text.trim() !== entry.v.trim()) continue;
181
+
182
+ used[entry.p] = true;
183
+ var span = document.createElement('span');
184
+ span.setAttribute('data-cms-editable', '');
185
+ span.setAttribute('data-block-id', blockId);
186
+ span.setAttribute('data-block-type', blockType);
187
+ span.setAttribute('data-content-path', entry.p);
188
+ span.setAttribute('contenteditable', 'true');
189
+ parentEl.insertBefore(span, textNode);
190
+ span.appendChild(textNode);
191
+ break;
192
+ }
193
+ }
194
+ }
195
+
134
196
  stampBlockElements();
135
197
 
136
198
  var stampObserver = new MutationObserver(function(mutations) {
@@ -185,6 +247,38 @@ function generateCmsOverlayScript(cmsParentOrigin) {
185
247
  var toolbarVisible = false;
186
248
  var selectedBlockId = null;
187
249
 
250
+ function decoratePreviewUrl(rawHref) {
251
+ if (!rawHref) return rawHref;
252
+ try {
253
+ var url = new URL(rawHref, window.location.href);
254
+ if (url.origin !== window.location.origin) return rawHref;
255
+ url.searchParams.set('edit_mode', 'true');
256
+ if (CMS_PARENT_ORIGIN) {
257
+ url.searchParams.set('cms_parent_origin', CMS_PARENT_ORIGIN);
258
+ }
259
+ return url.toString();
260
+ } catch (_) {
261
+ return rawHref;
262
+ }
263
+ }
264
+
265
+ function preserveEditParamsOnNavigation(e) {
266
+ if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
267
+ return;
268
+ }
269
+
270
+ var anchor = e.target.closest('a[href]');
271
+ if (!anchor || (anchor.target && anchor.target !== '_self') || anchor.hasAttribute('download')) {
272
+ return;
273
+ }
274
+
275
+ var href = anchor.getAttribute('href');
276
+ var decorated = decoratePreviewUrl(href);
277
+ if (decorated && decorated !== href) {
278
+ anchor.setAttribute('href', decorated);
279
+ }
280
+ }
281
+
188
282
  function postToParent(message) {
189
283
  if (!CMS_PARENT_ORIGIN || !window.parent || window.parent === window) {
190
284
  return;
@@ -303,6 +397,8 @@ function generateCmsOverlayScript(cmsParentOrigin) {
303
397
  });
304
398
 
305
399
  document.addEventListener('click', function(e) {
400
+ preserveEditParamsOnNavigation(e);
401
+
306
402
  if (toolbarVisible && !e.target.closest('[data-cms-toolbar]')) {
307
403
  var block = e.target.closest('[data-cms-block]');
308
404
  if (!block || block.getAttribute('data-block-id') !== currentBlockId) {
@@ -332,23 +428,23 @@ function generateCmsOverlayScript(cmsParentOrigin) {
332
428
  contentPath: null
333
429
  });
334
430
  }
335
- });
431
+ }, true);
336
432
 
337
433
  toolbar.querySelector('.move-up').addEventListener('click', function() {
338
434
  if (!currentBlockId) return;
339
- postToParent({ type: 'cms-block-move', blockId: currentBlockId, direction: 'up' });
435
+ postToParent({ type: 'cms-block-action', action: 'move-up', blockId: currentBlockId });
340
436
  hideToolbar();
341
437
  });
342
438
 
343
439
  toolbar.querySelector('.move-down').addEventListener('click', function() {
344
440
  if (!currentBlockId) return;
345
- postToParent({ type: 'cms-block-move', blockId: currentBlockId, direction: 'down' });
441
+ postToParent({ type: 'cms-block-action', action: 'move-down', blockId: currentBlockId });
346
442
  hideToolbar();
347
443
  });
348
444
 
349
445
  toolbar.querySelector('.delete').addEventListener('click', function() {
350
446
  if (!currentBlockId) return;
351
- postToParent({ type: 'cms-block-delete', blockId: currentBlockId });
447
+ postToParent({ type: 'cms-block-action', action: 'delete', blockId: currentBlockId });
352
448
  hideToolbar();
353
449
  });
354
450
 
@@ -556,6 +652,7 @@ function BlockRenderer({
556
652
  block,
557
653
  registry,
558
654
  disableEditable,
655
+ enableContentEditable,
559
656
  routeParams,
560
657
  path
561
658
  }) {
@@ -571,6 +668,7 @@ function BlockRenderer({
571
668
  if (disableEditable) {
572
669
  return component;
573
670
  }
671
+ const contentEntries = enableContentEditable ? [...extractContentValues(block.content).values()].flat().map(({ value, contentPath }) => ({ v: value, p: contentPath })) : void 0;
574
672
  return /* @__PURE__ */ jsxs(Fragment, { children: [
575
673
  /* @__PURE__ */ jsx(
576
674
  "span",
@@ -578,6 +676,7 @@ function BlockRenderer({
578
676
  "data-cms-sentinel": "",
579
677
  "data-block-id": block.id,
580
678
  "data-block-type": block.type,
679
+ "data-content-entries": contentEntries ? JSON.stringify(contentEntries) : void 0,
581
680
  style: { display: "none" },
582
681
  "aria-hidden": "true"
583
682
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../lib/block-renderer.tsx","../../lib/cms-overlay-script.ts","../../lib/cms-post-message.ts"],"sourcesContent":["/**\n * Block Renderer Component\n *\n * Dispatches block data to the appropriate component using the ComponentMap pattern.\n * This is the main entry point for rendering blocks from the CMS.\n */\n\nimport React from 'react';\nimport { generateCmsOverlayScript } from './cms-overlay-script';\nimport { getCmsParentTargetOrigin } from './cms-post-message';\nimport type { BlockComponentRegistry, BlockData, ResolvedRouteParams } from './types';\n\ntype TextInfo = {\n value: string;\n path: Array<string | number>;\n parentType?: React.ElementType;\n key?: React.Key | null;\n inSvg?: boolean;\n};\n\ntype ElementInfo = {\n element: React.ReactElement;\n path: Array<string | number>;\n};\n\ntype WalkVisitors = {\n /**\n * Called for every string/number child encountered.\n * Return:\n * - same string (or modified)\n * - a ReactNode (e.g. wrap in <span/>)\n */\n onText?: (info: TextInfo) => React.ReactNode;\n\n /**\n * Called for every ReactElement encountered (after children are processed).\n * Return:\n * - same element\n * - a cloned/modified element\n */\n onElement?: (info: ElementInfo) => React.ReactElement;\n};\n\n/**\n * Recursively maps a ReactNode tree, allowing transformations of text nodes and/or elements.\n * SSR-safe: does not touch DOM APIs.\n */\nexport function walkReactNode(\n node: React.ReactNode,\n visitors: WalkVisitors,\n ctx: {\n path?: Array<string | number>;\n parentType?: React.ElementType;\n key?: React.Key | null;\n inSvg?: boolean;\n } = {}\n): React.ReactNode {\n const path = ctx.path ?? [];\n\n // Fast-path primitives\n if (node == null || typeof node === 'boolean') return node;\n\n if (typeof node === 'string' || typeof node === 'number') {\n const value = String(node);\n return visitors.onText\n ? visitors.onText({ value, path, parentType: ctx.parentType, key: ctx.key, inSvg: ctx.inSvg })\n : node;\n }\n\n // Arrays\n if (Array.isArray(node)) {\n return node.map((child, i) => {\n // biome-ignore lint/suspicious/noExplicitAny: React child key access\n const childKey = (child as any)?.key ?? null;\n const result = walkReactNode(child, visitors, {\n path: [...path, i],\n parentType: ctx.parentType,\n key: childKey,\n inSvg: ctx.inSvg,\n });\n // Ensure array children have keys\n if (React.isValidElement(result) && result.key == null) {\n return React.cloneElement(result, { key: childKey ?? `arr-${path.join('-')}-${i}` });\n }\n return result;\n });\n }\n\n // ReactElement (including Fragment)\n if (React.isValidElement(node)) {\n // biome-ignore lint/suspicious/noExplicitAny: React element props access\n const el = node as React.ReactElement<any>;\n const elProps = el.props as Record<string, unknown> | null;\n\n // Track SVG context so we never inject <span> inside SVG subtrees\n const nextInSvg = ctx.inSvg || el.type === 'svg';\n\n // Recurse into children (if any)\n const hasChildren = elProps && 'children' in elProps;\n const nextChildren = hasChildren\n ? React.Children.map(elProps.children as React.ReactNode, (child, i) => {\n // biome-ignore lint/suspicious/noExplicitAny: React child key access\n const childKey = (child as any)?.key ?? null;\n const result = walkReactNode(child, visitors, {\n path: [...path, 'children', i],\n parentType: el.type as React.ElementType,\n key: childKey,\n inSvg: nextInSvg,\n });\n // Ensure children have keys\n if (React.isValidElement(result) && result.key == null) {\n return React.cloneElement(result, { key: childKey ?? `child-${path.join('-')}-${i}` });\n }\n return result;\n })\n : (elProps?.children as React.ReactNode);\n\n // Only clone if children changed (or if you want to force a clone)\n const cloned = hasChildren\n ? React.cloneElement(el, undefined, nextChildren as React.ReactNode)\n : el;\n\n return visitors.onElement ? visitors.onElement({ element: cloned, path }) : cloned;\n }\n\n // Functions, symbols, portals, etc. are rare here; return as-is\n return node;\n}\n\n// -----------------------------------------------------------------------------\n// Content Value Extraction\n// -----------------------------------------------------------------------------\n\ntype ContentMatch = {\n contentPath: string;\n value: string;\n};\n\n/**\n * Extracts all string values from a content object with their paths.\n * Returns a Map where keys are string values and values are arrays of content paths.\n */\nexport function extractContentValues(\n content: Record<string, unknown>,\n basePath: string[] = []\n): Map<string, ContentMatch[]> {\n const map = new Map<string, ContentMatch[]>();\n\n function walk(obj: unknown, path: string[]) {\n if (typeof obj === 'string' && obj.trim() !== '') {\n const contentPath = path.join('.');\n const existing = map.get(obj) || [];\n existing.push({ contentPath, value: obj });\n map.set(obj, existing);\n } else if (Array.isArray(obj)) {\n for (let index = 0; index < obj.length; index++) {\n walk(obj[index], [...path, String(index)]);\n }\n } else if (obj && typeof obj === 'object') {\n for (const [key, value] of Object.entries(obj)) {\n walk(value, [...path, key]);\n }\n }\n }\n\n walk(content, basePath);\n return map;\n}\n\n// -----------------------------------------------------------------------------\n// CmsEditableInit — render once per page in edit mode\n// -----------------------------------------------------------------------------\n\n/**\n * Renders the shared CMS edit-mode styles and click-routing script.\n * Place this once at the top of your page when edit_mode is active.\n */\nexport function CmsEditableInit({\n cmsUrl,\n cmsParentOrigin,\n}: {\n cmsUrl?: string;\n /** Admin window origin when proxied (from ?cms_parent_origin=). */\n cmsParentOrigin?: string;\n}) {\n const targetOrigin = getCmsParentTargetOrigin(cmsUrl, cmsParentOrigin) ?? '';\n\n return (\n <script\n // biome-ignore lint/security/noDangerouslySetInnerHtml: Inline script for CMS overlay functionality\n dangerouslySetInnerHTML={{\n __html: generateCmsOverlayScript(targetOrigin),\n }}\n />\n );\n}\n\n// -----------------------------------------------------------------------------\n// Props\n// -----------------------------------------------------------------------------\n\ninterface BlockRendererProps {\n /**\n * The block data to render.\n * Must have a `type` field that maps to a registered component.\n */\n block: BlockData;\n registry: Partial<BlockComponentRegistry>;\n /**\n * If true, renders the component without any tree walking or editable wrappers.\n */\n disableEditable?: boolean;\n /**\n * Resolved route parameters from parametric routes.\n * Each key is a param name (e.g., \"country\") with its value, schema name, and full document.\n */\n routeParams?: ResolvedRouteParams;\n /**\n * The current URL path (e.g. \"/en/test\").\n * When provided, enables path-namespaced component lookup.\n * Registry keys like \"/{lang}/test Article\" will be matched against this path,\n * where `{x}` and `(x)` are treated as single-segment wildcards.\n */\n path?: string;\n}\n\n// -----------------------------------------------------------------------------\n// Path-namespaced component resolution\n// -----------------------------------------------------------------------------\n\n/**\n * Returns true if each segment of `path` matches the corresponding segment in\n * `pattern`, where a pattern segment wrapped in `{…}` or `(…)` is a wildcard.\n */\nexport function pathMatchesPattern(path: string, pattern: string): boolean {\n const pathSegs = path.split('/').filter(Boolean);\n const patternSegs = pattern.split('/').filter(Boolean);\n if (pathSegs.length !== patternSegs.length) return false;\n for (let i = 0; i < patternSegs.length; i++) {\n const seg = patternSegs[i];\n if (!seg) return false;\n if ((seg.startsWith('{') && seg.endsWith('}')) || (seg.startsWith('(') && seg.endsWith(')'))) {\n continue;\n }\n if (seg !== pathSegs[i]) return false;\n }\n return true;\n}\n\n/**\n * Resolves the component for `blockType` from the registry.\n *\n * When `path` is provided, keys of the form `\"/{pattern} BlockType\"` are\n * checked first. The first key whose path pattern matches the current path\n * and whose block-type suffix matches `blockType` wins. Falls back to a\n * direct `registry[blockType]` lookup.\n */\nexport function resolveComponent(\n registry: Partial<BlockComponentRegistry>,\n blockType: string,\n path?: string\n): BlockComponentRegistry[string] | undefined {\n if (path) {\n for (const key of Object.keys(registry)) {\n if (!key.startsWith('/')) continue;\n const spaceIdx = key.indexOf(' ');\n if (spaceIdx === -1) continue;\n const pathPattern = key.slice(0, spaceIdx);\n const registeredType = key.slice(spaceIdx + 1);\n if (registeredType !== blockType) continue;\n if (pathMatchesPattern(path, pathPattern)) {\n return registry[key];\n }\n }\n }\n return registry[blockType];\n}\n\n// -----------------------------------------------------------------------------\n// Component\n// -----------------------------------------------------------------------------\n\n/**\n * Renders a single block by dispatching to the appropriate component.\n *\n * Uses the ComponentMap pattern: the block's `type` field determines which\n * component renders the block's `content`.\n *\n * In editable mode, renders a hidden sentinel before the component. The shared\n * CMS overlay script uses that sentinel to stamp attributes on the component's\n * root element without adding a layout-affecting wrapper.\n */\nexport function BlockRenderer({\n block,\n registry,\n disableEditable,\n routeParams,\n path,\n}: BlockRendererProps) {\n const Component = resolveComponent(registry, block.type, path);\n\n if (!Component) {\n // Log warning in development, render nothing in production\n if (process.env.NODE_ENV === 'development') {\n console.warn(`[BlockRenderer] Unknown block type: ${block.type}`);\n }\n return null;\n }\n\n // Extract language code from route params if any param is bound to the language schema.\n const language = routeParams\n ? Object.values(routeParams).find((p) => p.schemaName === 'language')?.value\n : undefined;\n\n const component = (\n <Component content={block.content} routeParams={routeParams} language={language} />\n );\n\n if (disableEditable) {\n return component;\n }\n\n return (\n <>\n <span\n data-cms-sentinel=\"\"\n data-block-id={block.id}\n data-block-type={block.type}\n style={{ display: 'none' }}\n aria-hidden=\"true\"\n />\n {component}\n </>\n );\n}\n","/**\n * CMS Overlay Script\n *\n * Framework-agnostic vanilla JS script that handles all CMS edit mode overlays:\n * - Block hover outlines\n * - Cursor indicator\n * - Block toolbar (move up/down, delete)\n *\n * Uses a sentinel-based approach: hidden <span> elements mark block positions,\n * and this script stamps data attributes on the actual block elements (next sibling).\n * This avoids wrapper elements that could break layouts.\n *\n * Compatible with Next.js, TanStack Start, Remix, and other frameworks.\n */\n\nexport function generateCmsOverlayScript(cmsParentOrigin: string): string {\n return `\n(function() {\n if (window.__cmsOverlayInitialized) return;\n window.__cmsOverlayInitialized = true;\n\n var CMS_PARENT_ORIGIN = ${JSON.stringify(cmsParentOrigin)};\n\n var style = document.createElement('style');\n style.setAttribute('data-cms-overlay', '');\n style.textContent = \\`\n [data-cms-editable] {\n cursor: pointer;\n border-radius: 2px;\n }\n [data-cms-editable]:hover {\n outline: 2px solid #3b82f6;\n outline-offset: 2px;\n }\n #cms-overlay-root {\n position: fixed;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n pointer-events: none;\n z-index: 99998;\n }\n #cms-overlay-root > * {\n pointer-events: auto;\n }\n .cms-block-outline {\n position: fixed;\n border: 2px solid #3b82f6;\n border-radius: 4px;\n pointer-events: none;\n z-index: 99997;\n transition: all 0.15s ease;\n }\n .cms-block-outline.cms-outline-selected {\n border-color: #2563eb;\n border-width: 3px;\n }\n .cms-block-cursor {\n position: fixed;\n width: 22px;\n height: 22px;\n background: radial-gradient(circle, #fff 5px, #000 5px);\n border-radius: 50%;\n pointer-events: none;\n z-index: 99999;\n transform: translate(-50%, -50%);\n opacity: 0;\n transition: opacity 0.1s ease;\n }\n .cms-block-cursor.cms-cursor-visible {\n opacity: 1;\n }\n .cms-block-toolbar {\n position: fixed;\n display: flex;\n gap: 4px;\n background: #1f2937;\n border-radius: 6px;\n padding: 4px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.25);\n z-index: 99999;\n pointer-events: none;\n opacity: 0;\n transform: scale(0.9) translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n }\n .cms-block-toolbar.cms-toolbar-visible {\n opacity: 1;\n transform: scale(1) translateY(0);\n pointer-events: auto;\n }\n .cms-block-toolbar button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n color: #9ca3af;\n border-radius: 4px;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .cms-block-toolbar button:hover {\n background: #374151;\n color: #fff;\n }\n .cms-block-toolbar button.delete:hover {\n background: #dc2626;\n color: #fff;\n }\n .cms-block-toolbar button:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n .cms-block-toolbar button:disabled:hover {\n background: transparent;\n color: #9ca3af;\n }\n .cms-block-toolbar svg {\n width: 16px;\n height: 16px;\n }\n \\`;\n document.head.appendChild(style);\n\n function stampBlockElements() {\n var sentinels = document.querySelectorAll('[data-cms-sentinel]');\n sentinels.forEach(function(sentinel) {\n var blockEl = sentinel.nextElementSibling;\n if (!blockEl) return;\n\n var blockId = sentinel.getAttribute('data-block-id');\n var blockType = sentinel.getAttribute('data-block-type');\n\n blockEl.setAttribute('data-cms-block', '');\n blockEl.setAttribute('data-block-id', blockId);\n blockEl.setAttribute('data-block-type', blockType);\n blockEl.setAttribute('data-cms-editable', '');\n });\n }\n\n stampBlockElements();\n\n var stampObserver = new MutationObserver(function(mutations) {\n var needsStamp = mutations.some(function(m) {\n return m.addedNodes.length > 0;\n });\n if (needsStamp) stampBlockElements();\n });\n stampObserver.observe(document.body, { childList: true, subtree: true });\n\n var overlayRoot = document.createElement('div');\n overlayRoot.id = 'cms-overlay-root';\n document.body.appendChild(overlayRoot);\n\n var cursor = document.createElement('div');\n cursor.className = 'cms-block-cursor';\n overlayRoot.appendChild(cursor);\n\n var outline = document.createElement('div');\n outline.className = 'cms-block-outline';\n outline.style.display = 'none';\n overlayRoot.appendChild(outline);\n\n var selectedOutline = document.createElement('div');\n selectedOutline.className = 'cms-block-outline cms-outline-selected';\n selectedOutline.style.display = 'none';\n overlayRoot.appendChild(selectedOutline);\n\n var toolbar = document.createElement('div');\n toolbar.className = 'cms-block-toolbar';\n toolbar.setAttribute('data-cms-toolbar', '');\n toolbar.innerHTML = \\`\n <button class=\"move-up\" title=\"Move up\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M18 15l-6-6-6 6\"/>\n </svg>\n </button>\n <button class=\"move-down\" title=\"Move down\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M6 9l6 6 6-6\"/>\n </svg>\n </button>\n <button class=\"delete\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"/>\n </svg>\n </button>\n \\`;\n overlayRoot.appendChild(toolbar);\n\n var currentBlockId = null;\n var toolbarVisible = false;\n var selectedBlockId = null;\n\n function postToParent(message) {\n if (!CMS_PARENT_ORIGIN || !window.parent || window.parent === window) {\n return;\n }\n window.parent.postMessage(message, CMS_PARENT_ORIGIN);\n }\n\n function sendReadySignal() {\n postToParent({ type: 'cms-preview-ready' });\n }\n sendReadySignal();\n // The preview iframe can execute before the admin listener is attached.\n setTimeout(sendReadySignal, 500);\n setTimeout(sendReadySignal, 1500);\n\n function getBlockElement(blockId) {\n return document.querySelector('[data-block-id=\"' + blockId + '\"]');\n }\n\n function getAllBlocks() {\n return Array.from(document.querySelectorAll('[data-cms-block]'));\n }\n\n function getBlockIndex(blockId) {\n var blocks = getAllBlocks();\n for (var i = 0; i < blocks.length; i++) {\n if (blocks[i].getAttribute('data-block-id') === blockId) return i;\n }\n return -1;\n }\n\n function updateOutline(el, outlineEl) {\n if (!el) {\n outlineEl.style.display = 'none';\n return;\n }\n var rect = el.getBoundingClientRect();\n outlineEl.style.display = 'block';\n outlineEl.style.top = rect.top + 'px';\n outlineEl.style.left = rect.left + 'px';\n outlineEl.style.width = rect.width + 'px';\n outlineEl.style.height = rect.height + 'px';\n }\n\n function positionToolbar(x, y) {\n var rect = toolbar.getBoundingClientRect();\n var top = Math.max(4, y - rect.height - 12);\n var left = Math.max(4, Math.min(x - rect.width / 2, window.innerWidth - rect.width - 4));\n toolbar.style.top = top + 'px';\n toolbar.style.left = left + 'px';\n }\n\n function showToolbar(x, y, blockId) {\n currentBlockId = blockId;\n toolbarVisible = true;\n positionToolbar(x, y);\n toolbar.classList.add('cms-toolbar-visible');\n\n var index = getBlockIndex(blockId);\n var total = getAllBlocks().length;\n toolbar.querySelector('.move-up').disabled = index <= 0;\n toolbar.querySelector('.move-down').disabled = index >= total - 1;\n }\n\n function hideToolbar() {\n toolbarVisible = false;\n toolbar.classList.remove('cms-toolbar-visible');\n }\n\n function showCursor(x, y) {\n cursor.style.top = y + 'px';\n cursor.style.left = x + 'px';\n cursor.classList.add('cms-cursor-visible');\n }\n\n function hideCursor() {\n cursor.classList.remove('cms-cursor-visible');\n }\n\n document.addEventListener('mouseover', function(e) {\n if (toolbarVisible) return;\n var block = e.target.closest('[data-cms-block]');\n if (block) {\n updateOutline(block, outline);\n }\n });\n\n document.addEventListener('mouseout', function(e) {\n var block = e.target.closest('[data-cms-block]');\n var related = e.relatedTarget ? e.relatedTarget.closest('[data-cms-block]') : null;\n if (block && block !== related) {\n outline.style.display = 'none';\n hideCursor();\n }\n });\n\n document.addEventListener('mousemove', function(e) {\n if (toolbarVisible) return;\n var block = e.target.closest('[data-cms-block]');\n if (block) {\n showCursor(e.clientX, e.clientY);\n }\n });\n\n document.addEventListener('contextmenu', function(e) {\n if (e.target.closest('[data-cms-toolbar]')) return;\n var block = e.target.closest('[data-cms-block]');\n if (block) {\n if (toolbarVisible) return;\n e.preventDefault();\n hideCursor();\n outline.style.display = 'none';\n var blockId = block.getAttribute('data-block-id');\n showToolbar(e.clientX, e.clientY, blockId);\n }\n });\n\n document.addEventListener('click', function(e) {\n if (toolbarVisible && !e.target.closest('[data-cms-toolbar]')) {\n var block = e.target.closest('[data-cms-block]');\n if (!block || block.getAttribute('data-block-id') !== currentBlockId) {\n hideToolbar();\n }\n }\n\n if (e.target.closest('[data-cms-toolbar]')) return;\n\n var editable = e.target.closest('[data-cms-editable]');\n if (editable) {\n postToParent({\n type: 'cms-editable-click',\n blockId: editable.getAttribute('data-block-id'),\n blockType: editable.getAttribute('data-block-type'),\n contentPath: editable.getAttribute('data-content-path')\n });\n return;\n }\n\n var block = e.target.closest('[data-cms-block]');\n if (block) {\n postToParent({\n type: 'cms-editable-click',\n blockId: block.getAttribute('data-block-id'),\n blockType: block.getAttribute('data-block-type'),\n contentPath: null\n });\n }\n });\n\n toolbar.querySelector('.move-up').addEventListener('click', function() {\n if (!currentBlockId) return;\n postToParent({ type: 'cms-block-move', blockId: currentBlockId, direction: 'up' });\n hideToolbar();\n });\n\n toolbar.querySelector('.move-down').addEventListener('click', function() {\n if (!currentBlockId) return;\n postToParent({ type: 'cms-block-move', blockId: currentBlockId, direction: 'down' });\n hideToolbar();\n });\n\n toolbar.querySelector('.delete').addEventListener('click', function() {\n if (!currentBlockId) return;\n postToParent({ type: 'cms-block-delete', blockId: currentBlockId });\n hideToolbar();\n });\n\n window.addEventListener('scroll', function() {\n hideCursor();\n hideToolbar();\n if (selectedBlockId) {\n var el = getBlockElement(selectedBlockId);\n updateOutline(el, selectedOutline);\n }\n }, { passive: true, capture: true });\n\n window.addEventListener('resize', function() {\n if (selectedBlockId) {\n var el = getBlockElement(selectedBlockId);\n updateOutline(el, selectedOutline);\n }\n }, { passive: true });\n\n window.addEventListener('message', function(e) {\n if (CMS_PARENT_ORIGIN && e.origin !== CMS_PARENT_ORIGIN) return;\n if (e.source !== window.parent) return;\n\n if (e.data && e.data.type === 'cms-select-block') {\n selectedBlockId = e.data.blockId || null;\n if (selectedBlockId) {\n var el = getBlockElement(selectedBlockId);\n updateOutline(el, selectedOutline);\n } else {\n selectedOutline.style.display = 'none';\n }\n }\n });\n})();\n`;\n}\n","/**\n * Trusted-origin helpers for CMS template-builder postMessage traffic\n * between the admin UI (parent) and the site preview iframe (child).\n */\n\nexport const CMS_PARENT_ORIGIN_PARAM = 'cms_parent_origin';\n\nexport function parseOrigin(url: string | undefined): string | null {\n if (!url) return null;\n try {\n return new URL(url).origin;\n } catch {\n return null;\n }\n}\n\nfunction getParentOriginFromQueryParam(): string | null {\n if (typeof window === 'undefined') return null;\n const value = new URLSearchParams(window.location.search).get(CMS_PARENT_ORIGIN_PARAM);\n return parseOrigin(value ?? undefined);\n}\n\nfunction getReferrerOrigin(): string | null {\n if (typeof document === 'undefined' || !document.referrer) return null;\n return parseOrigin(document.referrer);\n}\n\n/** Same-origin embed (e.g. proxied admin + preview on the customer domain). */\nfunction getSameOriginParentOrigin(): string | null {\n if (typeof window === 'undefined' || !window.parent || window.parent === window) {\n return null;\n }\n try {\n return window.parent.location.origin;\n } catch {\n return null;\n }\n}\n\n/** Origins allowed to send messages to the preview iframe (CMS admin hosts). */\nexport function getAllowedCmsParentOrigins(explicitParentOrigin?: string): string[] {\n const origins = new Set<string>();\n const apiOrigin = parseOrigin(process.env.NEXT_PUBLIC_CMS_API_URL);\n if (apiOrigin) origins.add(apiOrigin);\n\n const fromQuery = parseOrigin(explicitParentOrigin) ?? getParentOriginFromQueryParam();\n if (fromQuery) origins.add(fromQuery);\n\n const referrerOrigin = getReferrerOrigin();\n if (referrerOrigin) origins.add(referrerOrigin);\n\n const sameOriginParent = getSameOriginParentOrigin();\n if (sameOriginParent) origins.add(sameOriginParent);\n\n return [...origins];\n}\n\n/**\n * Target origin when posting from the preview iframe to the CMS parent.\n *\n * When the admin is proxied on a customer domain, the parent window origin is\n * the customer site — not NEXT_PUBLIC_CMS_API_URL. Prefer explicit/referrer/\n * same-origin parent detection over the CMS API URL.\n */\nexport function getCmsParentTargetOrigin(\n preferredCmsUrl?: string,\n explicitParentOrigin?: string\n): string | null {\n const fromExplicit = parseOrigin(explicitParentOrigin) ?? getParentOriginFromQueryParam();\n if (fromExplicit) return fromExplicit;\n\n const referrerOrigin = getReferrerOrigin();\n if (referrerOrigin) return referrerOrigin;\n\n const sameOriginParent = getSameOriginParentOrigin();\n if (sameOriginParent) return sameOriginParent;\n\n const preferredOrigin = parseOrigin(preferredCmsUrl);\n if (preferredOrigin) return preferredOrigin;\n\n const allowed = getAllowedCmsParentOrigins(explicitParentOrigin);\n return allowed[0] ?? null;\n}\n\nexport function isTrustedCmsParentMessage(\n event: MessageEvent,\n explicitParentOrigin?: string\n): boolean {\n const allowed = getAllowedCmsParentOrigins(explicitParentOrigin);\n if (allowed.length === 0) return false;\n if (!allowed.includes(event.origin)) return false;\n return event.source === window.parent;\n}\n\nexport function postMessageToCmsParent(\n message: unknown,\n options?: { preferredCmsUrl?: string; explicitParentOrigin?: string }\n): void {\n if (typeof window === 'undefined' || !window.parent || window.parent === window) return;\n const targetOrigin = getCmsParentTargetOrigin(\n options?.preferredCmsUrl,\n options?.explicitParentOrigin\n );\n if (!targetOrigin) return;\n window.parent.postMessage(message, targetOrigin);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../lib/block-renderer.tsx","../../lib/cms-overlay-script.ts","../../lib/cms-post-message.ts"],"sourcesContent":["/**\n * Block Renderer Component\n *\n * Dispatches block data to the appropriate component using the ComponentMap pattern.\n * This is the main entry point for rendering blocks from the CMS.\n */\n\nimport React from 'react';\nimport { generateCmsOverlayScript } from './cms-overlay-script';\nimport { getCmsParentTargetOrigin } from './cms-post-message';\nimport type { BlockComponentRegistry, BlockData, ResolvedRouteParams } from './types';\n\ntype TextInfo = {\n value: string;\n path: Array<string | number>;\n parentType?: React.ElementType;\n key?: React.Key | null;\n inSvg?: boolean;\n};\n\ntype ElementInfo = {\n element: React.ReactElement;\n path: Array<string | number>;\n};\n\ntype WalkVisitors = {\n /**\n * Called for every string/number child encountered.\n * Return:\n * - same string (or modified)\n * - a ReactNode (e.g. wrap in <span/>)\n */\n onText?: (info: TextInfo) => React.ReactNode;\n\n /**\n * Called for every ReactElement encountered (after children are processed).\n * Return:\n * - same element\n * - a cloned/modified element\n */\n onElement?: (info: ElementInfo) => React.ReactElement;\n};\n\n/**\n * Recursively maps a ReactNode tree, allowing transformations of text nodes and/or elements.\n * SSR-safe: does not touch DOM APIs.\n */\nexport function walkReactNode(\n node: React.ReactNode,\n visitors: WalkVisitors,\n ctx: {\n path?: Array<string | number>;\n parentType?: React.ElementType;\n key?: React.Key | null;\n inSvg?: boolean;\n } = {}\n): React.ReactNode {\n const path = ctx.path ?? [];\n\n // Fast-path primitives\n if (node == null || typeof node === 'boolean') return node;\n\n if (typeof node === 'string' || typeof node === 'number') {\n const value = String(node);\n return visitors.onText\n ? visitors.onText({ value, path, parentType: ctx.parentType, key: ctx.key, inSvg: ctx.inSvg })\n : node;\n }\n\n // Arrays\n if (Array.isArray(node)) {\n return node.map((child, i) => {\n // biome-ignore lint/suspicious/noExplicitAny: React child key access\n const childKey = (child as any)?.key ?? null;\n const result = walkReactNode(child, visitors, {\n path: [...path, i],\n parentType: ctx.parentType,\n key: childKey,\n inSvg: ctx.inSvg,\n });\n // Ensure array children have keys\n if (React.isValidElement(result) && result.key == null) {\n return React.cloneElement(result, { key: childKey ?? `arr-${path.join('-')}-${i}` });\n }\n return result;\n });\n }\n\n // ReactElement (including Fragment)\n if (React.isValidElement(node)) {\n // biome-ignore lint/suspicious/noExplicitAny: React element props access\n const el = node as React.ReactElement<any>;\n const elProps = el.props as Record<string, unknown> | null;\n\n // Track SVG context so we never inject <span> inside SVG subtrees\n const nextInSvg = ctx.inSvg || el.type === 'svg';\n\n // Recurse into children (if any)\n const hasChildren = elProps && 'children' in elProps;\n const nextChildren = hasChildren\n ? React.Children.map(elProps.children as React.ReactNode, (child, i) => {\n // biome-ignore lint/suspicious/noExplicitAny: React child key access\n const childKey = (child as any)?.key ?? null;\n const result = walkReactNode(child, visitors, {\n path: [...path, 'children', i],\n parentType: el.type as React.ElementType,\n key: childKey,\n inSvg: nextInSvg,\n });\n // Ensure children have keys\n if (React.isValidElement(result) && result.key == null) {\n return React.cloneElement(result, { key: childKey ?? `child-${path.join('-')}-${i}` });\n }\n return result;\n })\n : (elProps?.children as React.ReactNode);\n\n // Only clone if children changed (or if you want to force a clone)\n const cloned = hasChildren\n ? React.cloneElement(el, undefined, nextChildren as React.ReactNode)\n : el;\n\n return visitors.onElement ? visitors.onElement({ element: cloned, path }) : cloned;\n }\n\n // Functions, symbols, portals, etc. are rare here; return as-is\n return node;\n}\n\n// -----------------------------------------------------------------------------\n// Content Value Extraction\n// -----------------------------------------------------------------------------\n\ntype ContentMatch = {\n contentPath: string;\n value: string;\n};\n\n/**\n * Extracts all string values from a content object with their paths.\n * Returns a Map where keys are string values and values are arrays of content paths.\n */\nexport function extractContentValues(\n content: Record<string, unknown>,\n basePath: string[] = []\n): Map<string, ContentMatch[]> {\n const map = new Map<string, ContentMatch[]>();\n\n function walk(obj: unknown, path: string[]) {\n if (typeof obj === 'string' && obj.trim() !== '') {\n const contentPath = path.join('.');\n const existing = map.get(obj) || [];\n existing.push({ contentPath, value: obj });\n map.set(obj, existing);\n } else if (Array.isArray(obj)) {\n for (let index = 0; index < obj.length; index++) {\n walk(obj[index], [...path, String(index)]);\n }\n } else if (obj && typeof obj === 'object') {\n for (const [key, value] of Object.entries(obj)) {\n walk(value, [...path, key]);\n }\n }\n }\n\n walk(content, basePath);\n return map;\n}\n\n// -----------------------------------------------------------------------------\n// CmsEditableInit — render once per page in edit mode\n// -----------------------------------------------------------------------------\n\n/**\n * Renders the shared CMS edit-mode styles and click-routing script.\n * Place this once at the top of your page when edit_mode is active.\n */\nexport function CmsEditableInit({\n cmsUrl,\n cmsParentOrigin,\n}: {\n cmsUrl?: string;\n /** Admin window origin when proxied (from ?cms_parent_origin=). */\n cmsParentOrigin?: string;\n}) {\n const targetOrigin = getCmsParentTargetOrigin(cmsUrl, cmsParentOrigin) ?? '';\n\n return (\n <script\n // biome-ignore lint/security/noDangerouslySetInnerHtml: Inline script for CMS overlay functionality\n dangerouslySetInnerHTML={{\n __html: generateCmsOverlayScript(targetOrigin),\n }}\n />\n );\n}\n\n// -----------------------------------------------------------------------------\n// Props\n// -----------------------------------------------------------------------------\n\ninterface BlockRendererProps {\n /**\n * The block data to render.\n * Must have a `type` field that maps to a registered component.\n */\n block: BlockData;\n registry: Partial<BlockComponentRegistry>;\n /**\n * If true, renders the component without any tree walking or editable wrappers.\n */\n disableEditable?: boolean;\n /**\n * If true, wraps matched text nodes in contentEditable CMS spans.\n */\n enableContentEditable?: boolean;\n /**\n * Resolved route parameters from parametric routes.\n * Each key is a param name (e.g., \"country\") with its value, schema name, and full document.\n */\n routeParams?: ResolvedRouteParams;\n /**\n * The current URL path (e.g. \"/en/test\").\n * When provided, enables path-namespaced component lookup.\n * Registry keys like \"/{lang}/test Article\" will be matched against this path,\n * where `{x}` and `(x)` are treated as single-segment wildcards.\n */\n path?: string;\n}\n\n// -----------------------------------------------------------------------------\n// Path-namespaced component resolution\n// -----------------------------------------------------------------------------\n\n/**\n * Returns true if each segment of `path` matches the corresponding segment in\n * `pattern`, where a pattern segment wrapped in `{…}` or `(…)` is a wildcard.\n */\nexport function pathMatchesPattern(path: string, pattern: string): boolean {\n const pathSegs = path.split('/').filter(Boolean);\n const patternSegs = pattern.split('/').filter(Boolean);\n if (pathSegs.length !== patternSegs.length) return false;\n for (let i = 0; i < patternSegs.length; i++) {\n const seg = patternSegs[i];\n if (!seg) return false;\n if ((seg.startsWith('{') && seg.endsWith('}')) || (seg.startsWith('(') && seg.endsWith(')'))) {\n continue;\n }\n if (seg !== pathSegs[i]) return false;\n }\n return true;\n}\n\n/**\n * Resolves the component for `blockType` from the registry.\n *\n * When `path` is provided, keys of the form `\"/{pattern} BlockType\"` are\n * checked first. The first key whose path pattern matches the current path\n * and whose block-type suffix matches `blockType` wins. Falls back to a\n * direct `registry[blockType]` lookup.\n */\nexport function resolveComponent(\n registry: Partial<BlockComponentRegistry>,\n blockType: string,\n path?: string\n): BlockComponentRegistry[string] | undefined {\n if (path) {\n for (const key of Object.keys(registry)) {\n if (!key.startsWith('/')) continue;\n const spaceIdx = key.indexOf(' ');\n if (spaceIdx === -1) continue;\n const pathPattern = key.slice(0, spaceIdx);\n const registeredType = key.slice(spaceIdx + 1);\n if (registeredType !== blockType) continue;\n if (pathMatchesPattern(path, pathPattern)) {\n return registry[key];\n }\n }\n }\n return registry[blockType];\n}\n\n// -----------------------------------------------------------------------------\n// Component\n// -----------------------------------------------------------------------------\n\n/**\n * Renders a single block by dispatching to the appropriate component.\n *\n * Uses the ComponentMap pattern: the block's `type` field determines which\n * component renders the block's `content`.\n *\n * In editable mode, renders a hidden sentinel before the component. The shared\n * CMS overlay script uses that sentinel to stamp attributes on the component's\n * root element without adding a layout-affecting wrapper.\n */\nexport function BlockRenderer({\n block,\n registry,\n disableEditable,\n enableContentEditable,\n routeParams,\n path,\n}: BlockRendererProps) {\n const Component = resolveComponent(registry, block.type, path);\n\n if (!Component) {\n // Log warning in development, render nothing in production\n if (process.env.NODE_ENV === 'development') {\n console.warn(`[BlockRenderer] Unknown block type: ${block.type}`);\n }\n return null;\n }\n\n // Extract language code from route params if any param is bound to the language schema.\n const language = routeParams\n ? Object.values(routeParams).find((p) => p.schemaName === 'language')?.value\n : undefined;\n\n const component = (\n <Component content={block.content} routeParams={routeParams} language={language} />\n );\n\n if (disableEditable) {\n return component;\n }\n\n const contentEntries = enableContentEditable\n ? [...extractContentValues(block.content as Record<string, unknown>).values()]\n .flat()\n .map(({ value, contentPath }) => ({ v: value, p: contentPath }))\n : undefined;\n\n return (\n <>\n <span\n data-cms-sentinel=\"\"\n data-block-id={block.id}\n data-block-type={block.type}\n data-content-entries={contentEntries ? JSON.stringify(contentEntries) : undefined}\n style={{ display: 'none' }}\n aria-hidden=\"true\"\n />\n {component}\n </>\n );\n}\n","/**\n * CMS Overlay Script\n *\n * Framework-agnostic vanilla JS script that handles all CMS edit mode overlays:\n * - Block hover outlines\n * - Cursor indicator\n * - Block toolbar (move up/down, delete)\n *\n * Uses a sentinel-based approach: hidden <span> elements mark block positions,\n * and this script stamps data attributes on the actual block elements (next sibling).\n * This avoids wrapper elements that could break layouts.\n *\n * Compatible with Next.js, TanStack Start, Remix, and other frameworks.\n */\n\nexport function generateCmsOverlayScript(cmsParentOrigin: string): string {\n return `\n(function() {\n if (window.__cmsOverlayInitialized) return;\n window.__cmsOverlayInitialized = true;\n\n var CMS_PARENT_ORIGIN = ${JSON.stringify(cmsParentOrigin)};\n\n var style = document.createElement('style');\n style.setAttribute('data-cms-overlay', '');\n style.textContent = \\`\n [data-cms-block],\n [data-cms-editable] {\n cursor: pointer;\n }\n [data-cms-editable] {\n border-radius: 2px;\n }\n [data-cms-editable]:not([data-cms-block]):hover {\n outline: 2px solid #3b82f6;\n outline-offset: 2px;\n }\n #cms-overlay-root {\n position: fixed;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n pointer-events: none;\n z-index: 99998;\n }\n #cms-overlay-root > *:not(.cms-block-toolbar) {\n pointer-events: none;\n }\n .cms-block-outline {\n position: fixed;\n border: 2px solid #3b82f6;\n border-radius: 4px;\n pointer-events: none;\n z-index: 99997;\n transition: all 0.15s ease;\n }\n .cms-block-outline.cms-outline-selected {\n border-color: #2563eb;\n border-width: 3px;\n }\n .cms-block-cursor {\n position: fixed;\n width: 22px;\n height: 22px;\n background: radial-gradient(circle, #fff 5px, #000 5px);\n border-radius: 50%;\n pointer-events: none;\n z-index: 99999;\n transform: translate(-50%, -50%);\n opacity: 0;\n transition: opacity 0.1s ease;\n }\n .cms-block-cursor.cms-cursor-visible {\n opacity: 1;\n }\n .cms-block-toolbar {\n position: fixed;\n display: flex;\n gap: 4px;\n background: #1f2937;\n border-radius: 6px;\n padding: 4px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.25);\n z-index: 99999;\n pointer-events: none;\n opacity: 0;\n transform: scale(0.9) translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n }\n .cms-block-toolbar.cms-toolbar-visible {\n opacity: 1;\n transform: scale(1) translateY(0);\n pointer-events: auto;\n }\n .cms-block-toolbar button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n color: #9ca3af;\n border-radius: 4px;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .cms-block-toolbar button:hover {\n background: #374151;\n color: #fff;\n }\n .cms-block-toolbar button.delete:hover {\n background: #dc2626;\n color: #fff;\n }\n .cms-block-toolbar button:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n .cms-block-toolbar button:disabled:hover {\n background: transparent;\n color: #9ca3af;\n }\n .cms-block-toolbar svg {\n width: 16px;\n height: 16px;\n }\n \\`;\n document.head.appendChild(style);\n\n function stampBlockElements() {\n var sentinels = document.querySelectorAll('[data-cms-sentinel]');\n sentinels.forEach(function(sentinel) {\n var blockEl = sentinel.nextElementSibling;\n if (!blockEl) return;\n\n var blockId = sentinel.getAttribute('data-block-id');\n var blockType = sentinel.getAttribute('data-block-type');\n\n blockEl.setAttribute('data-cms-block', '');\n blockEl.setAttribute('data-block-id', blockId);\n blockEl.setAttribute('data-block-type', blockType);\n\n injectEditableSpans(\n blockEl,\n blockId,\n blockType,\n sentinel.getAttribute('data-content-entries')\n );\n });\n }\n\n function injectEditableSpans(blockEl, blockId, blockType, rawEntries) {\n if (!rawEntries || blockEl.querySelector('[data-cms-editable][data-content-path]')) return;\n\n var entries;\n try {\n entries = JSON.parse(rawEntries);\n } catch (_) {\n return;\n }\n if (!Array.isArray(entries) || entries.length === 0) return;\n\n var used = {};\n var walker = document.createTreeWalker(blockEl, NodeFilter.SHOW_TEXT, null);\n var textNodes = [];\n var node = walker.nextNode();\n while (node !== null) {\n if (node.nodeValue && node.nodeValue.trim()) {\n textNodes.push(node);\n }\n node = walker.nextNode();\n }\n\n for (var i = 0; i < textNodes.length; i++) {\n var textNode = textNodes[i];\n var parentEl = textNode.parentElement;\n if (!parentEl || parentEl.closest('svg') || parentEl.closest('[data-cms-editable]')) {\n continue;\n }\n\n var text = textNode.nodeValue;\n if (!text) continue;\n\n for (var j = 0; j < entries.length; j++) {\n var entry = entries[j];\n if (!entry || typeof entry.v !== 'string' || typeof entry.p !== 'string' || used[entry.p]) {\n continue;\n }\n if (text.trim() !== entry.v.trim()) continue;\n\n used[entry.p] = true;\n var span = document.createElement('span');\n span.setAttribute('data-cms-editable', '');\n span.setAttribute('data-block-id', blockId);\n span.setAttribute('data-block-type', blockType);\n span.setAttribute('data-content-path', entry.p);\n span.setAttribute('contenteditable', 'true');\n parentEl.insertBefore(span, textNode);\n span.appendChild(textNode);\n break;\n }\n }\n }\n\n stampBlockElements();\n\n var stampObserver = new MutationObserver(function(mutations) {\n var needsStamp = mutations.some(function(m) {\n return m.addedNodes.length > 0;\n });\n if (needsStamp) stampBlockElements();\n });\n stampObserver.observe(document.body, { childList: true, subtree: true });\n\n var overlayRoot = document.createElement('div');\n overlayRoot.id = 'cms-overlay-root';\n document.body.appendChild(overlayRoot);\n\n var cursor = document.createElement('div');\n cursor.className = 'cms-block-cursor';\n overlayRoot.appendChild(cursor);\n\n var outline = document.createElement('div');\n outline.className = 'cms-block-outline';\n outline.style.display = 'none';\n overlayRoot.appendChild(outline);\n\n var selectedOutline = document.createElement('div');\n selectedOutline.className = 'cms-block-outline cms-outline-selected';\n selectedOutline.style.display = 'none';\n overlayRoot.appendChild(selectedOutline);\n\n var toolbar = document.createElement('div');\n toolbar.className = 'cms-block-toolbar';\n toolbar.setAttribute('data-cms-toolbar', '');\n toolbar.innerHTML = \\`\n <button class=\"move-up\" title=\"Move up\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M18 15l-6-6-6 6\"/>\n </svg>\n </button>\n <button class=\"move-down\" title=\"Move down\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M6 9l6 6 6-6\"/>\n </svg>\n </button>\n <button class=\"delete\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"/>\n </svg>\n </button>\n \\`;\n overlayRoot.appendChild(toolbar);\n\n var currentBlockId = null;\n var toolbarVisible = false;\n var selectedBlockId = null;\n\n function decoratePreviewUrl(rawHref) {\n if (!rawHref) return rawHref;\n try {\n var url = new URL(rawHref, window.location.href);\n if (url.origin !== window.location.origin) return rawHref;\n url.searchParams.set('edit_mode', 'true');\n if (CMS_PARENT_ORIGIN) {\n url.searchParams.set('cms_parent_origin', CMS_PARENT_ORIGIN);\n }\n return url.toString();\n } catch (_) {\n return rawHref;\n }\n }\n\n function preserveEditParamsOnNavigation(e) {\n if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return;\n }\n\n var anchor = e.target.closest('a[href]');\n if (!anchor || (anchor.target && anchor.target !== '_self') || anchor.hasAttribute('download')) {\n return;\n }\n\n var href = anchor.getAttribute('href');\n var decorated = decoratePreviewUrl(href);\n if (decorated && decorated !== href) {\n anchor.setAttribute('href', decorated);\n }\n }\n\n function postToParent(message) {\n if (!CMS_PARENT_ORIGIN || !window.parent || window.parent === window) {\n return;\n }\n window.parent.postMessage(message, CMS_PARENT_ORIGIN);\n }\n\n function sendReadySignal() {\n postToParent({ type: 'cms-preview-ready' });\n }\n sendReadySignal();\n // The preview iframe can execute before the admin listener is attached.\n setTimeout(sendReadySignal, 500);\n setTimeout(sendReadySignal, 1500);\n\n function getBlockElement(blockId) {\n return document.querySelector('[data-block-id=\"' + blockId + '\"]');\n }\n\n function getAllBlocks() {\n return Array.from(document.querySelectorAll('[data-cms-block]'));\n }\n\n function getBlockIndex(blockId) {\n var blocks = getAllBlocks();\n for (var i = 0; i < blocks.length; i++) {\n if (blocks[i].getAttribute('data-block-id') === blockId) return i;\n }\n return -1;\n }\n\n function updateOutline(el, outlineEl) {\n if (!el) {\n outlineEl.style.display = 'none';\n return;\n }\n var rect = el.getBoundingClientRect();\n outlineEl.style.display = 'block';\n outlineEl.style.top = rect.top + 'px';\n outlineEl.style.left = rect.left + 'px';\n outlineEl.style.width = rect.width + 'px';\n outlineEl.style.height = rect.height + 'px';\n }\n\n function positionToolbar(x, y) {\n var rect = toolbar.getBoundingClientRect();\n var top = Math.max(4, y - rect.height - 12);\n var left = Math.max(4, Math.min(x - rect.width / 2, window.innerWidth - rect.width - 4));\n toolbar.style.top = top + 'px';\n toolbar.style.left = left + 'px';\n }\n\n function showToolbar(x, y, blockId) {\n currentBlockId = blockId;\n toolbarVisible = true;\n positionToolbar(x, y);\n toolbar.classList.add('cms-toolbar-visible');\n\n var index = getBlockIndex(blockId);\n var total = getAllBlocks().length;\n toolbar.querySelector('.move-up').disabled = index <= 0;\n toolbar.querySelector('.move-down').disabled = index >= total - 1;\n }\n\n function hideToolbar() {\n toolbarVisible = false;\n toolbar.classList.remove('cms-toolbar-visible');\n }\n\n function showCursor(x, y) {\n cursor.style.top = y + 'px';\n cursor.style.left = x + 'px';\n cursor.classList.add('cms-cursor-visible');\n }\n\n function hideCursor() {\n cursor.classList.remove('cms-cursor-visible');\n }\n\n document.addEventListener('mouseover', function(e) {\n if (toolbarVisible) return;\n var block = e.target.closest('[data-cms-block]');\n if (block) {\n updateOutline(block, outline);\n }\n });\n\n document.addEventListener('mouseout', function(e) {\n var block = e.target.closest('[data-cms-block]');\n var related = e.relatedTarget ? e.relatedTarget.closest('[data-cms-block]') : null;\n if (block && block !== related) {\n outline.style.display = 'none';\n hideCursor();\n }\n });\n\n document.addEventListener('mousemove', function(e) {\n if (toolbarVisible) return;\n var block = e.target.closest('[data-cms-block]');\n if (block) {\n showCursor(e.clientX, e.clientY);\n }\n });\n\n document.addEventListener('contextmenu', function(e) {\n if (e.target.closest('[data-cms-toolbar]')) return;\n var block = e.target.closest('[data-cms-block]');\n if (block) {\n if (toolbarVisible) return;\n e.preventDefault();\n hideCursor();\n outline.style.display = 'none';\n var blockId = block.getAttribute('data-block-id');\n showToolbar(e.clientX, e.clientY, blockId);\n }\n });\n\n document.addEventListener('click', function(e) {\n preserveEditParamsOnNavigation(e);\n\n if (toolbarVisible && !e.target.closest('[data-cms-toolbar]')) {\n var block = e.target.closest('[data-cms-block]');\n if (!block || block.getAttribute('data-block-id') !== currentBlockId) {\n hideToolbar();\n }\n }\n\n if (e.target.closest('[data-cms-toolbar]')) return;\n\n var editable = e.target.closest('[data-cms-editable]');\n if (editable) {\n postToParent({\n type: 'cms-editable-click',\n blockId: editable.getAttribute('data-block-id'),\n blockType: editable.getAttribute('data-block-type'),\n contentPath: editable.getAttribute('data-content-path')\n });\n return;\n }\n\n var block = e.target.closest('[data-cms-block]');\n if (block) {\n postToParent({\n type: 'cms-editable-click',\n blockId: block.getAttribute('data-block-id'),\n blockType: block.getAttribute('data-block-type'),\n contentPath: null\n });\n }\n }, true);\n\n toolbar.querySelector('.move-up').addEventListener('click', function() {\n if (!currentBlockId) return;\n postToParent({ type: 'cms-block-action', action: 'move-up', blockId: currentBlockId });\n hideToolbar();\n });\n\n toolbar.querySelector('.move-down').addEventListener('click', function() {\n if (!currentBlockId) return;\n postToParent({ type: 'cms-block-action', action: 'move-down', blockId: currentBlockId });\n hideToolbar();\n });\n\n toolbar.querySelector('.delete').addEventListener('click', function() {\n if (!currentBlockId) return;\n postToParent({ type: 'cms-block-action', action: 'delete', blockId: currentBlockId });\n hideToolbar();\n });\n\n window.addEventListener('scroll', function() {\n hideCursor();\n hideToolbar();\n if (selectedBlockId) {\n var el = getBlockElement(selectedBlockId);\n updateOutline(el, selectedOutline);\n }\n }, { passive: true, capture: true });\n\n window.addEventListener('resize', function() {\n if (selectedBlockId) {\n var el = getBlockElement(selectedBlockId);\n updateOutline(el, selectedOutline);\n }\n }, { passive: true });\n\n window.addEventListener('message', function(e) {\n if (CMS_PARENT_ORIGIN && e.origin !== CMS_PARENT_ORIGIN) return;\n if (e.source !== window.parent) return;\n\n if (e.data && e.data.type === 'cms-select-block') {\n selectedBlockId = e.data.blockId || null;\n if (selectedBlockId) {\n var el = getBlockElement(selectedBlockId);\n updateOutline(el, selectedOutline);\n } else {\n selectedOutline.style.display = 'none';\n }\n }\n });\n})();\n`;\n}\n","/**\n * Trusted-origin helpers for CMS template-builder postMessage traffic\n * between the admin UI (parent) and the site preview iframe (child).\n */\n\nexport const CMS_PARENT_ORIGIN_PARAM = 'cms_parent_origin';\n\nexport function parseOrigin(url: string | undefined): string | null {\n if (!url) return null;\n try {\n return new URL(url).origin;\n } catch {\n return null;\n }\n}\n\nfunction getParentOriginFromQueryParam(): string | null {\n if (typeof window === 'undefined') return null;\n const value = new URLSearchParams(window.location.search).get(CMS_PARENT_ORIGIN_PARAM);\n return parseOrigin(value ?? undefined);\n}\n\nfunction getReferrerOrigin(): string | null {\n if (typeof document === 'undefined' || !document.referrer) return null;\n return parseOrigin(document.referrer);\n}\n\n/** Same-origin embed (e.g. proxied admin + preview on the customer domain). */\nfunction getSameOriginParentOrigin(): string | null {\n if (typeof window === 'undefined' || !window.parent || window.parent === window) {\n return null;\n }\n try {\n return window.parent.location.origin;\n } catch {\n return null;\n }\n}\n\n/** Origins allowed to send messages to the preview iframe (CMS admin hosts). */\nexport function getAllowedCmsParentOrigins(explicitParentOrigin?: string): string[] {\n const origins = new Set<string>();\n const apiOrigin = parseOrigin(process.env.NEXT_PUBLIC_CMS_API_URL);\n if (apiOrigin) origins.add(apiOrigin);\n\n const fromQuery = parseOrigin(explicitParentOrigin) ?? getParentOriginFromQueryParam();\n if (fromQuery) origins.add(fromQuery);\n\n const referrerOrigin = getReferrerOrigin();\n if (referrerOrigin) origins.add(referrerOrigin);\n\n const sameOriginParent = getSameOriginParentOrigin();\n if (sameOriginParent) origins.add(sameOriginParent);\n\n return [...origins];\n}\n\n/**\n * Target origin when posting from the preview iframe to the CMS parent.\n *\n * When the admin is proxied on a customer domain, the parent window origin is\n * the customer site — not NEXT_PUBLIC_CMS_API_URL. Prefer explicit/referrer/\n * same-origin parent detection over the CMS API URL.\n */\nexport function getCmsParentTargetOrigin(\n preferredCmsUrl?: string,\n explicitParentOrigin?: string\n): string | null {\n const fromExplicit = parseOrigin(explicitParentOrigin) ?? getParentOriginFromQueryParam();\n if (fromExplicit) return fromExplicit;\n\n const referrerOrigin = getReferrerOrigin();\n if (referrerOrigin) return referrerOrigin;\n\n const sameOriginParent = getSameOriginParentOrigin();\n if (sameOriginParent) return sameOriginParent;\n\n const preferredOrigin = parseOrigin(preferredCmsUrl);\n if (preferredOrigin) return preferredOrigin;\n\n const allowed = getAllowedCmsParentOrigins(explicitParentOrigin);\n return allowed[0] ?? null;\n}\n\nexport function isTrustedCmsParentMessage(\n event: MessageEvent,\n explicitParentOrigin?: string\n): boolean {\n const allowed = getAllowedCmsParentOrigins(explicitParentOrigin);\n if (allowed.length === 0) return false;\n if (!allowed.includes(event.origin)) return false;\n return event.source === window.parent;\n}\n\nexport function postMessageToCmsParent(\n message: unknown,\n options?: { preferredCmsUrl?: string; explicitParentOrigin?: string }\n): void {\n if (typeof window === 'undefined' || !window.parent || window.parent === window) return;\n const targetOrigin = getCmsParentTargetOrigin(\n options?.preferredCmsUrl,\n options?.explicitParentOrigin\n );\n if (!targetOrigin) return;\n window.parent.postMessage(message, targetOrigin);\n}\n"],"mappings":";AAOA,OAAO,WAAW;;;ACQX,SAAS,yBAAyB,iBAAiC;AACxE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,4BAKmB,KAAK,UAAU,eAAe,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":[]}
@@ -107,12 +107,26 @@ var BlockToolbar = forwardRef(function BlockToolbar2({ blockId }, ref) {
107
107
  "button",
108
108
  {
109
109
  type: "button",
110
- title: "Add block",
111
- "aria-label": "Add block",
112
- "data-action": "add-block",
110
+ title: "Add block above",
111
+ "aria-label": "Add block above",
112
+ "data-action": "add-block-above",
113
113
  onClick: (e) => {
114
114
  e.stopPropagation();
115
- handleAction("add-block");
115
+ handleAction("add-block-above");
116
+ },
117
+ children: /* @__PURE__ */ jsx(PlusIcon, {})
118
+ }
119
+ ),
120
+ /* @__PURE__ */ jsx(
121
+ "button",
122
+ {
123
+ type: "button",
124
+ title: "Add block below",
125
+ "aria-label": "Add block below",
126
+ "data-action": "add-block-below",
127
+ onClick: (e) => {
128
+ e.stopPropagation();
129
+ handleAction("add-block-below");
116
130
  },
117
131
  children: /* @__PURE__ */ jsx(PlusIcon, {})
118
132
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../lib/block-toolbar.tsx","../../lib/cms-post-message.ts"],"sourcesContent":["'use client';\n\nimport { ChevronDownIcon, ChevronUpIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';\nimport { forwardRef } from 'react';\nimport { postMessageToCmsParent } from './cms-post-message';\n\n/**\n * Block Toolbar Component\n *\n * Provides move up/down and delete controls for blocks in edit mode.\n * Position is managed imperatively by the parent via a forwarded ref —\n * the toolbar follows the mouse cursor and has no internal layout logic.\n */\n\nexport const BlockToolbar = forwardRef<HTMLDivElement, { blockId: string }>(function BlockToolbar(\n { blockId },\n ref\n) {\n const handleAction = (action: string) => {\n postMessageToCmsParent({ type: 'cms-block-action', action, blockId });\n };\n\n return (\n <div ref={ref} className=\"cms-block-toolbar\" data-cms-toolbar=\"true\">\n <button\n type=\"button\"\n title=\"Move up\"\n aria-label=\"Move up\"\n data-action=\"move-up\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-up');\n }}\n >\n <ChevronUpIcon />\n </button>\n <button\n type=\"button\"\n title=\"Move down\"\n aria-label=\"Move down\"\n data-action=\"move-down\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-down');\n }}\n >\n <ChevronDownIcon />\n </button>\n <button\n type=\"button\"\n title=\"Add block\"\n aria-label=\"Add block\"\n data-action=\"add-block\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('add-block');\n }}\n >\n <PlusIcon />\n </button>\n <button\n type=\"button\"\n className=\"delete\"\n title=\"Delete block\"\n aria-label=\"Delete block\"\n data-action=\"delete\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('delete');\n }}\n >\n <TrashIcon />\n </button>\n </div>\n );\n});\n","/**\n * Trusted-origin helpers for CMS template-builder postMessage traffic\n * between the admin UI (parent) and the site preview iframe (child).\n */\n\nexport const CMS_PARENT_ORIGIN_PARAM = 'cms_parent_origin';\n\nexport function parseOrigin(url: string | undefined): string | null {\n if (!url) return null;\n try {\n return new URL(url).origin;\n } catch {\n return null;\n }\n}\n\nfunction getParentOriginFromQueryParam(): string | null {\n if (typeof window === 'undefined') return null;\n const value = new URLSearchParams(window.location.search).get(CMS_PARENT_ORIGIN_PARAM);\n return parseOrigin(value ?? undefined);\n}\n\nfunction getReferrerOrigin(): string | null {\n if (typeof document === 'undefined' || !document.referrer) return null;\n return parseOrigin(document.referrer);\n}\n\n/** Same-origin embed (e.g. proxied admin + preview on the customer domain). */\nfunction getSameOriginParentOrigin(): string | null {\n if (typeof window === 'undefined' || !window.parent || window.parent === window) {\n return null;\n }\n try {\n return window.parent.location.origin;\n } catch {\n return null;\n }\n}\n\n/** Origins allowed to send messages to the preview iframe (CMS admin hosts). */\nexport function getAllowedCmsParentOrigins(explicitParentOrigin?: string): string[] {\n const origins = new Set<string>();\n const apiOrigin = parseOrigin(process.env.NEXT_PUBLIC_CMS_API_URL);\n if (apiOrigin) origins.add(apiOrigin);\n\n const fromQuery = parseOrigin(explicitParentOrigin) ?? getParentOriginFromQueryParam();\n if (fromQuery) origins.add(fromQuery);\n\n const referrerOrigin = getReferrerOrigin();\n if (referrerOrigin) origins.add(referrerOrigin);\n\n const sameOriginParent = getSameOriginParentOrigin();\n if (sameOriginParent) origins.add(sameOriginParent);\n\n return [...origins];\n}\n\n/**\n * Target origin when posting from the preview iframe to the CMS parent.\n *\n * When the admin is proxied on a customer domain, the parent window origin is\n * the customer site — not NEXT_PUBLIC_CMS_API_URL. Prefer explicit/referrer/\n * same-origin parent detection over the CMS API URL.\n */\nexport function getCmsParentTargetOrigin(\n preferredCmsUrl?: string,\n explicitParentOrigin?: string\n): string | null {\n const fromExplicit = parseOrigin(explicitParentOrigin) ?? getParentOriginFromQueryParam();\n if (fromExplicit) return fromExplicit;\n\n const referrerOrigin = getReferrerOrigin();\n if (referrerOrigin) return referrerOrigin;\n\n const sameOriginParent = getSameOriginParentOrigin();\n if (sameOriginParent) return sameOriginParent;\n\n const preferredOrigin = parseOrigin(preferredCmsUrl);\n if (preferredOrigin) return preferredOrigin;\n\n const allowed = getAllowedCmsParentOrigins(explicitParentOrigin);\n return allowed[0] ?? null;\n}\n\nexport function isTrustedCmsParentMessage(\n event: MessageEvent,\n explicitParentOrigin?: string\n): boolean {\n const allowed = getAllowedCmsParentOrigins(explicitParentOrigin);\n if (allowed.length === 0) return false;\n if (!allowed.includes(event.origin)) return false;\n return event.source === window.parent;\n}\n\nexport function postMessageToCmsParent(\n message: unknown,\n options?: { preferredCmsUrl?: string; explicitParentOrigin?: string }\n): void {\n if (typeof window === 'undefined' || !window.parent || window.parent === window) return;\n const targetOrigin = getCmsParentTargetOrigin(\n options?.preferredCmsUrl,\n options?.explicitParentOrigin\n );\n if (!targetOrigin) return;\n window.parent.postMessage(message, targetOrigin);\n}\n"],"mappings":"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","names":["BlockToolbar"]}
1
+ {"version":3,"sources":["../../lib/block-toolbar.tsx","../../lib/cms-post-message.ts"],"sourcesContent":["'use client';\n\nimport { ChevronDownIcon, ChevronUpIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';\nimport { forwardRef } from 'react';\nimport { postMessageToCmsParent } from './cms-post-message';\n\n/**\n * Block Toolbar Component\n *\n * Provides move up/down and delete controls for blocks in edit mode.\n * Position is managed imperatively by the parent via a forwarded ref —\n * the toolbar follows the mouse cursor and has no internal layout logic.\n */\n\nexport const BlockToolbar = forwardRef<HTMLDivElement, { blockId: string }>(function BlockToolbar(\n { blockId },\n ref\n) {\n const handleAction = (action: string) => {\n postMessageToCmsParent({ type: 'cms-block-action', action, blockId });\n };\n\n return (\n <div ref={ref} className=\"cms-block-toolbar\" data-cms-toolbar=\"true\">\n <button\n type=\"button\"\n title=\"Move up\"\n aria-label=\"Move up\"\n data-action=\"move-up\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-up');\n }}\n >\n <ChevronUpIcon />\n </button>\n <button\n type=\"button\"\n title=\"Move down\"\n aria-label=\"Move down\"\n data-action=\"move-down\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-down');\n }}\n >\n <ChevronDownIcon />\n </button>\n <button\n type=\"button\"\n title=\"Add block above\"\n aria-label=\"Add block above\"\n data-action=\"add-block-above\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('add-block-above');\n }}\n >\n <PlusIcon />\n </button>\n <button\n type=\"button\"\n title=\"Add block below\"\n aria-label=\"Add block below\"\n data-action=\"add-block-below\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('add-block-below');\n }}\n >\n <PlusIcon />\n </button>\n <button\n type=\"button\"\n className=\"delete\"\n title=\"Delete block\"\n aria-label=\"Delete block\"\n data-action=\"delete\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('delete');\n }}\n >\n <TrashIcon />\n </button>\n </div>\n );\n});\n","/**\n * Trusted-origin helpers for CMS template-builder postMessage traffic\n * between the admin UI (parent) and the site preview iframe (child).\n */\n\nexport const CMS_PARENT_ORIGIN_PARAM = 'cms_parent_origin';\n\nexport function parseOrigin(url: string | undefined): string | null {\n if (!url) return null;\n try {\n return new URL(url).origin;\n } catch {\n return null;\n }\n}\n\nfunction getParentOriginFromQueryParam(): string | null {\n if (typeof window === 'undefined') return null;\n const value = new URLSearchParams(window.location.search).get(CMS_PARENT_ORIGIN_PARAM);\n return parseOrigin(value ?? undefined);\n}\n\nfunction getReferrerOrigin(): string | null {\n if (typeof document === 'undefined' || !document.referrer) return null;\n return parseOrigin(document.referrer);\n}\n\n/** Same-origin embed (e.g. proxied admin + preview on the customer domain). */\nfunction getSameOriginParentOrigin(): string | null {\n if (typeof window === 'undefined' || !window.parent || window.parent === window) {\n return null;\n }\n try {\n return window.parent.location.origin;\n } catch {\n return null;\n }\n}\n\n/** Origins allowed to send messages to the preview iframe (CMS admin hosts). */\nexport function getAllowedCmsParentOrigins(explicitParentOrigin?: string): string[] {\n const origins = new Set<string>();\n const apiOrigin = parseOrigin(process.env.NEXT_PUBLIC_CMS_API_URL);\n if (apiOrigin) origins.add(apiOrigin);\n\n const fromQuery = parseOrigin(explicitParentOrigin) ?? getParentOriginFromQueryParam();\n if (fromQuery) origins.add(fromQuery);\n\n const referrerOrigin = getReferrerOrigin();\n if (referrerOrigin) origins.add(referrerOrigin);\n\n const sameOriginParent = getSameOriginParentOrigin();\n if (sameOriginParent) origins.add(sameOriginParent);\n\n return [...origins];\n}\n\n/**\n * Target origin when posting from the preview iframe to the CMS parent.\n *\n * When the admin is proxied on a customer domain, the parent window origin is\n * the customer site — not NEXT_PUBLIC_CMS_API_URL. Prefer explicit/referrer/\n * same-origin parent detection over the CMS API URL.\n */\nexport function getCmsParentTargetOrigin(\n preferredCmsUrl?: string,\n explicitParentOrigin?: string\n): string | null {\n const fromExplicit = parseOrigin(explicitParentOrigin) ?? getParentOriginFromQueryParam();\n if (fromExplicit) return fromExplicit;\n\n const referrerOrigin = getReferrerOrigin();\n if (referrerOrigin) return referrerOrigin;\n\n const sameOriginParent = getSameOriginParentOrigin();\n if (sameOriginParent) return sameOriginParent;\n\n const preferredOrigin = parseOrigin(preferredCmsUrl);\n if (preferredOrigin) return preferredOrigin;\n\n const allowed = getAllowedCmsParentOrigins(explicitParentOrigin);\n return allowed[0] ?? null;\n}\n\nexport function isTrustedCmsParentMessage(\n event: MessageEvent,\n explicitParentOrigin?: string\n): boolean {\n const allowed = getAllowedCmsParentOrigins(explicitParentOrigin);\n if (allowed.length === 0) return false;\n if (!allowed.includes(event.origin)) return false;\n return event.source === window.parent;\n}\n\nexport function postMessageToCmsParent(\n message: unknown,\n options?: { preferredCmsUrl?: string; explicitParentOrigin?: string }\n): void {\n if (typeof window === 'undefined' || !window.parent || window.parent === window) return;\n const targetOrigin = getCmsParentTargetOrigin(\n options?.preferredCmsUrl,\n options?.explicitParentOrigin\n );\n if (!targetOrigin) return;\n window.parent.postMessage(message, targetOrigin);\n}\n"],"mappings":";;;;AAEA,SAAS,iBAAiB,eAAe,UAAU,iBAAiB;AACpE,SAAS,kBAAkB;;;ACEpB,IAAM,0BAA0B;AAEhC,SAAS,YAAY,KAAwC;AAClE,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,gCAA+C;AACtD,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAM,QAAQ,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAAE,IAAI,uBAAuB;AACrF,SAAO,YAAY,SAAS,MAAS;AACvC;AAEA,SAAS,oBAAmC;AAC1C,MAAI,OAAO,aAAa,eAAe,CAAC,SAAS,SAAU,QAAO;AAClE,SAAO,YAAY,SAAS,QAAQ;AACtC;AAGA,SAAS,4BAA2C;AAClD,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,UAAU,OAAO,WAAW,QAAQ;AAC/E,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,OAAO,OAAO,SAAS;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,2BAA2B,sBAAyC;AAClF,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,YAAY,YAAY,QAAQ,IAAI,uBAAuB;AACjE,MAAI,UAAW,SAAQ,IAAI,SAAS;AAEpC,QAAM,YAAY,YAAY,oBAAoB,KAAK,8BAA8B;AACrF,MAAI,UAAW,SAAQ,IAAI,SAAS;AAEpC,QAAM,iBAAiB,kBAAkB;AACzC,MAAI,eAAgB,SAAQ,IAAI,cAAc;AAE9C,QAAM,mBAAmB,0BAA0B;AACnD,MAAI,iBAAkB,SAAQ,IAAI,gBAAgB;AAElD,SAAO,CAAC,GAAG,OAAO;AACpB;AASO,SAAS,yBACd,iBACA,sBACe;AACf,QAAM,eAAe,YAAY,oBAAoB,KAAK,8BAA8B;AACxF,MAAI,aAAc,QAAO;AAEzB,QAAM,iBAAiB,kBAAkB;AACzC,MAAI,eAAgB,QAAO;AAE3B,QAAM,mBAAmB,0BAA0B;AACnD,MAAI,iBAAkB,QAAO;AAE7B,QAAM,kBAAkB,YAAY,eAAe;AACnD,MAAI,gBAAiB,QAAO;AAE5B,QAAM,UAAU,2BAA2B,oBAAoB;AAC/D,SAAO,QAAQ,CAAC,KAAK;AACvB;AAYO,SAAS,uBACd,SACA,SACM;AACN,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,UAAU,OAAO,WAAW,OAAQ;AACjF,QAAM,eAAe;AAAA,IACnB,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AACA,MAAI,CAAC,aAAc;AACnB,SAAO,OAAO,YAAY,SAAS,YAAY;AACjD;;;ADlFI,SAWI,KAXJ;AATG,IAAM,eAAe,WAAgD,SAASA,cACnF,EAAE,QAAQ,GACV,KACA;AACA,QAAM,eAAe,CAAC,WAAmB;AACvC,2BAAuB,EAAE,MAAM,oBAAoB,QAAQ,QAAQ,CAAC;AAAA,EACtE;AAEA,SACE,qBAAC,SAAI,KAAU,WAAU,qBAAoB,oBAAiB,QAC5D;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,SAAS;AAAA,QACxB;AAAA,QAEA,8BAAC,iBAAc;AAAA;AAAA,IACjB;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,WAAW;AAAA,QAC1B;AAAA,QAEA,8BAAC,mBAAgB;AAAA;AAAA,IACnB;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,iBAAiB;AAAA,QAChC;AAAA,QAEA,8BAAC,YAAS;AAAA;AAAA,IACZ;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,iBAAiB;AAAA,QAChC;AAAA,QAEA,8BAAC,YAAS;AAAA;AAAA,IACZ;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,QAAQ;AAAA,QACvB;AAAA,QAEA,8BAAC,aAAU;AAAA;AAAA,IACb;AAAA,KACF;AAEJ,CAAC;","names":["BlockToolbar"]}
@@ -161,12 +161,26 @@ var BlockToolbar = forwardRef(function BlockToolbar2({ blockId }, ref) {
161
161
  "button",
162
162
  {
163
163
  type: "button",
164
- title: "Add block",
165
- "aria-label": "Add block",
166
- "data-action": "add-block",
164
+ title: "Add block above",
165
+ "aria-label": "Add block above",
166
+ "data-action": "add-block-above",
167
167
  onClick: (e) => {
168
168
  e.stopPropagation();
169
- handleAction("add-block");
169
+ handleAction("add-block-above");
170
+ },
171
+ children: /* @__PURE__ */ jsx2(PlusIcon, {})
172
+ }
173
+ ),
174
+ /* @__PURE__ */ jsx2(
175
+ "button",
176
+ {
177
+ type: "button",
178
+ title: "Add block below",
179
+ "aria-label": "Add block below",
180
+ "data-action": "add-block-below",
181
+ onClick: (e) => {
182
+ e.stopPropagation();
183
+ handleAction("add-block-below");
170
184
  },
171
185
  children: /* @__PURE__ */ jsx2(PlusIcon, {})
172
186
  }
@@ -206,7 +220,7 @@ var CMS_STYLES = `
206
220
  z-index: 99998;
207
221
  }
208
222
  #${CMS_OVERLAY_ROOT_ID} > * {
209
- pointer-events: auto;
223
+ pointer-events: none;
210
224
  }
211
225
  .cms-block-cursor {
212
226
  position: fixed; width: 22px; height: 22px;