cms-renderer 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -154
- package/dist/lib/block-renderer.d.ts +9 -10
- package/dist/lib/block-renderer.js +109 -239
- package/dist/lib/block-renderer.js.map +1 -1
- package/dist/lib/block-toolbar.d.ts +5 -5
- package/dist/lib/block-toolbar.js +89 -70
- package/dist/lib/block-toolbar.js.map +1 -1
- package/dist/lib/client-editable-block.d.ts +11 -10
- package/dist/lib/client-editable-block.js +248 -31
- package/dist/lib/client-editable-block.js.map +1 -1
- package/dist/lib/custom-schemas.js +77 -39
- package/dist/lib/custom-schemas.js.map +1 -1
- package/dist/lib/renderer.js +109 -284
- package/dist/lib/renderer.js.map +1 -1
- package/package.json +1 -1
package/dist/lib/renderer.js
CHANGED
|
@@ -243,68 +243,8 @@ import { notFound } from "next/navigation";
|
|
|
243
243
|
|
|
244
244
|
// lib/block-renderer.tsx
|
|
245
245
|
import React from "react";
|
|
246
|
-
import { BlockToolbar } from "./block-toolbar.js";
|
|
247
246
|
import { ClientEditableBlock } from "./client-editable-block.js";
|
|
248
247
|
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
|
-
]);
|
|
263
|
-
function walkReactNode(node, visitors, ctx = {}) {
|
|
264
|
-
const path = ctx.path ?? [];
|
|
265
|
-
if (node == null || typeof node === "boolean") return node;
|
|
266
|
-
if (typeof node === "string" || typeof node === "number") {
|
|
267
|
-
const value = String(node);
|
|
268
|
-
return visitors.onText ? visitors.onText({ value, path, parentType: ctx.parentType, key: ctx.key, inSvg: ctx.inSvg }) : node;
|
|
269
|
-
}
|
|
270
|
-
if (Array.isArray(node)) {
|
|
271
|
-
return node.map((child, i) => {
|
|
272
|
-
const childKey = child?.key ?? null;
|
|
273
|
-
const result = walkReactNode(child, visitors, {
|
|
274
|
-
path: [...path, i],
|
|
275
|
-
parentType: ctx.parentType,
|
|
276
|
-
key: childKey,
|
|
277
|
-
inSvg: ctx.inSvg
|
|
278
|
-
});
|
|
279
|
-
if (React.isValidElement(result) && result.key == null) {
|
|
280
|
-
return React.cloneElement(result, { key: childKey ?? `arr-${path.join("-")}-${i}` });
|
|
281
|
-
}
|
|
282
|
-
return result;
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
if (React.isValidElement(node)) {
|
|
286
|
-
const el = node;
|
|
287
|
-
const elProps = el.props;
|
|
288
|
-
const nextInSvg = ctx.inSvg || el.type === "svg";
|
|
289
|
-
const hasChildren = elProps && "children" in elProps;
|
|
290
|
-
const nextChildren = hasChildren ? React.Children.map(elProps.children, (child, i) => {
|
|
291
|
-
const childKey = child?.key ?? null;
|
|
292
|
-
const result = walkReactNode(child, visitors, {
|
|
293
|
-
path: [...path, "children", i],
|
|
294
|
-
parentType: el.type,
|
|
295
|
-
key: childKey,
|
|
296
|
-
inSvg: nextInSvg
|
|
297
|
-
});
|
|
298
|
-
if (React.isValidElement(result) && result.key == null) {
|
|
299
|
-
return React.cloneElement(result, { key: childKey ?? `child-${path.join("-")}-${i}` });
|
|
300
|
-
}
|
|
301
|
-
return result;
|
|
302
|
-
}) : elProps?.children;
|
|
303
|
-
const cloned = hasChildren ? React.cloneElement(el, void 0, nextChildren) : el;
|
|
304
|
-
return visitors.onElement ? visitors.onElement({ element: cloned, path }) : cloned;
|
|
305
|
-
}
|
|
306
|
-
return node;
|
|
307
|
-
}
|
|
308
248
|
function extractContentValues(content, basePath = []) {
|
|
309
249
|
const map = /* @__PURE__ */ new Map();
|
|
310
250
|
function walk(obj, path) {
|
|
@@ -326,136 +266,118 @@ function extractContentValues(content, basePath = []) {
|
|
|
326
266
|
walk(content, basePath);
|
|
327
267
|
return map;
|
|
328
268
|
}
|
|
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
269
|
function CmsEditableInit() {
|
|
453
270
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
454
|
-
/* @__PURE__ */ jsx("style", { children:
|
|
271
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
272
|
+
[data-cms-block]:hover {
|
|
273
|
+
outline: 2px solid #3b82f6;
|
|
274
|
+
outline-offset: 4px;
|
|
275
|
+
}
|
|
276
|
+
[data-cms-editable] {
|
|
277
|
+
cursor: pointer;
|
|
278
|
+
border-radius: 2px;
|
|
279
|
+
}
|
|
280
|
+
[data-cms-editable]:hover {
|
|
281
|
+
outline: 2px solid #3b82f6;
|
|
282
|
+
outline-offset: 2px;
|
|
283
|
+
}
|
|
284
|
+
.cms-block-toolbar {
|
|
285
|
+
position: absolute;
|
|
286
|
+
bottom: 8px;
|
|
287
|
+
left: 50%;
|
|
288
|
+
transform: translateX(-50%);
|
|
289
|
+
display: flex;
|
|
290
|
+
gap: 4px;
|
|
291
|
+
background: #1f2937;
|
|
292
|
+
border-radius: 6px;
|
|
293
|
+
padding: 4px;
|
|
294
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
295
|
+
opacity: 0;
|
|
296
|
+
pointer-events: none;
|
|
297
|
+
transition: opacity 0.15s ease;
|
|
298
|
+
z-index: 1000;
|
|
299
|
+
}
|
|
300
|
+
[data-cms-block]:hover .cms-block-toolbar {
|
|
301
|
+
opacity: 1;
|
|
302
|
+
pointer-events: auto;
|
|
303
|
+
}
|
|
304
|
+
.cms-block-toolbar button {
|
|
305
|
+
display: flex;
|
|
306
|
+
align-items: center;
|
|
307
|
+
justify-content: center;
|
|
308
|
+
width: 28px;
|
|
309
|
+
height: 28px;
|
|
310
|
+
border: none;
|
|
311
|
+
background: transparent;
|
|
312
|
+
color: #9ca3af;
|
|
313
|
+
border-radius: 4px;
|
|
314
|
+
cursor: pointer;
|
|
315
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
316
|
+
}
|
|
317
|
+
.cms-block-toolbar button:hover {
|
|
318
|
+
background: #374151;
|
|
319
|
+
color: #fff;
|
|
320
|
+
}
|
|
321
|
+
.cms-block-toolbar button.delete:hover {
|
|
322
|
+
background: #dc2626;
|
|
323
|
+
color: #fff;
|
|
324
|
+
}
|
|
325
|
+
.cms-block-toolbar button:disabled {
|
|
326
|
+
opacity: 0.4;
|
|
327
|
+
cursor: not-allowed;
|
|
328
|
+
}
|
|
329
|
+
.cms-block-toolbar button:disabled:hover {
|
|
330
|
+
background: transparent;
|
|
331
|
+
color: #9ca3af;
|
|
332
|
+
}
|
|
333
|
+
.cms-block-toolbar svg {
|
|
334
|
+
width: 16px;
|
|
335
|
+
height: 16px;
|
|
336
|
+
}
|
|
337
|
+
` }),
|
|
455
338
|
/* @__PURE__ */ jsx(
|
|
456
339
|
"script",
|
|
457
340
|
{
|
|
458
|
-
dangerouslySetInnerHTML: {
|
|
341
|
+
dangerouslySetInnerHTML: {
|
|
342
|
+
__html: `
|
|
343
|
+
(function() {
|
|
344
|
+
if (!window.__cmsEditableInitialized) {
|
|
345
|
+
window.__cmsEditableInitialized = true;
|
|
346
|
+
|
|
347
|
+
document.addEventListener('click', function(e) {
|
|
348
|
+
if (e.target.closest('.cms-block-toolbar')) return;
|
|
349
|
+
|
|
350
|
+
var editableTarget = e.target.closest('[data-cms-editable]');
|
|
351
|
+
if (editableTarget) {
|
|
352
|
+
var message = {
|
|
353
|
+
type: 'cms-editable-click',
|
|
354
|
+
blockId: editableTarget.getAttribute('data-block-id'),
|
|
355
|
+
blockType: editableTarget.getAttribute('data-block-type'),
|
|
356
|
+
contentPath: editableTarget.getAttribute('data-content-path')
|
|
357
|
+
};
|
|
358
|
+
if (window.parent && window.parent !== window) {
|
|
359
|
+
window.parent.postMessage(message, '*');
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
var blockTarget = e.target.closest('[data-cms-block]');
|
|
365
|
+
if (blockTarget) {
|
|
366
|
+
var message = {
|
|
367
|
+
type: 'cms-editable-click',
|
|
368
|
+
blockId: blockTarget.getAttribute('data-block-id'),
|
|
369
|
+
blockType: blockTarget.getAttribute('data-block-type'),
|
|
370
|
+
contentPath: null
|
|
371
|
+
};
|
|
372
|
+
if (window.parent && window.parent !== window) {
|
|
373
|
+
window.parent.postMessage(message, '*');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
})();
|
|
379
|
+
`
|
|
380
|
+
}
|
|
459
381
|
}
|
|
460
382
|
)
|
|
461
383
|
] });
|
|
@@ -490,38 +412,6 @@ function resolveComponent(registry, blockType, path) {
|
|
|
490
412
|
}
|
|
491
413
|
return registry[blockType];
|
|
492
414
|
}
|
|
493
|
-
function renderToWalkableTree(node, keyPrefix = "") {
|
|
494
|
-
if (node == null || typeof node === "boolean") return node;
|
|
495
|
-
if (typeof node === "string" || typeof node === "number") return node;
|
|
496
|
-
if (Array.isArray(node)) {
|
|
497
|
-
return node.map((child, i) => {
|
|
498
|
-
const result = renderToWalkableTree(child, `${keyPrefix}${i}-`);
|
|
499
|
-
if (React.isValidElement(result) && result.key == null) {
|
|
500
|
-
const existingKey = child?.key;
|
|
501
|
-
return React.cloneElement(result, { key: existingKey ?? `${keyPrefix}${i}` });
|
|
502
|
-
}
|
|
503
|
-
return result;
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
if (React.isValidElement(node)) {
|
|
507
|
-
const el = node;
|
|
508
|
-
const elProps = el.props;
|
|
509
|
-
if (typeof el.type === "function") {
|
|
510
|
-
try {
|
|
511
|
-
const rendered = el.type(el.props);
|
|
512
|
-
return renderToWalkableTree(rendered, keyPrefix);
|
|
513
|
-
} catch {
|
|
514
|
-
return node;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
if (elProps && "children" in elProps) {
|
|
518
|
-
const newChildren = renderToWalkableTree(elProps.children, keyPrefix);
|
|
519
|
-
return React.cloneElement(el, void 0, newChildren);
|
|
520
|
-
}
|
|
521
|
-
return node;
|
|
522
|
-
}
|
|
523
|
-
return node;
|
|
524
|
-
}
|
|
525
415
|
function BlockRenderer({
|
|
526
416
|
block,
|
|
527
417
|
registry,
|
|
@@ -541,73 +431,8 @@ function BlockRenderer({
|
|
|
541
431
|
return component;
|
|
542
432
|
}
|
|
543
433
|
const contentValueMap = extractContentValues(block.content);
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
try {
|
|
547
|
-
const renderedTree = renderToWalkableTree(component);
|
|
548
|
-
const isWalkable = React.isValidElement(renderedTree) && typeof renderedTree.type === "string";
|
|
549
|
-
if (isWalkable) {
|
|
550
|
-
const usedPaths = /* @__PURE__ */ new Set();
|
|
551
|
-
renderedComponent = walkReactNode(renderedTree, {
|
|
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
|
-
}
|
|
557
|
-
const matches = contentValueMap.get(value);
|
|
558
|
-
if (!matches || matches.length === 0) return value;
|
|
559
|
-
const match = matches.find((m) => !usedPaths.has(m.contentPath)) ?? matches[0];
|
|
560
|
-
if (!match) return value;
|
|
561
|
-
usedPaths.add(match.contentPath);
|
|
562
|
-
const spanKey = key ?? `${block.id}-${match.contentPath}-${path2.join("-")}`;
|
|
563
|
-
return /* @__PURE__ */ jsx(
|
|
564
|
-
"span",
|
|
565
|
-
{
|
|
566
|
-
"data-cms-editable": true,
|
|
567
|
-
"data-block-id": block.id,
|
|
568
|
-
"data-block-type": block.type,
|
|
569
|
-
"data-content-path": match.contentPath,
|
|
570
|
-
style: { display: "contents" },
|
|
571
|
-
children: value
|
|
572
|
-
},
|
|
573
|
-
spanKey
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
needsClientSideSpans = false;
|
|
578
|
-
}
|
|
579
|
-
} catch {
|
|
580
|
-
}
|
|
581
|
-
const contentEntries = needsClientSideSpans ? Array.from(contentValueMap.entries()).map(([value, matches]) => ({ v: value, p: matches[0]?.contentPath })).filter((e) => !!e.p) : [];
|
|
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
|
-
] });
|
|
434
|
+
const contentEntries = Array.from(contentValueMap.entries()).map(([value, matches]) => ({ v: value, p: matches[0]?.contentPath })).filter((e) => !!e.p);
|
|
435
|
+
return /* @__PURE__ */ jsx(ClientEditableBlock, { blockId: block.id, blockType: block.type, contentEntries, children: component });
|
|
611
436
|
}
|
|
612
437
|
|
|
613
438
|
// lib/cms-api.ts
|