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.
@@ -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: CMS_EDITABLE_STYLES }),
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: { __html: CMS_EDITABLE_SCRIPT }
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
- let renderedComponent = component;
545
- let needsClientSideSpans = true;
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