cms-renderer 0.6.10 → 0.6.11

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.
@@ -15,10 +15,14 @@ import '@repo/cms-schema/blocks';
15
15
  type ParametricRouteProps = CmsConfig & {
16
16
  params: Promise<{
17
17
  slug: string[];
18
- }>;
18
+ }> | {
19
+ slug: string[];
20
+ };
19
21
  searchParams?: Promise<{
20
- [key: string]: string | string[] | undefined;
21
- }>;
22
+ [key: string]: string | string[] | boolean | undefined;
23
+ }> | {
24
+ [key: string]: string | string[] | boolean | undefined;
25
+ };
22
26
  registry?: Partial<BlockComponentRegistry>;
23
27
  };
24
28
  type ParametricRouteResult = {
@@ -381,7 +381,391 @@ var LanguageSchema = z9.object({
381
381
 
382
382
  // lib/block-renderer.tsx
383
383
  import React from "react";
384
- import { ClientEditableBlock } from "./client-editable-block.js";
384
+
385
+ // lib/cms-overlay-script.ts
386
+ function generateCmsOverlayScript(cmsParentOrigin) {
387
+ return `
388
+ (function() {
389
+ if (window.__cmsOverlayInitialized) return;
390
+ window.__cmsOverlayInitialized = true;
391
+
392
+ var CMS_PARENT_ORIGIN = ${JSON.stringify(cmsParentOrigin)};
393
+
394
+ var style = document.createElement('style');
395
+ style.setAttribute('data-cms-overlay', '');
396
+ style.textContent = \`
397
+ [data-cms-editable] {
398
+ cursor: pointer;
399
+ border-radius: 2px;
400
+ }
401
+ [data-cms-editable]:hover {
402
+ outline: 2px solid #3b82f6;
403
+ outline-offset: 2px;
404
+ }
405
+ #cms-overlay-root {
406
+ position: fixed;
407
+ top: 0;
408
+ left: 0;
409
+ width: 0;
410
+ height: 0;
411
+ pointer-events: none;
412
+ z-index: 99998;
413
+ }
414
+ #cms-overlay-root > * {
415
+ pointer-events: auto;
416
+ }
417
+ .cms-block-outline {
418
+ position: fixed;
419
+ border: 2px solid #3b82f6;
420
+ border-radius: 4px;
421
+ pointer-events: none;
422
+ z-index: 99997;
423
+ transition: all 0.15s ease;
424
+ }
425
+ .cms-block-outline.cms-outline-selected {
426
+ border-color: #2563eb;
427
+ border-width: 3px;
428
+ }
429
+ .cms-block-cursor {
430
+ position: fixed;
431
+ width: 22px;
432
+ height: 22px;
433
+ background: radial-gradient(circle, #fff 5px, #000 5px);
434
+ border-radius: 50%;
435
+ pointer-events: none;
436
+ z-index: 99999;
437
+ transform: translate(-50%, -50%);
438
+ opacity: 0;
439
+ transition: opacity 0.1s ease;
440
+ }
441
+ .cms-block-cursor.cms-cursor-visible {
442
+ opacity: 1;
443
+ }
444
+ .cms-block-toolbar {
445
+ position: fixed;
446
+ display: flex;
447
+ gap: 4px;
448
+ background: #1f2937;
449
+ border-radius: 6px;
450
+ padding: 4px;
451
+ box-shadow: 0 4px 12px rgba(0,0,0,0.25);
452
+ z-index: 99999;
453
+ pointer-events: none;
454
+ opacity: 0;
455
+ transform: scale(0.9) translateY(4px);
456
+ transition: opacity 0.15s ease, transform 0.15s ease;
457
+ }
458
+ .cms-block-toolbar.cms-toolbar-visible {
459
+ opacity: 1;
460
+ transform: scale(1) translateY(0);
461
+ pointer-events: auto;
462
+ }
463
+ .cms-block-toolbar button {
464
+ display: flex;
465
+ align-items: center;
466
+ justify-content: center;
467
+ width: 28px;
468
+ height: 28px;
469
+ border: none;
470
+ background: transparent;
471
+ color: #9ca3af;
472
+ border-radius: 4px;
473
+ cursor: pointer;
474
+ transition: background 0.15s ease, color 0.15s ease;
475
+ }
476
+ .cms-block-toolbar button:hover {
477
+ background: #374151;
478
+ color: #fff;
479
+ }
480
+ .cms-block-toolbar button.delete:hover {
481
+ background: #dc2626;
482
+ color: #fff;
483
+ }
484
+ .cms-block-toolbar button:disabled {
485
+ opacity: 0.4;
486
+ cursor: not-allowed;
487
+ }
488
+ .cms-block-toolbar button:disabled:hover {
489
+ background: transparent;
490
+ color: #9ca3af;
491
+ }
492
+ .cms-block-toolbar svg {
493
+ width: 16px;
494
+ height: 16px;
495
+ }
496
+ \`;
497
+ document.head.appendChild(style);
498
+
499
+ function stampBlockElements() {
500
+ var sentinels = document.querySelectorAll('[data-cms-sentinel]');
501
+ sentinels.forEach(function(sentinel) {
502
+ var blockEl = sentinel.nextElementSibling;
503
+ if (!blockEl) return;
504
+
505
+ var blockId = sentinel.getAttribute('data-block-id');
506
+ var blockType = sentinel.getAttribute('data-block-type');
507
+
508
+ blockEl.setAttribute('data-cms-block', '');
509
+ blockEl.setAttribute('data-block-id', blockId);
510
+ blockEl.setAttribute('data-block-type', blockType);
511
+ blockEl.setAttribute('data-cms-editable', '');
512
+ });
513
+ }
514
+
515
+ stampBlockElements();
516
+
517
+ var stampObserver = new MutationObserver(function(mutations) {
518
+ var needsStamp = mutations.some(function(m) {
519
+ return m.addedNodes.length > 0;
520
+ });
521
+ if (needsStamp) stampBlockElements();
522
+ });
523
+ stampObserver.observe(document.body, { childList: true, subtree: true });
524
+
525
+ var overlayRoot = document.createElement('div');
526
+ overlayRoot.id = 'cms-overlay-root';
527
+ document.body.appendChild(overlayRoot);
528
+
529
+ var cursor = document.createElement('div');
530
+ cursor.className = 'cms-block-cursor';
531
+ overlayRoot.appendChild(cursor);
532
+
533
+ var outline = document.createElement('div');
534
+ outline.className = 'cms-block-outline';
535
+ outline.style.display = 'none';
536
+ overlayRoot.appendChild(outline);
537
+
538
+ var selectedOutline = document.createElement('div');
539
+ selectedOutline.className = 'cms-block-outline cms-outline-selected';
540
+ selectedOutline.style.display = 'none';
541
+ overlayRoot.appendChild(selectedOutline);
542
+
543
+ var toolbar = document.createElement('div');
544
+ toolbar.className = 'cms-block-toolbar';
545
+ toolbar.setAttribute('data-cms-toolbar', '');
546
+ toolbar.innerHTML = \`
547
+ <button class="move-up" title="Move up">
548
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
549
+ <path d="M18 15l-6-6-6 6"/>
550
+ </svg>
551
+ </button>
552
+ <button class="move-down" title="Move down">
553
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
554
+ <path d="M6 9l6 6 6-6"/>
555
+ </svg>
556
+ </button>
557
+ <button class="delete" title="Delete">
558
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
559
+ <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"/>
560
+ </svg>
561
+ </button>
562
+ \`;
563
+ overlayRoot.appendChild(toolbar);
564
+
565
+ var currentBlockId = null;
566
+ var toolbarVisible = false;
567
+ var selectedBlockId = null;
568
+
569
+ function postToParent(message) {
570
+ if (!CMS_PARENT_ORIGIN || !window.parent || window.parent === window) {
571
+ return;
572
+ }
573
+ window.parent.postMessage(message, CMS_PARENT_ORIGIN);
574
+ }
575
+
576
+ function sendReadySignal() {
577
+ postToParent({ type: 'cms-preview-ready' });
578
+ }
579
+ sendReadySignal();
580
+ // The preview iframe can execute before the admin listener is attached.
581
+ setTimeout(sendReadySignal, 500);
582
+ setTimeout(sendReadySignal, 1500);
583
+
584
+ function getBlockElement(blockId) {
585
+ return document.querySelector('[data-block-id="' + blockId + '"]');
586
+ }
587
+
588
+ function getAllBlocks() {
589
+ return Array.from(document.querySelectorAll('[data-cms-block]'));
590
+ }
591
+
592
+ function getBlockIndex(blockId) {
593
+ var blocks = getAllBlocks();
594
+ for (var i = 0; i < blocks.length; i++) {
595
+ if (blocks[i].getAttribute('data-block-id') === blockId) return i;
596
+ }
597
+ return -1;
598
+ }
599
+
600
+ function updateOutline(el, outlineEl) {
601
+ if (!el) {
602
+ outlineEl.style.display = 'none';
603
+ return;
604
+ }
605
+ var rect = el.getBoundingClientRect();
606
+ outlineEl.style.display = 'block';
607
+ outlineEl.style.top = rect.top + 'px';
608
+ outlineEl.style.left = rect.left + 'px';
609
+ outlineEl.style.width = rect.width + 'px';
610
+ outlineEl.style.height = rect.height + 'px';
611
+ }
612
+
613
+ function positionToolbar(x, y) {
614
+ var rect = toolbar.getBoundingClientRect();
615
+ var top = Math.max(4, y - rect.height - 12);
616
+ var left = Math.max(4, Math.min(x - rect.width / 2, window.innerWidth - rect.width - 4));
617
+ toolbar.style.top = top + 'px';
618
+ toolbar.style.left = left + 'px';
619
+ }
620
+
621
+ function showToolbar(x, y, blockId) {
622
+ currentBlockId = blockId;
623
+ toolbarVisible = true;
624
+ positionToolbar(x, y);
625
+ toolbar.classList.add('cms-toolbar-visible');
626
+
627
+ var index = getBlockIndex(blockId);
628
+ var total = getAllBlocks().length;
629
+ toolbar.querySelector('.move-up').disabled = index <= 0;
630
+ toolbar.querySelector('.move-down').disabled = index >= total - 1;
631
+ }
632
+
633
+ function hideToolbar() {
634
+ toolbarVisible = false;
635
+ toolbar.classList.remove('cms-toolbar-visible');
636
+ }
637
+
638
+ function showCursor(x, y) {
639
+ cursor.style.top = y + 'px';
640
+ cursor.style.left = x + 'px';
641
+ cursor.classList.add('cms-cursor-visible');
642
+ }
643
+
644
+ function hideCursor() {
645
+ cursor.classList.remove('cms-cursor-visible');
646
+ }
647
+
648
+ document.addEventListener('mouseover', function(e) {
649
+ if (toolbarVisible) return;
650
+ var block = e.target.closest('[data-cms-block]');
651
+ if (block) {
652
+ updateOutline(block, outline);
653
+ }
654
+ });
655
+
656
+ document.addEventListener('mouseout', function(e) {
657
+ var block = e.target.closest('[data-cms-block]');
658
+ var related = e.relatedTarget ? e.relatedTarget.closest('[data-cms-block]') : null;
659
+ if (block && block !== related) {
660
+ outline.style.display = 'none';
661
+ hideCursor();
662
+ }
663
+ });
664
+
665
+ document.addEventListener('mousemove', function(e) {
666
+ if (toolbarVisible) return;
667
+ var block = e.target.closest('[data-cms-block]');
668
+ if (block) {
669
+ showCursor(e.clientX, e.clientY);
670
+ }
671
+ });
672
+
673
+ document.addEventListener('contextmenu', function(e) {
674
+ if (e.target.closest('[data-cms-toolbar]')) return;
675
+ var block = e.target.closest('[data-cms-block]');
676
+ if (block) {
677
+ if (toolbarVisible) return;
678
+ e.preventDefault();
679
+ hideCursor();
680
+ outline.style.display = 'none';
681
+ var blockId = block.getAttribute('data-block-id');
682
+ showToolbar(e.clientX, e.clientY, blockId);
683
+ }
684
+ });
685
+
686
+ document.addEventListener('click', function(e) {
687
+ if (toolbarVisible && !e.target.closest('[data-cms-toolbar]')) {
688
+ var block = e.target.closest('[data-cms-block]');
689
+ if (!block || block.getAttribute('data-block-id') !== currentBlockId) {
690
+ hideToolbar();
691
+ }
692
+ }
693
+
694
+ if (e.target.closest('[data-cms-toolbar]')) return;
695
+
696
+ var editable = e.target.closest('[data-cms-editable]');
697
+ if (editable) {
698
+ postToParent({
699
+ type: 'cms-editable-click',
700
+ blockId: editable.getAttribute('data-block-id'),
701
+ blockType: editable.getAttribute('data-block-type'),
702
+ contentPath: editable.getAttribute('data-content-path')
703
+ });
704
+ return;
705
+ }
706
+
707
+ var block = e.target.closest('[data-cms-block]');
708
+ if (block) {
709
+ postToParent({
710
+ type: 'cms-editable-click',
711
+ blockId: block.getAttribute('data-block-id'),
712
+ blockType: block.getAttribute('data-block-type'),
713
+ contentPath: null
714
+ });
715
+ }
716
+ });
717
+
718
+ toolbar.querySelector('.move-up').addEventListener('click', function() {
719
+ if (!currentBlockId) return;
720
+ postToParent({ type: 'cms-block-move', blockId: currentBlockId, direction: 'up' });
721
+ hideToolbar();
722
+ });
723
+
724
+ toolbar.querySelector('.move-down').addEventListener('click', function() {
725
+ if (!currentBlockId) return;
726
+ postToParent({ type: 'cms-block-move', blockId: currentBlockId, direction: 'down' });
727
+ hideToolbar();
728
+ });
729
+
730
+ toolbar.querySelector('.delete').addEventListener('click', function() {
731
+ if (!currentBlockId) return;
732
+ postToParent({ type: 'cms-block-delete', blockId: currentBlockId });
733
+ hideToolbar();
734
+ });
735
+
736
+ window.addEventListener('scroll', function() {
737
+ hideCursor();
738
+ hideToolbar();
739
+ if (selectedBlockId) {
740
+ var el = getBlockElement(selectedBlockId);
741
+ updateOutline(el, selectedOutline);
742
+ }
743
+ }, { passive: true, capture: true });
744
+
745
+ window.addEventListener('resize', function() {
746
+ if (selectedBlockId) {
747
+ var el = getBlockElement(selectedBlockId);
748
+ updateOutline(el, selectedOutline);
749
+ }
750
+ }, { passive: true });
751
+
752
+ window.addEventListener('message', function(e) {
753
+ if (CMS_PARENT_ORIGIN && e.origin !== CMS_PARENT_ORIGIN) return;
754
+ if (e.source !== window.parent) return;
755
+
756
+ if (e.data && e.data.type === 'cms-select-block') {
757
+ selectedBlockId = e.data.blockId || null;
758
+ if (selectedBlockId) {
759
+ var el = getBlockElement(selectedBlockId);
760
+ updateOutline(el, selectedOutline);
761
+ } else {
762
+ selectedOutline.style.display = 'none';
763
+ }
764
+ }
765
+ });
766
+ })();
767
+ `;
768
+ }
385
769
 
386
770
  // lib/cms-post-message.ts
387
771
  var CMS_PARENT_ORIGIN_PARAM = "cms_parent_origin";
@@ -439,137 +823,19 @@ function getCmsParentTargetOrigin(preferredCmsUrl, explicitParentOrigin) {
439
823
 
440
824
  // lib/block-renderer.tsx
441
825
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
442
- function extractContentValues(content, basePath = []) {
443
- const map = /* @__PURE__ */ new Map();
444
- function walk(obj, path) {
445
- if (typeof obj === "string" && obj.trim() !== "") {
446
- const contentPath = path.join(".");
447
- const existing = map.get(obj) || [];
448
- existing.push({ contentPath, value: obj });
449
- map.set(obj, existing);
450
- } else if (Array.isArray(obj)) {
451
- for (let index = 0; index < obj.length; index++) {
452
- walk(obj[index], [...path, String(index)]);
453
- }
454
- } else if (obj && typeof obj === "object") {
455
- for (const [key, value] of Object.entries(obj)) {
456
- walk(value, [...path, key]);
457
- }
458
- }
459
- }
460
- walk(content, basePath);
461
- return map;
462
- }
463
826
  function CmsEditableInit({
464
827
  cmsUrl,
465
828
  cmsParentOrigin
466
829
  }) {
467
- const cmsParentOriginJson = JSON.stringify(
468
- getCmsParentTargetOrigin(cmsUrl, cmsParentOrigin) ?? ""
469
- );
470
- return /* @__PURE__ */ jsxs(Fragment, { children: [
471
- /* @__PURE__ */ jsx("style", { children: `
472
- [data-cms-editable] {
473
- cursor: pointer;
474
- border-radius: 2px;
475
- }
476
- [data-cms-editable]:hover {
477
- outline: 2px solid #3b82f6;
478
- outline-offset: 2px;
479
- }
480
- .cms-block-toolbar {
481
- position: fixed;
482
- display: flex;
483
- gap: 4px;
484
- background: #1f2937;
485
- border-radius: 6px;
486
- padding: 4px;
487
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
488
- transition: opacity 0.15s ease;
489
- z-index: 99999;
490
- pointer-events: auto;
491
- }
492
- .cms-block-toolbar button {
493
- display: flex;
494
- align-items: center;
495
- justify-content: center;
496
- width: 28px;
497
- height: 28px;
498
- border: none;
499
- background: transparent;
500
- color: #9ca3af;
501
- border-radius: 4px;
502
- cursor: pointer;
503
- transition: background 0.15s ease, color 0.15s ease;
504
- }
505
- .cms-block-toolbar button:hover {
506
- background: #374151;
507
- color: #fff;
508
- }
509
- .cms-block-toolbar button.delete:hover {
510
- background: #dc2626;
511
- color: #fff;
512
- }
513
- .cms-block-toolbar button:disabled {
514
- opacity: 0.4;
515
- cursor: not-allowed;
516
- }
517
- .cms-block-toolbar button:disabled:hover {
518
- background: transparent;
519
- color: #9ca3af;
520
- }
521
- .cms-block-toolbar svg {
522
- width: 16px;
523
- height: 16px;
524
- }
525
- ` }),
526
- /* @__PURE__ */ jsx(
527
- "script",
528
- {
529
- dangerouslySetInnerHTML: {
530
- __html: `
531
- (function() {
532
- var cmsParentOrigin = ${cmsParentOriginJson};
533
- if (!window.__cmsEditableInitialized) {
534
- window.__cmsEditableInitialized = true;
535
-
536
- document.addEventListener('click', function(e) {
537
- if (e.target.closest('.cms-block-toolbar')) return;
538
-
539
- var editableTarget = e.target.closest('[data-cms-editable]');
540
- if (editableTarget) {
541
- var message = {
542
- type: 'cms-editable-click',
543
- blockId: editableTarget.getAttribute('data-block-id'),
544
- blockType: editableTarget.getAttribute('data-block-type'),
545
- contentPath: editableTarget.getAttribute('data-content-path')
546
- };
547
- if (cmsParentOrigin && window.parent && window.parent !== window) {
548
- window.parent.postMessage(message, cmsParentOrigin);
549
- }
550
- return;
551
- }
552
-
553
- var blockTarget = e.target.closest('[data-cms-block]');
554
- if (blockTarget) {
555
- var message = {
556
- type: 'cms-editable-click',
557
- blockId: blockTarget.getAttribute('data-block-id'),
558
- blockType: blockTarget.getAttribute('data-block-type'),
559
- contentPath: null
560
- };
561
- if (cmsParentOrigin && window.parent && window.parent !== window) {
562
- window.parent.postMessage(message, cmsParentOrigin);
563
- }
564
- }
565
- });
566
- }
567
- })();
568
- `
569
- }
830
+ const targetOrigin = getCmsParentTargetOrigin(cmsUrl, cmsParentOrigin) ?? "";
831
+ return /* @__PURE__ */ jsx(
832
+ "script",
833
+ {
834
+ dangerouslySetInnerHTML: {
835
+ __html: generateCmsOverlayScript(targetOrigin)
570
836
  }
571
- )
572
- ] });
837
+ }
838
+ );
573
839
  }
574
840
  function pathMatchesPattern(path, pattern) {
575
841
  const pathSegs = path.split("/").filter(Boolean);
@@ -620,9 +886,19 @@ function BlockRenderer({
620
886
  if (disableEditable) {
621
887
  return component;
622
888
  }
623
- const contentValueMap = extractContentValues(block.content);
624
- const contentEntries = Array.from(contentValueMap.entries()).map(([value, matches]) => ({ v: value, p: matches[0]?.contentPath })).filter((e) => !!e.p);
625
- return /* @__PURE__ */ jsx(ClientEditableBlock, { blockId: block.id, blockType: block.type, contentEntries, children: component });
889
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
890
+ /* @__PURE__ */ jsx(
891
+ "span",
892
+ {
893
+ "data-cms-sentinel": "",
894
+ "data-block-id": block.id,
895
+ "data-block-type": block.type,
896
+ style: { display: "none" },
897
+ "aria-hidden": "true"
898
+ }
899
+ ),
900
+ component
901
+ ] });
626
902
  }
627
903
 
628
904
  // lib/cms-api.ts
@@ -700,11 +976,11 @@ async function renderParametricRoute({
700
976
  websiteId: providedWebsiteId
701
977
  }) {
702
978
  const websiteId = getWebsiteId(providedWebsiteId);
703
- const { slug } = await params;
704
- const resolvedSearchParams = await searchParams;
979
+ const { slug } = "then" in params ? await params : params;
980
+ const resolvedSearchParams = searchParams && "then" in searchParams ? await searchParams : searchParams;
705
981
  let aiPreviewIndex = null;
706
982
  const aiPreviewParam = resolvedSearchParams?.ai_preview;
707
- if (aiPreviewParam) {
983
+ if (aiPreviewParam && typeof aiPreviewParam !== "boolean") {
708
984
  const paramValue = Array.isArray(aiPreviewParam) ? aiPreviewParam[0] : aiPreviewParam;
709
985
  if (paramValue) {
710
986
  const parsed = parseInt(paramValue, 10);
@@ -714,9 +990,9 @@ async function renderParametricRoute({
714
990
  }
715
991
  }
716
992
  const editModeParam = resolvedSearchParams?.edit_mode;
717
- const editMode = editModeParam === "true" || editModeParam === "1";
993
+ const editMode = editModeParam === true || editModeParam === "true" || editModeParam === "1";
718
994
  const cmsParentOriginParam = resolvedSearchParams?.cms_parent_origin;
719
- const cmsParentOrigin = Array.isArray(cmsParentOriginParam) ? cmsParentOriginParam[0] : cmsParentOriginParam;
995
+ const cmsParentOrigin = typeof cmsParentOriginParam === "boolean" ? void 0 : Array.isArray(cmsParentOriginParam) ? cmsParentOriginParam[0] : cmsParentOriginParam;
720
996
  const rawPath = `/${slug.join("/")}`;
721
997
  const path = normalizePath(rawPath);
722
998
  if (/\.[a-zA-Z0-9]+$/.test(path)) {