cms-renderer 0.5.0 → 0.5.2
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/lib/block-renderer.d.ts +12 -5
- package/dist/lib/block-renderer.js +191 -164
- package/dist/lib/block-renderer.js.map +1 -1
- package/dist/lib/client-editable-block.d.ts +5 -1
- package/dist/lib/client-editable-block.js +27 -12
- package/dist/lib/client-editable-block.js.map +1 -1
- package/dist/lib/renderer.js +205 -176
- package/dist/lib/renderer.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,19 +3,23 @@
|
|
|
3
3
|
|
|
4
4
|
// lib/client-editable-block.tsx
|
|
5
5
|
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
6
|
-
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
function ClientEditableBlock({
|
|
8
8
|
blockId,
|
|
9
9
|
blockType,
|
|
10
10
|
contentEntries,
|
|
11
|
-
children
|
|
11
|
+
children,
|
|
12
|
+
blockProps
|
|
12
13
|
}) {
|
|
13
|
-
const
|
|
14
|
+
const sentinelRef = useRef(null);
|
|
14
15
|
const observerRef = useRef(null);
|
|
15
16
|
const isInjectingRef = useRef(false);
|
|
17
|
+
const getBlockRoot = useCallback(() => {
|
|
18
|
+
return sentinelRef.current?.nextElementSibling ?? null;
|
|
19
|
+
}, []);
|
|
16
20
|
const injectSpans = useCallback(() => {
|
|
17
21
|
if (isInjectingRef.current) return;
|
|
18
|
-
const container =
|
|
22
|
+
const container = getBlockRoot();
|
|
19
23
|
if (!container) return;
|
|
20
24
|
isInjectingRef.current = true;
|
|
21
25
|
observerRef.current?.disconnect();
|
|
@@ -41,6 +45,7 @@ function ClientEditableBlock({
|
|
|
41
45
|
n = walker.nextNode();
|
|
42
46
|
}
|
|
43
47
|
for (const textNode of textNodes) {
|
|
48
|
+
if (textNode.parentElement?.closest("svg") != null) continue;
|
|
44
49
|
const text = textNode.nodeValue;
|
|
45
50
|
if (!text) continue;
|
|
46
51
|
for (const entry of contentEntries) {
|
|
@@ -48,6 +53,7 @@ function ClientEditableBlock({
|
|
|
48
53
|
if (text.indexOf(entry.v) !== -1 && text.trim() === entry.v.trim()) {
|
|
49
54
|
used.add(entry.p);
|
|
50
55
|
const span = document.createElement("span");
|
|
56
|
+
span.style.display = "contents";
|
|
51
57
|
span.setAttribute("data-cms-editable", "");
|
|
52
58
|
span.setAttribute("data-block-id", blockId);
|
|
53
59
|
span.setAttribute("data-block-type", blockType);
|
|
@@ -60,22 +66,28 @@ function ClientEditableBlock({
|
|
|
60
66
|
}
|
|
61
67
|
} finally {
|
|
62
68
|
isInjectingRef.current = false;
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
observerRef.current.observe(
|
|
69
|
+
const blockRoot = getBlockRoot();
|
|
70
|
+
if (blockRoot && observerRef.current) {
|
|
71
|
+
observerRef.current.observe(blockRoot, {
|
|
66
72
|
childList: true,
|
|
67
73
|
subtree: true,
|
|
68
74
|
characterData: true
|
|
69
75
|
});
|
|
70
76
|
}
|
|
71
77
|
}
|
|
72
|
-
}, [blockId, blockType, contentEntries]);
|
|
78
|
+
}, [blockId, blockType, contentEntries, getBlockRoot]);
|
|
73
79
|
useLayoutEffect(() => {
|
|
80
|
+
const blockRoot = getBlockRoot();
|
|
81
|
+
if (blockRoot && blockProps) {
|
|
82
|
+
for (const [key, value] of Object.entries(blockProps)) {
|
|
83
|
+
blockRoot.setAttribute(key, value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
74
86
|
injectSpans();
|
|
75
87
|
const observer = new MutationObserver(injectSpans);
|
|
76
88
|
observerRef.current = observer;
|
|
77
|
-
if (
|
|
78
|
-
observer.observe(
|
|
89
|
+
if (blockRoot) {
|
|
90
|
+
observer.observe(blockRoot, {
|
|
79
91
|
childList: true,
|
|
80
92
|
subtree: true,
|
|
81
93
|
characterData: true
|
|
@@ -85,8 +97,11 @@ function ClientEditableBlock({
|
|
|
85
97
|
observer.disconnect();
|
|
86
98
|
observerRef.current = null;
|
|
87
99
|
};
|
|
88
|
-
}, [injectSpans]);
|
|
89
|
-
return /* @__PURE__ */
|
|
100
|
+
}, [injectSpans, getBlockRoot, blockProps]);
|
|
101
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
102
|
+
/* @__PURE__ */ jsx("span", { ref: sentinelRef, style: { display: "none" }, "aria-hidden": "true" }),
|
|
103
|
+
children
|
|
104
|
+
] });
|
|
90
105
|
}
|
|
91
106
|
export {
|
|
92
107
|
ClientEditableBlock
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../lib/client-editable-block.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useLayoutEffect, useRef } from 'react';\n\ninterface ContentEntry {\n v: string;\n p: string;\n}\n\ninterface ClientEditableBlockProps {\n blockId: string;\n blockType: string;\n contentEntries: ContentEntry[];\n children: React.ReactNode;\n}\n\n/**\n * Client-side span injector for blocks whose components use React hooks\n * and therefore can't be walked server-side.\n *\n * Uses a MutationObserver to re-inject [data-cms-editable] spans after every\n * React commit (including state-driven re-renders like animations), so the\n * overlays survive React's reconciliation.\n */\nexport function ClientEditableBlock({\n blockId,\n blockType,\n contentEntries,\n children,\n}: ClientEditableBlockProps) {\n const
|
|
1
|
+
{"version":3,"sources":["../../lib/client-editable-block.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useLayoutEffect, useRef } from 'react';\n\ninterface ContentEntry {\n v: string;\n p: string;\n}\n\ninterface ClientEditableBlockProps {\n blockId: string;\n blockType: string;\n contentEntries: ContentEntry[];\n children: React.ReactNode;\n blockProps?: Record<string, string>;\n}\n\n/**\n * Client-side span injector for blocks whose components use React hooks\n * and therefore can't be walked server-side.\n *\n * Renders a hidden sentinel element, then locates the block's actual root DOM\n * element via nextElementSibling — no wrapper div is added to the tree.\n *\n * Uses a MutationObserver to re-inject [data-cms-editable] spans after every\n * React commit (including state-driven re-renders like animations), so the\n * overlays survive React's reconciliation.\n */\nexport function ClientEditableBlock({\n blockId,\n blockType,\n contentEntries,\n children,\n blockProps,\n}: ClientEditableBlockProps) {\n const sentinelRef = useRef<HTMLSpanElement>(null);\n const observerRef = useRef<MutationObserver | null>(null);\n const isInjectingRef = useRef(false);\n\n const getBlockRoot = useCallback((): Element | null => {\n return sentinelRef.current?.nextElementSibling ?? null;\n }, []);\n\n const injectSpans = useCallback(() => {\n if (isInjectingRef.current) return;\n const container = getBlockRoot();\n if (!container) return;\n\n isInjectingRef.current = true;\n // Disconnect observer while we mutate the DOM to avoid re-entrant calls.\n observerRef.current?.disconnect();\n\n try {\n // Remove previously injected text-level spans, restoring the original text nodes.\n const existing = Array.from(\n container.querySelectorAll<HTMLElement>('span[data-cms-editable][data-content-path]')\n );\n for (const span of existing) {\n const parent = span.parentNode;\n if (!parent) continue;\n while (span.firstChild) parent.insertBefore(span.firstChild, span);\n parent.removeChild(span);\n }\n\n // Walk all visible text nodes and wrap the ones that match content values.\n const used = new Set<string>();\n const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);\n const textNodes: Text[] = [];\n let n = walker.nextNode();\n while (n !== null) {\n const textNode = n as Text;\n if (\n textNode.parentElement?.closest('.cms-block-toolbar') == null &&\n textNode.nodeValue?.trim()\n ) {\n textNodes.push(textNode);\n }\n n = walker.nextNode();\n }\n\n for (const textNode of textNodes) {\n // Never inject spans inside SVG — <span> is not valid SVG content\n if (textNode.parentElement?.closest('svg') != null) continue;\n\n const text = textNode.nodeValue;\n if (!text) continue;\n for (const entry of contentEntries) {\n if (used.has(entry.p)) continue;\n if (text.indexOf(entry.v) !== -1 && text.trim() === entry.v.trim()) {\n used.add(entry.p);\n const span = document.createElement('span');\n // Inline style ensures layout-transparency before the stylesheet cascade applies\n span.style.display = 'contents';\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 textNode.parentNode?.insertBefore(span, textNode);\n span.appendChild(textNode);\n break;\n }\n }\n }\n } finally {\n isInjectingRef.current = false;\n // Reconnect after our mutations are done.\n const blockRoot = getBlockRoot();\n if (blockRoot && observerRef.current) {\n observerRef.current.observe(blockRoot, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n }\n }, [blockId, blockType, contentEntries, getBlockRoot]);\n\n useLayoutEffect(() => {\n const blockRoot = getBlockRoot();\n\n // Stamp block data attributes directly onto the component's root DOM element.\n if (blockRoot && blockProps) {\n for (const [key, value] of Object.entries(blockProps)) {\n blockRoot.setAttribute(key, value);\n }\n }\n\n // Initial injection after React's first commit.\n injectSpans();\n\n // Re-inject whenever the child component mutates the DOM\n // (e.g. animated state changes that swap visible elements).\n const observer = new MutationObserver(injectSpans);\n observerRef.current = observer;\n\n if (blockRoot) {\n observer.observe(blockRoot, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n\n return () => {\n observer.disconnect();\n observerRef.current = null;\n };\n }, [injectSpans, getBlockRoot, blockProps]);\n\n return (\n <>\n {/* Sentinel: zero-size, invisible anchor used only to locate the block's root element via nextElementSibling */}\n <span ref={sentinelRef} style={{ display: 'none' }} aria-hidden=\"true\" />\n {children}\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/lib/renderer.js
CHANGED
|
@@ -245,13 +245,27 @@ import { notFound } from "next/navigation";
|
|
|
245
245
|
import React from "react";
|
|
246
246
|
import { BlockToolbar } from "./block-toolbar.js";
|
|
247
247
|
import { ClientEditableBlock } from "./client-editable-block.js";
|
|
248
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
248
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
249
|
+
var SKIP_SPAN_PARENTS = /* @__PURE__ */ new Set([
|
|
250
|
+
// SVG text-bearing elements — spans are not valid SVG children
|
|
251
|
+
"text",
|
|
252
|
+
"tspan",
|
|
253
|
+
"textPath",
|
|
254
|
+
"desc",
|
|
255
|
+
// Form elements whose text content must not be interrupted
|
|
256
|
+
"option",
|
|
257
|
+
"optgroup",
|
|
258
|
+
// Non-rendered elements
|
|
259
|
+
"script",
|
|
260
|
+
"style",
|
|
261
|
+
"noscript"
|
|
262
|
+
]);
|
|
249
263
|
function walkReactNode(node, visitors, ctx = {}) {
|
|
250
264
|
const path = ctx.path ?? [];
|
|
251
265
|
if (node == null || typeof node === "boolean") return node;
|
|
252
266
|
if (typeof node === "string" || typeof node === "number") {
|
|
253
267
|
const value = String(node);
|
|
254
|
-
return visitors.onText ? visitors.onText({ value, path, parentType: ctx.parentType, key: ctx.key }) : node;
|
|
268
|
+
return visitors.onText ? visitors.onText({ value, path, parentType: ctx.parentType, key: ctx.key, inSvg: ctx.inSvg }) : node;
|
|
255
269
|
}
|
|
256
270
|
if (Array.isArray(node)) {
|
|
257
271
|
return node.map((child, i) => {
|
|
@@ -259,7 +273,8 @@ function walkReactNode(node, visitors, ctx = {}) {
|
|
|
259
273
|
const result = walkReactNode(child, visitors, {
|
|
260
274
|
path: [...path, i],
|
|
261
275
|
parentType: ctx.parentType,
|
|
262
|
-
key: childKey
|
|
276
|
+
key: childKey,
|
|
277
|
+
inSvg: ctx.inSvg
|
|
263
278
|
});
|
|
264
279
|
if (React.isValidElement(result) && result.key == null) {
|
|
265
280
|
return React.cloneElement(result, { key: childKey ?? `arr-${path.join("-")}-${i}` });
|
|
@@ -270,13 +285,15 @@ function walkReactNode(node, visitors, ctx = {}) {
|
|
|
270
285
|
if (React.isValidElement(node)) {
|
|
271
286
|
const el = node;
|
|
272
287
|
const elProps = el.props;
|
|
288
|
+
const nextInSvg = ctx.inSvg || el.type === "svg";
|
|
273
289
|
const hasChildren = elProps && "children" in elProps;
|
|
274
290
|
const nextChildren = hasChildren ? React.Children.map(elProps.children, (child, i) => {
|
|
275
291
|
const childKey = child?.key ?? null;
|
|
276
292
|
const result = walkReactNode(child, visitors, {
|
|
277
293
|
path: [...path, "children", i],
|
|
278
294
|
parentType: el.type,
|
|
279
|
-
key: childKey
|
|
295
|
+
key: childKey,
|
|
296
|
+
inSvg: nextInSvg
|
|
280
297
|
});
|
|
281
298
|
if (React.isValidElement(result) && result.key == null) {
|
|
282
299
|
return React.cloneElement(result, { key: childKey ?? `child-${path.join("-")}-${i}` });
|
|
@@ -309,6 +326,140 @@ function extractContentValues(content, basePath = []) {
|
|
|
309
326
|
walk(content, basePath);
|
|
310
327
|
return map;
|
|
311
328
|
}
|
|
329
|
+
var CMS_EDITABLE_STYLES = `
|
|
330
|
+
.cms-block-toolbar {
|
|
331
|
+
position: fixed;
|
|
332
|
+
transform: translateX(-50%);
|
|
333
|
+
display: flex;
|
|
334
|
+
gap: 4px;
|
|
335
|
+
background: #1f2937;
|
|
336
|
+
border-radius: 6px;
|
|
337
|
+
padding: 4px;
|
|
338
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
339
|
+
opacity: 0;
|
|
340
|
+
pointer-events: none;
|
|
341
|
+
transition: opacity 0.15s ease;
|
|
342
|
+
z-index: 9999;
|
|
343
|
+
}
|
|
344
|
+
.cms-block-toolbar button {
|
|
345
|
+
display: flex;
|
|
346
|
+
align-items: center;
|
|
347
|
+
justify-content: center;
|
|
348
|
+
width: 28px;
|
|
349
|
+
height: 28px;
|
|
350
|
+
border: none;
|
|
351
|
+
background: transparent;
|
|
352
|
+
color: #9ca3af;
|
|
353
|
+
border-radius: 4px;
|
|
354
|
+
cursor: pointer;
|
|
355
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
356
|
+
}
|
|
357
|
+
.cms-block-toolbar button:hover {
|
|
358
|
+
background: #374151;
|
|
359
|
+
color: #fff;
|
|
360
|
+
}
|
|
361
|
+
.cms-block-toolbar button.delete:hover {
|
|
362
|
+
background: #dc2626;
|
|
363
|
+
color: #fff;
|
|
364
|
+
}
|
|
365
|
+
.cms-block-toolbar button:disabled {
|
|
366
|
+
opacity: 0.4;
|
|
367
|
+
cursor: not-allowed;
|
|
368
|
+
}
|
|
369
|
+
.cms-block-toolbar button:disabled:hover {
|
|
370
|
+
background: transparent;
|
|
371
|
+
color: #9ca3af;
|
|
372
|
+
}
|
|
373
|
+
.cms-block-toolbar svg {
|
|
374
|
+
width: 16px;
|
|
375
|
+
height: 16px;
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
378
|
+
var CMS_EDITABLE_SCRIPT = `
|
|
379
|
+
(function() {
|
|
380
|
+
if (!window.__cmsEditableInitialized) {
|
|
381
|
+
window.__cmsEditableInitialized = true;
|
|
382
|
+
var _ab = null;
|
|
383
|
+
|
|
384
|
+
function _tb(bid) {
|
|
385
|
+
return document.querySelector('.cms-block-toolbar[data-block-id="' + bid + '"]');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function _show(bid, target) {
|
|
389
|
+
var tb = _tb(bid);
|
|
390
|
+
if (tb && target) {
|
|
391
|
+
var r = target.getBoundingClientRect();
|
|
392
|
+
tb.style.left = Math.round(r.left + r.width / 2) + 'px';
|
|
393
|
+
tb.style.top = Math.round(r.bottom + 8) + 'px';
|
|
394
|
+
tb.style.opacity = '1';
|
|
395
|
+
tb.style.pointerEvents = 'auto';
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function _hide(bid) {
|
|
400
|
+
var tb = _tb(bid);
|
|
401
|
+
if (tb) { tb.style.opacity = '0'; tb.style.pointerEvents = 'none'; }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
document.addEventListener('mouseover', function(e) {
|
|
405
|
+
if (e.target.closest('.cms-block-toolbar')) return;
|
|
406
|
+
var bel = e.target.closest('[data-cms-block]');
|
|
407
|
+
var bid = bel ? bel.getAttribute('data-block-id') : null;
|
|
408
|
+
if (bid === _ab) return;
|
|
409
|
+
if (_ab) _hide(_ab);
|
|
410
|
+
_ab = bid;
|
|
411
|
+
if (bid) _show(bid, e.target);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
document.addEventListener('click', function(e) {
|
|
415
|
+
if (e.target.closest('.cms-block-toolbar')) return;
|
|
416
|
+
|
|
417
|
+
var path = e.composedPath ? e.composedPath() : [e.target];
|
|
418
|
+
var et = null;
|
|
419
|
+
for (var i = 0; i < path.length; i++) {
|
|
420
|
+
var n = path[i];
|
|
421
|
+
if (n.nodeType === 1 && n.hasAttribute && n.hasAttribute('data-cms-editable')) { et = n; break; }
|
|
422
|
+
}
|
|
423
|
+
if (!et) et = e.target.closest && e.target.closest('[data-cms-editable]');
|
|
424
|
+
|
|
425
|
+
if (et) {
|
|
426
|
+
if (window.parent && window.parent !== window) {
|
|
427
|
+
window.parent.postMessage({
|
|
428
|
+
type: 'cms-editable-click',
|
|
429
|
+
blockId: et.getAttribute('data-block-id'),
|
|
430
|
+
blockType: et.getAttribute('data-block-type'),
|
|
431
|
+
contentPath: et.getAttribute('data-content-path')
|
|
432
|
+
}, '*');
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
var bt = e.target.closest && e.target.closest('[data-cms-block]');
|
|
438
|
+
if (bt) {
|
|
439
|
+
if (window.parent && window.parent !== window) {
|
|
440
|
+
window.parent.postMessage({
|
|
441
|
+
type: 'cms-editable-click',
|
|
442
|
+
blockId: bt.getAttribute('data-block-id'),
|
|
443
|
+
blockType: bt.getAttribute('data-block-type'),
|
|
444
|
+
contentPath: null
|
|
445
|
+
}, '*');
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
})();
|
|
451
|
+
`;
|
|
452
|
+
function CmsEditableInit() {
|
|
453
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
454
|
+
/* @__PURE__ */ jsx("style", { children: CMS_EDITABLE_STYLES }),
|
|
455
|
+
/* @__PURE__ */ jsx(
|
|
456
|
+
"script",
|
|
457
|
+
{
|
|
458
|
+
dangerouslySetInnerHTML: { __html: CMS_EDITABLE_SCRIPT }
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
] });
|
|
462
|
+
}
|
|
312
463
|
function pathMatchesPattern(path, pattern) {
|
|
313
464
|
const pathSegs = path.split("/").filter(Boolean);
|
|
314
465
|
const patternSegs = pattern.split("/").filter(Boolean);
|
|
@@ -398,7 +549,11 @@ function BlockRenderer({
|
|
|
398
549
|
if (isWalkable) {
|
|
399
550
|
const usedPaths = /* @__PURE__ */ new Set();
|
|
400
551
|
renderedComponent = walkReactNode(renderedTree, {
|
|
401
|
-
onText: ({ value, key, path: path2 }) => {
|
|
552
|
+
onText: ({ value, key, path: path2, inSvg, parentType }) => {
|
|
553
|
+
if (inSvg) return value;
|
|
554
|
+
if (parentType && typeof parentType === "string" && SKIP_SPAN_PARENTS.has(parentType)) {
|
|
555
|
+
return value;
|
|
556
|
+
}
|
|
402
557
|
const matches = contentValueMap.get(value);
|
|
403
558
|
if (!matches || matches.length === 0) return value;
|
|
404
559
|
const match = matches.find((m) => !usedPaths.has(m.contentPath)) ?? matches[0];
|
|
@@ -412,6 +567,7 @@ function BlockRenderer({
|
|
|
412
567
|
"data-block-id": block.id,
|
|
413
568
|
"data-block-type": block.type,
|
|
414
569
|
"data-content-path": match.contentPath,
|
|
570
|
+
style: { display: "contents" },
|
|
415
571
|
children: value
|
|
416
572
|
},
|
|
417
573
|
spanKey
|
|
@@ -423,165 +579,35 @@ function BlockRenderer({
|
|
|
423
579
|
} catch {
|
|
424
580
|
}
|
|
425
581
|
const contentEntries = needsClientSideSpans ? Array.from(contentValueMap.entries()).map(([value, matches]) => ({ v: value, p: matches[0]?.contentPath })).filter((e) => !!e.p) : [];
|
|
426
|
-
|
|
427
|
-
"
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
.cms-block-toolbar button {
|
|
457
|
-
display: flex;
|
|
458
|
-
align-items: center;
|
|
459
|
-
justify-content: center;
|
|
460
|
-
width: 28px;
|
|
461
|
-
height: 28px;
|
|
462
|
-
border: none;
|
|
463
|
-
background: transparent;
|
|
464
|
-
color: #9ca3af;
|
|
465
|
-
border-radius: 4px;
|
|
466
|
-
cursor: pointer;
|
|
467
|
-
transition: background 0.15s ease, color 0.15s ease;
|
|
468
|
-
}
|
|
469
|
-
.cms-block-toolbar button:hover {
|
|
470
|
-
background: #374151;
|
|
471
|
-
color: #fff;
|
|
472
|
-
}
|
|
473
|
-
.cms-block-toolbar button.delete:hover {
|
|
474
|
-
background: #dc2626;
|
|
475
|
-
color: #fff;
|
|
476
|
-
}
|
|
477
|
-
.cms-block-toolbar button:disabled {
|
|
478
|
-
opacity: 0.4;
|
|
479
|
-
cursor: not-allowed;
|
|
480
|
-
}
|
|
481
|
-
.cms-block-toolbar button:disabled:hover {
|
|
482
|
-
background: transparent;
|
|
483
|
-
color: #9ca3af;
|
|
484
|
-
}
|
|
485
|
-
.cms-block-toolbar svg {
|
|
486
|
-
width: 16px;
|
|
487
|
-
height: 16px;
|
|
488
|
-
}
|
|
489
|
-
` }),
|
|
490
|
-
/* @__PURE__ */ jsx(
|
|
491
|
-
"script",
|
|
492
|
-
{
|
|
493
|
-
dangerouslySetInnerHTML: {
|
|
494
|
-
__html: `
|
|
495
|
-
(function() {
|
|
496
|
-
if (!window.__cmsEditableInitialized) {
|
|
497
|
-
window.__cmsEditableInitialized = true;
|
|
498
|
-
var _ab = null;
|
|
499
|
-
|
|
500
|
-
function _tb(bid) {
|
|
501
|
-
return document.querySelector('.cms-block-toolbar[data-block-id="' + bid + '"]');
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function _show(bid, target) {
|
|
505
|
-
var tb = _tb(bid);
|
|
506
|
-
if (tb && target) {
|
|
507
|
-
var r = target.getBoundingClientRect();
|
|
508
|
-
tb.style.left = Math.round(r.left + r.width / 2) + 'px';
|
|
509
|
-
tb.style.top = Math.round(r.bottom + 8) + 'px';
|
|
510
|
-
tb.style.opacity = '1';
|
|
511
|
-
tb.style.pointerEvents = 'auto';
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function _hide(bid) {
|
|
516
|
-
var tb = _tb(bid);
|
|
517
|
-
if (tb) { tb.style.opacity = '0'; tb.style.pointerEvents = 'none'; }
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
document.addEventListener('mouseover', function(e) {
|
|
521
|
-
if (e.target.closest('.cms-block-toolbar')) return;
|
|
522
|
-
var bel = e.target.closest('[data-cms-block]');
|
|
523
|
-
var bid = bel ? bel.getAttribute('data-block-id') : null;
|
|
524
|
-
if (bid === _ab) return;
|
|
525
|
-
if (_ab) _hide(_ab);
|
|
526
|
-
_ab = bid;
|
|
527
|
-
if (bid) _show(bid, e.target);
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
document.addEventListener('click', function(e) {
|
|
531
|
-
if (e.target.closest('.cms-block-toolbar')) return;
|
|
532
|
-
|
|
533
|
-
var path = e.composedPath ? e.composedPath() : [e.target];
|
|
534
|
-
var et = null;
|
|
535
|
-
for (var i = 0; i < path.length; i++) {
|
|
536
|
-
var n = path[i];
|
|
537
|
-
if (n.nodeType === 1 && n.hasAttribute && n.hasAttribute('data-cms-editable')) { et = n; break; }
|
|
538
|
-
}
|
|
539
|
-
if (!et) et = e.target.closest && e.target.closest('[data-cms-editable]');
|
|
540
|
-
|
|
541
|
-
if (et) {
|
|
542
|
-
if (window.parent && window.parent !== window) {
|
|
543
|
-
window.parent.postMessage({
|
|
544
|
-
type: 'cms-editable-click',
|
|
545
|
-
blockId: et.getAttribute('data-block-id'),
|
|
546
|
-
blockType: et.getAttribute('data-block-type'),
|
|
547
|
-
contentPath: et.getAttribute('data-content-path')
|
|
548
|
-
}, '*');
|
|
549
|
-
}
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
var bt = e.target.closest && e.target.closest('[data-cms-block]');
|
|
554
|
-
if (bt) {
|
|
555
|
-
if (window.parent && window.parent !== window) {
|
|
556
|
-
window.parent.postMessage({
|
|
557
|
-
type: 'cms-editable-click',
|
|
558
|
-
blockId: bt.getAttribute('data-block-id'),
|
|
559
|
-
blockType: bt.getAttribute('data-block-type'),
|
|
560
|
-
contentPath: null
|
|
561
|
-
}, '*');
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
})();
|
|
567
|
-
`
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
),
|
|
571
|
-
needsClientSideSpans && contentEntries.length > 0 ? /* @__PURE__ */ jsx(
|
|
572
|
-
ClientEditableBlock,
|
|
573
|
-
{
|
|
574
|
-
blockId: block.id,
|
|
575
|
-
blockType: block.type,
|
|
576
|
-
contentEntries,
|
|
577
|
-
children: renderedComponent
|
|
578
|
-
}
|
|
579
|
-
) : renderedComponent,
|
|
580
|
-
/* @__PURE__ */ jsx(BlockToolbar, { blockId: block.id }, "cms-toolbar")
|
|
581
|
-
]
|
|
582
|
-
},
|
|
583
|
-
block.id
|
|
584
|
-
);
|
|
582
|
+
const blockProps = {
|
|
583
|
+
"data-cms-block": "true",
|
|
584
|
+
"data-block-id": block.id,
|
|
585
|
+
"data-block-type": block.type
|
|
586
|
+
};
|
|
587
|
+
if (needsClientSideSpans) {
|
|
588
|
+
return /* @__PURE__ */ jsxs(
|
|
589
|
+
ClientEditableBlock,
|
|
590
|
+
{
|
|
591
|
+
blockId: block.id,
|
|
592
|
+
blockType: block.type,
|
|
593
|
+
contentEntries,
|
|
594
|
+
blockProps,
|
|
595
|
+
children: [
|
|
596
|
+
renderedComponent,
|
|
597
|
+
/* @__PURE__ */ jsx(BlockToolbar, { blockId: block.id }, "cms-toolbar")
|
|
598
|
+
]
|
|
599
|
+
},
|
|
600
|
+
block.id
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
const rootWithProps = React.isValidElement(renderedComponent) ? React.cloneElement(
|
|
604
|
+
renderedComponent,
|
|
605
|
+
blockProps
|
|
606
|
+
) : renderedComponent;
|
|
607
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
608
|
+
rootWithProps,
|
|
609
|
+
/* @__PURE__ */ jsx(BlockToolbar, { blockId: block.id }, "cms-toolbar")
|
|
610
|
+
] });
|
|
585
611
|
}
|
|
586
612
|
|
|
587
613
|
// lib/cms-api.ts
|
|
@@ -624,7 +650,7 @@ function getCmsClient(options) {
|
|
|
624
650
|
}
|
|
625
651
|
|
|
626
652
|
// lib/renderer.tsx
|
|
627
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
653
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
628
654
|
function getWebsiteId(providedWebsiteId) {
|
|
629
655
|
const websiteId = providedWebsiteId ?? process.env.NEXT_PUBLIC_WEBSITE_ID ?? process.env.WEBSITE_ID ?? process.env.CMS_WEBSITE_ID;
|
|
630
656
|
if (!websiteId) {
|
|
@@ -743,17 +769,20 @@ async function ParametricRoutePage({
|
|
|
743
769
|
}
|
|
744
770
|
])
|
|
745
771
|
) : void 0;
|
|
746
|
-
return /* @__PURE__ */
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
772
|
+
return /* @__PURE__ */ jsxs2("main", { children: [
|
|
773
|
+
editMode && /* @__PURE__ */ jsx2(CmsEditableInit, {}),
|
|
774
|
+
blocks.map((block) => /* @__PURE__ */ jsx2(
|
|
775
|
+
BlockRenderer,
|
|
776
|
+
{
|
|
777
|
+
block,
|
|
778
|
+
registry: registry ?? {},
|
|
779
|
+
disableEditable: !editMode,
|
|
780
|
+
routeParams,
|
|
781
|
+
path
|
|
782
|
+
},
|
|
783
|
+
block.id
|
|
784
|
+
))
|
|
785
|
+
] });
|
|
757
786
|
} catch (error) {
|
|
758
787
|
console.error(`Route fetch error for path: ${path}`, error);
|
|
759
788
|
const errorCode = error instanceof Error && "data" in error ? error.data?.code : error instanceof Error && "code" in error ? error.code : void 0;
|