cms-renderer 0.8.1 → 0.8.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.
@@ -102,6 +102,24 @@ function isArticlePublished(article) {
102
102
 
103
103
  // ../../packages/cms-schema/src/blocks/metadata.ts
104
104
  var BLOCK_METADATA = {
105
+ "nav-block": {
106
+ name: "Navbar",
107
+ description: "Fixed top navigation bar with brand, links, login, and CTA",
108
+ category: "Marketing",
109
+ icon: "bars-3",
110
+ fields: [
111
+ { name: "brandName", label: "Brand Name", type: "Text" },
112
+ { name: "brandUrl", label: "Brand URL", type: "Text" },
113
+ { name: "logo", label: "Logo", type: "Image" },
114
+ { name: "links", label: "Navigation Links", type: "List" },
115
+ { name: "loginLabel", label: "Login Label", type: "Text" },
116
+ { name: "loginUrl", label: "Login URL", type: "Text" },
117
+ { name: "ctaLabel", label: "CTA Label", type: "Text" },
118
+ { name: "ctaUrl", label: "CTA URL", type: "Text" },
119
+ { name: "overlay", label: "Overlay on Page", type: "Boolean" }
120
+ ],
121
+ variants: [{ name: "Default" }]
122
+ },
105
123
  "hero-block": {
106
124
  name: "Hero",
107
125
  description: "Full-width hero section with headline, description, and CTA",
@@ -112,6 +130,10 @@ var BLOCK_METADATA = {
112
130
  { name: "subheadline", label: "Description", type: "Text" },
113
131
  { name: "ctaText", label: "CTA Label", type: "Text" },
114
132
  { name: "ctaUrl", label: "CTA URL", type: "URL" },
133
+ { name: "ctaType", label: "CTA Type", type: "Select" },
134
+ { name: "cta2Text", label: "CTA 2 Label", type: "Text" },
135
+ { name: "cta2Url", label: "CTA 2 URL", type: "URL" },
136
+ { name: "cta2Type", label: "CTA 2 Type", type: "Select" },
115
137
  { name: "backgroundImage", label: "Background Image", type: "Image" }
116
138
  ],
117
139
  variants: [{ name: "Left" }, { name: "Center" }, { name: "Right" }]
@@ -316,35 +338,37 @@ var fileSchema = z5.object({
316
338
  type: z5.string()
317
339
  });
318
340
 
341
+ // ../../packages/cms-schema/src/blocks/schemas/block-url.ts
342
+ var BLOCK_URL_REFINE_MESSAGE = "URL must be http(s)://, /path, #anchor, or empty";
343
+ function isValidBlockUrl(val, allowEmpty = true) {
344
+ if (val === "") return allowEmpty;
345
+ if (val.startsWith("http://") || val.startsWith("https://")) {
346
+ try {
347
+ new URL(val);
348
+ return true;
349
+ } catch {
350
+ return false;
351
+ }
352
+ }
353
+ if (val.startsWith("/") || val.startsWith("#")) return true;
354
+ return false;
355
+ }
356
+
319
357
  // ../../packages/cms-schema/src/blocks/schemas/hero-block.ts
320
358
  var HeroAlignment = ["left", "center", "right"];
321
359
  var HeroBlockContentSchema = z6.object({
322
360
  headline: z6.string().min(1, "Headline is required").max(100, "Headline too long"),
323
361
  subheadline: z6.string().max(200, "Description too long").optional(),
324
362
  ctaText: z6.string().max(50, "CTA text too long").optional(),
325
- ctaUrl: z6.string().refine(
326
- (val) => {
327
- if (val === "") return true;
328
- if (val.startsWith("http://") || val.startsWith("https://")) {
329
- try {
330
- new URL(val);
331
- return true;
332
- } catch {
333
- return false;
334
- }
335
- }
336
- if (val.startsWith("/")) {
337
- return true;
338
- }
339
- if (val.startsWith("#")) {
340
- return true;
341
- }
342
- return false;
343
- },
344
- {
345
- message: "URL must be a valid full URL (http://... or https://...), relative path (/path), anchor (#anchor), or empty string"
346
- }
347
- ).optional().or(z6.literal("")),
363
+ ctaUrl: z6.string().refine(isValidBlockUrl, {
364
+ message: BLOCK_URL_REFINE_MESSAGE
365
+ }).optional().or(z6.literal("")),
366
+ ctaType: z6.enum(["Primary", "Secondary", "Ghost"]).default("Primary").optional(),
367
+ cta2Text: z6.string().max(50, "CTA 2 text too long").optional(),
368
+ cta2Url: z6.string().refine(isValidBlockUrl, {
369
+ message: BLOCK_URL_REFINE_MESSAGE
370
+ }).optional().or(z6.literal("")),
371
+ cta2Type: z6.enum(["Primary", "Secondary", "Ghost"]).default("Secondary").optional(),
348
372
  backgroundImage: ImageReferenceSchema.nullable().optional(),
349
373
  alignment: z6.enum(HeroAlignment).default("center")
350
374
  });
@@ -374,9 +398,39 @@ var LogoTrustBlockContentSchema = z7.object({
374
398
  });
375
399
  var LOGO_TRUST_BLOCK_SCHEMA_NAME = "logo-trust-block";
376
400
 
401
+ // ../../packages/cms-schema/src/blocks/schemas/nav-block.ts
402
+ import { z as z8 } from "zod";
403
+ var NavLinkSchema = z8.object({
404
+ label: z8.string().min(1, "Link label is required").max(40, "Label too long"),
405
+ url: z8.string().refine(isValidBlockUrl, {
406
+ message: BLOCK_URL_REFINE_MESSAGE
407
+ }),
408
+ hasDropdown: z8.boolean().default(false)
409
+ });
410
+ var NavBlockContentSchema = z8.object({
411
+ brandName: z8.string().min(1, "Brand name is required").max(40, "Brand name too long"),
412
+ brandUrl: z8.string().refine(isValidBlockUrl, {
413
+ message: BLOCK_URL_REFINE_MESSAGE
414
+ }),
415
+ logo: ImageReferenceSchema.nullable().optional(),
416
+ links: z8.array(NavLinkSchema).max(12, "Maximum 12 links"),
417
+ loginLabel: z8.string().max(30, "Label too long").optional(),
418
+ loginUrl: z8.string().refine(isValidBlockUrl, {
419
+ message: BLOCK_URL_REFINE_MESSAGE
420
+ }).optional(),
421
+ ctaLabel: z8.string().max(40, "Label too long").optional(),
422
+ ctaUrl: z8.string().refine(isValidBlockUrl, {
423
+ message: BLOCK_URL_REFINE_MESSAGE
424
+ }).optional(),
425
+ /** When true, nav is position:fixed and overlays page content. */
426
+ overlay: z8.boolean().default(true)
427
+ });
428
+ var NAV_BLOCK_SCHEMA_NAME = "nav-block";
429
+
377
430
  // ../../packages/cms-schema/src/blocks/registry.ts
378
431
  var BLOCK_SCHEMA_NAMES = [
379
432
  ARTICLE_BLOCK_SCHEMA_NAME,
433
+ NAV_BLOCK_SCHEMA_NAME,
380
434
  HERO_BLOCK_SCHEMA_NAME,
381
435
  FEATURES_BLOCK_SCHEMA_NAME,
382
436
  CTA_BLOCK_SCHEMA_NAME,
@@ -416,7 +470,7 @@ function normalizePath(path) {
416
470
  }
417
471
 
418
472
  // ../../packages/cms-schema/src/routing/path-validation.ts
419
- import { z as z8 } from "zod";
473
+ import { z as z9 } from "zod";
420
474
  var pathRegex = /^\/[\p{L}\p{N}\-_{}/:]*$/u;
421
475
  var placeholderSegmentRegex = /^(:[a-zA-Z0-9_]+|\{[a-zA-Z0-9_]+\})$/;
422
476
  function hasValidPlaceholderStructure(path) {
@@ -432,7 +486,7 @@ function hasValidPlaceholderStructure(path) {
432
486
  return placeholderSegmentRegex.test(segment);
433
487
  });
434
488
  }
435
- var pathSchema = z8.string().min(1, "Path is required").regex(
489
+ var pathSchema = z9.string().min(1, "Path is required").regex(
436
490
  pathRegex,
437
491
  "Path must start with / and contain only letters, numbers, hyphens, underscores, slashes, colons, and braces"
438
492
  ).refine(
@@ -441,14 +495,14 @@ var pathSchema = z8.string().min(1, "Path is required").regex(
441
495
  );
442
496
 
443
497
  // ../../packages/cms-schema/src/documents/schemas/language.ts
444
- import { z as z9 } from "zod";
445
- var LanguageSchema = z9.object({
498
+ import { z as z10 } from "zod";
499
+ var LanguageSchema = z10.object({
446
500
  /** 2-letter ISO 639-1 language code */
447
- code: z9.string().length(2, "Language code must be 2 characters"),
501
+ code: z10.string().length(2, "Language code must be 2 characters"),
448
502
  /** English name of the language */
449
- name: z9.string().min(1, "Language name required"),
503
+ name: z10.string().min(1, "Language name required"),
450
504
  /** Name in the language itself (optional but recommended) */
451
- nativeName: z9.string().optional()
505
+ nativeName: z10.string().optional()
452
506
  });
453
507
 
454
508
  // lib/block-renderer.tsx
@@ -466,14 +520,14 @@ function generateCmsOverlayScript(cmsParentOrigin) {
466
520
  var style = document.createElement('style');
467
521
  style.setAttribute('data-cms-overlay', '');
468
522
  style.textContent = \`
469
- [data-cms-block],
523
+ [data-cms-sentinel] + *,
470
524
  [data-cms-editable] {
471
525
  cursor: pointer;
472
526
  }
473
527
  [data-cms-editable] {
474
528
  border-radius: 2px;
475
529
  }
476
- [data-cms-editable]:not([data-cms-block]):hover {
530
+ [data-cms-editable]:hover {
477
531
  outline: 2px solid var(--component-accent, #A78BFA);
478
532
  outline-offset: 2px;
479
533
  }
@@ -594,28 +648,6 @@ function generateCmsOverlayScript(cmsParentOrigin) {
594
648
  \`;
595
649
  document.head.appendChild(style);
596
650
 
597
- function stampBlockElements() {
598
- var sentinels = document.querySelectorAll('[data-cms-sentinel]');
599
- sentinels.forEach(function(sentinel) {
600
- var blockEl = sentinel.nextElementSibling;
601
- if (!blockEl) return;
602
-
603
- var blockId = sentinel.getAttribute('data-block-id');
604
- var blockType = sentinel.getAttribute('data-block-type');
605
-
606
- blockEl.setAttribute('data-cms-block', '');
607
- blockEl.setAttribute('data-block-id', blockId);
608
- blockEl.setAttribute('data-block-type', blockType);
609
-
610
- injectEditableSpans(
611
- blockEl,
612
- blockId,
613
- blockType,
614
- sentinel.getAttribute('data-content-entries')
615
- );
616
- });
617
- }
618
-
619
651
  function injectEditableSpans(blockEl, blockId, blockType, rawEntries) {
620
652
  if (!rawEntries || blockEl.querySelector('[data-cms-editable][data-content-path]')) return;
621
653
 
@@ -669,15 +701,55 @@ function generateCmsOverlayScript(cmsParentOrigin) {
669
701
  }
670
702
  }
671
703
 
672
- stampBlockElements();
673
-
674
- var stampObserver = new MutationObserver(function(mutations) {
675
- var needsStamp = mutations.some(function(m) {
676
- return m.addedNodes.length > 0;
704
+ function injectAllEditableSpans() {
705
+ document.querySelectorAll('[data-cms-sentinel]').forEach(function(sentinel) {
706
+ var blockEl = sentinel.nextElementSibling;
707
+ if (!blockEl) return;
708
+ injectEditableSpans(
709
+ blockEl,
710
+ sentinel.getAttribute('data-block-id'),
711
+ sentinel.getAttribute('data-block-type'),
712
+ sentinel.getAttribute('data-content-entries')
713
+ );
677
714
  });
678
- if (needsStamp) stampBlockElements();
679
- });
680
- stampObserver.observe(document.body, { childList: true, subtree: true });
715
+ }
716
+
717
+ function getBlockRootFromSentinel(sentinel) {
718
+ return sentinel ? sentinel.nextElementSibling : null;
719
+ }
720
+
721
+ function getSentinelForBlock(blockEl) {
722
+ var prev = blockEl ? blockEl.previousElementSibling : null;
723
+ if (prev && prev.hasAttribute('data-cms-sentinel')) return prev;
724
+ return null;
725
+ }
726
+
727
+ function getBlockId(blockEl) {
728
+ var sentinel = getSentinelForBlock(blockEl);
729
+ return sentinel ? sentinel.getAttribute('data-block-id') : null;
730
+ }
731
+
732
+ function getBlockType(blockEl) {
733
+ var sentinel = getSentinelForBlock(blockEl);
734
+ return sentinel ? sentinel.getAttribute('data-block-type') : null;
735
+ }
736
+
737
+ function resolveBlockFromTarget(target) {
738
+ if (!target || !target.closest) return null;
739
+
740
+ var sentinels = document.querySelectorAll('[data-cms-sentinel]');
741
+ for (var i = 0; i < sentinels.length; i++) {
742
+ var root = getBlockRootFromSentinel(sentinels[i]);
743
+ if (root && root.contains(target)) return root;
744
+ }
745
+ return null;
746
+ }
747
+
748
+ var overlayUiReady = false;
749
+
750
+ function initOverlayUi() {
751
+ if (overlayUiReady) return;
752
+ overlayUiReady = true;
681
753
 
682
754
  var overlayRoot = document.createElement('div');
683
755
  overlayRoot.id = 'cms-overlay-root';
@@ -759,21 +831,23 @@ function generateCmsOverlayScript(cmsParentOrigin) {
759
831
  setTimeout(sendReadySignal, 1500);
760
832
 
761
833
  function getBlockElement(blockId) {
762
- // Match the real block element, not the hidden sentinel <span> that shares
763
- // the same data-block-id and precedes it in the DOM. The sentinel is
764
- // display:none (a 0x0 rect), so selecting it would paint the outline at the
765
- // top-left corner. Only the stamped block carries data-cms-block.
766
- return document.querySelector('[data-cms-block][data-block-id="' + blockId + '"]');
834
+ // Prefer stamped block roots when present; otherwise resolve via sentinel sibling.
835
+ var stamped = document.querySelector('[data-cms-block][data-block-id="' + blockId + '"]');
836
+ if (stamped) return stamped;
837
+ var sentinel = document.querySelector('[data-cms-sentinel][data-block-id="' + blockId + '"]');
838
+ return getBlockRootFromSentinel(sentinel);
767
839
  }
768
-
840
+
769
841
  function getAllBlocks() {
770
- return Array.from(document.querySelectorAll('[data-cms-block]'));
842
+ return Array.from(document.querySelectorAll('[data-cms-sentinel]'))
843
+ .map(getBlockRootFromSentinel)
844
+ .filter(Boolean);
771
845
  }
772
846
 
773
847
  function getBlockIndex(blockId) {
774
848
  var blocks = getAllBlocks();
775
849
  for (var i = 0; i < blocks.length; i++) {
776
- if (blocks[i].getAttribute('data-block-id') === blockId) return i;
850
+ if (getBlockId(blocks[i]) === blockId) return i;
777
851
  }
778
852
  return -1;
779
853
  }
@@ -807,7 +881,7 @@ function generateCmsOverlayScript(cmsParentOrigin) {
807
881
  outlineEl.style.width = rect.width + 'px';
808
882
  outlineEl.style.height = rect.height + 'px';
809
883
  if (label) {
810
- label.textContent = formatBlockType(el.getAttribute('data-block-type'));
884
+ label.textContent = formatBlockType(getBlockType(el));
811
885
  label.classList.add('cms-label-visible');
812
886
  }
813
887
  }
@@ -849,15 +923,15 @@ function generateCmsOverlayScript(cmsParentOrigin) {
849
923
 
850
924
  document.addEventListener('mouseover', function(e) {
851
925
  if (toolbarVisible) return;
852
- var block = e.target.closest('[data-cms-block]');
926
+ var block = resolveBlockFromTarget(e.target);
853
927
  if (block) {
854
928
  updateOutline(block, outline);
855
929
  }
856
930
  });
857
931
 
858
932
  document.addEventListener('mouseout', function(e) {
859
- var block = e.target.closest('[data-cms-block]');
860
- var related = e.relatedTarget ? e.relatedTarget.closest('[data-cms-block]') : null;
933
+ var block = resolveBlockFromTarget(e.target);
934
+ var related = e.relatedTarget ? resolveBlockFromTarget(e.relatedTarget) : null;
861
935
  if (block && block !== related) {
862
936
  outline.style.display = 'none';
863
937
  hideCursor();
@@ -866,7 +940,7 @@ function generateCmsOverlayScript(cmsParentOrigin) {
866
940
 
867
941
  document.addEventListener('mousemove', function(e) {
868
942
  if (toolbarVisible) return;
869
- var block = e.target.closest('[data-cms-block]');
943
+ var block = resolveBlockFromTarget(e.target);
870
944
  if (block) {
871
945
  showCursor(e.clientX, e.clientY);
872
946
  }
@@ -874,13 +948,13 @@ function generateCmsOverlayScript(cmsParentOrigin) {
874
948
 
875
949
  document.addEventListener('contextmenu', function(e) {
876
950
  if (e.target.closest('[data-cms-toolbar]')) return;
877
- var block = e.target.closest('[data-cms-block]');
951
+ var block = resolveBlockFromTarget(e.target);
878
952
  if (block) {
879
953
  if (toolbarVisible) return;
880
954
  e.preventDefault();
881
955
  hideCursor();
882
956
  outline.style.display = 'none';
883
- var blockId = block.getAttribute('data-block-id');
957
+ var blockId = getBlockId(block);
884
958
  showToolbar(e.clientX, e.clientY, blockId);
885
959
  }
886
960
  });
@@ -893,8 +967,8 @@ function generateCmsOverlayScript(cmsParentOrigin) {
893
967
  blockInteractiveActivation(e);
894
968
 
895
969
  if (toolbarVisible) {
896
- var activeBlock = e.target.closest('[data-cms-block]');
897
- if (!activeBlock || activeBlock.getAttribute('data-block-id') !== currentBlockId) {
970
+ var activeBlock = resolveBlockFromTarget(e.target);
971
+ if (!activeBlock || getBlockId(activeBlock) !== currentBlockId) {
898
972
  hideToolbar();
899
973
  }
900
974
  }
@@ -910,12 +984,12 @@ function generateCmsOverlayScript(cmsParentOrigin) {
910
984
  return;
911
985
  }
912
986
 
913
- var block = e.target.closest('[data-cms-block]');
987
+ var block = resolveBlockFromTarget(e.target);
914
988
  if (block) {
915
989
  postToParent({
916
990
  type: 'cms-editable-click',
917
- blockId: block.getAttribute('data-block-id'),
918
- blockType: block.getAttribute('data-block-type'),
991
+ blockId: getBlockId(block),
992
+ blockType: getBlockType(block),
919
993
  contentPath: null
920
994
  });
921
995
  }
@@ -969,6 +1043,29 @@ function generateCmsOverlayScript(cmsParentOrigin) {
969
1043
  }
970
1044
  }
971
1045
  });
1046
+ }
1047
+
1048
+ function scheduleEditableInjection() {
1049
+ requestAnimationFrame(function() {
1050
+ requestAnimationFrame(injectAllEditableSpans);
1051
+ });
1052
+ }
1053
+
1054
+ var stampObserver = new MutationObserver(function(mutations) {
1055
+ var needsInject = mutations.some(function(m) {
1056
+ return m.addedNodes.length > 0;
1057
+ });
1058
+ if (needsInject) scheduleEditableInjection();
1059
+ });
1060
+ stampObserver.observe(document.body, { childList: true, subtree: true });
1061
+
1062
+ initOverlayUi();
1063
+
1064
+ if (document.readyState === 'complete') {
1065
+ scheduleEditableInjection();
1066
+ } else {
1067
+ window.addEventListener('load', scheduleEditableInjection, { once: true });
1068
+ }
972
1069
  })();
973
1070
  `;
974
1071
  }
@@ -1185,7 +1282,8 @@ async function renderParametricRoute({
1185
1282
  try {
1186
1283
  const { route, resolvedParams } = await client.route.getByPath.query({
1187
1284
  websiteId,
1188
- path
1285
+ path,
1286
+ preview: previewMode
1189
1287
  });
1190
1288
  if (route.state !== "Live") {
1191
1289
  return { status: "not_found" };
@@ -1216,7 +1314,7 @@ async function renderParametricRoute({
1216
1314
  const blocks = [];
1217
1315
  for (const block of blockResults) {
1218
1316
  if (!block) continue;
1219
- const blockContent = previewMode ? block.draft_content ?? block.published_content : block.published_content;
1317
+ const blockContent = previewMode ? block.draft_content : block.published_content;
1220
1318
  if (blockContent == null) continue;
1221
1319
  let content = null;
1222
1320
  if (aiPreviewIndex !== null) {
@@ -1251,19 +1349,22 @@ async function renderParametricRoute({
1251
1349
  });
1252
1350
  }
1253
1351
  const routeParams = resolvedParams ? Object.fromEntries(
1254
- Object.entries(resolvedParams).map(([key, param]) => [
1255
- key,
1256
- {
1257
- value: param.value,
1258
- schemaName: param.schemaName,
1259
- document: {
1260
- id: param.document.id,
1261
- title: param.document.title,
1262
- content: param.document.content,
1263
- schema_name: param.document.schema_name
1352
+ Object.entries(resolvedParams).map(([key, param]) => {
1353
+ const documentContent = previewMode ? param.document.draft_content : param.document.published_content;
1354
+ return [
1355
+ key,
1356
+ {
1357
+ value: param.value,
1358
+ schemaName: param.schemaName,
1359
+ document: {
1360
+ id: param.document.id,
1361
+ title: param.document.title,
1362
+ content: documentContent ?? param.document.draft_content,
1363
+ schema_name: param.document.schema_name
1364
+ }
1264
1365
  }
1265
- }
1266
- ])
1366
+ ];
1367
+ })
1267
1368
  ) : void 0;
1268
1369
  return {
1269
1370
  status: "ok",