cms-renderer 0.8.0 → 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.
@@ -30,6 +30,24 @@ function isArticlePublished(article) {
30
30
 
31
31
  // ../../packages/cms-schema/src/blocks/metadata.ts
32
32
  var BLOCK_METADATA = {
33
+ "nav-block": {
34
+ name: "Navbar",
35
+ description: "Fixed top navigation bar with brand, links, login, and CTA",
36
+ category: "Marketing",
37
+ icon: "bars-3",
38
+ fields: [
39
+ { name: "brandName", label: "Brand Name", type: "Text" },
40
+ { name: "brandUrl", label: "Brand URL", type: "Text" },
41
+ { name: "logo", label: "Logo", type: "Image" },
42
+ { name: "links", label: "Navigation Links", type: "List" },
43
+ { name: "loginLabel", label: "Login Label", type: "Text" },
44
+ { name: "loginUrl", label: "Login URL", type: "Text" },
45
+ { name: "ctaLabel", label: "CTA Label", type: "Text" },
46
+ { name: "ctaUrl", label: "CTA URL", type: "Text" },
47
+ { name: "overlay", label: "Overlay on Page", type: "Boolean" }
48
+ ],
49
+ variants: [{ name: "Default" }]
50
+ },
33
51
  "hero-block": {
34
52
  name: "Hero",
35
53
  description: "Full-width hero section with headline, description, and CTA",
@@ -40,6 +58,10 @@ var BLOCK_METADATA = {
40
58
  { name: "subheadline", label: "Description", type: "Text" },
41
59
  { name: "ctaText", label: "CTA Label", type: "Text" },
42
60
  { name: "ctaUrl", label: "CTA URL", type: "URL" },
61
+ { name: "ctaType", label: "CTA Type", type: "Select" },
62
+ { name: "cta2Text", label: "CTA 2 Label", type: "Text" },
63
+ { name: "cta2Url", label: "CTA 2 URL", type: "URL" },
64
+ { name: "cta2Type", label: "CTA 2 Type", type: "Select" },
43
65
  { name: "backgroundImage", label: "Background Image", type: "Image" }
44
66
  ],
45
67
  variants: [{ name: "Left" }, { name: "Center" }, { name: "Right" }]
@@ -244,35 +266,37 @@ var fileSchema = z5.object({
244
266
  type: z5.string()
245
267
  });
246
268
 
269
+ // ../../packages/cms-schema/src/blocks/schemas/block-url.ts
270
+ var BLOCK_URL_REFINE_MESSAGE = "URL must be http(s)://, /path, #anchor, or empty";
271
+ function isValidBlockUrl(val, allowEmpty = true) {
272
+ if (val === "") return allowEmpty;
273
+ if (val.startsWith("http://") || val.startsWith("https://")) {
274
+ try {
275
+ new URL(val);
276
+ return true;
277
+ } catch {
278
+ return false;
279
+ }
280
+ }
281
+ if (val.startsWith("/") || val.startsWith("#")) return true;
282
+ return false;
283
+ }
284
+
247
285
  // ../../packages/cms-schema/src/blocks/schemas/hero-block.ts
248
286
  var HeroAlignment = ["left", "center", "right"];
249
287
  var HeroBlockContentSchema = z6.object({
250
288
  headline: z6.string().min(1, "Headline is required").max(100, "Headline too long"),
251
289
  subheadline: z6.string().max(200, "Description too long").optional(),
252
290
  ctaText: z6.string().max(50, "CTA text too long").optional(),
253
- ctaUrl: z6.string().refine(
254
- (val) => {
255
- if (val === "") return true;
256
- if (val.startsWith("http://") || val.startsWith("https://")) {
257
- try {
258
- new URL(val);
259
- return true;
260
- } catch {
261
- return false;
262
- }
263
- }
264
- if (val.startsWith("/")) {
265
- return true;
266
- }
267
- if (val.startsWith("#")) {
268
- return true;
269
- }
270
- return false;
271
- },
272
- {
273
- message: "URL must be a valid full URL (http://... or https://...), relative path (/path), anchor (#anchor), or empty string"
274
- }
275
- ).optional().or(z6.literal("")),
291
+ ctaUrl: z6.string().refine(isValidBlockUrl, {
292
+ message: BLOCK_URL_REFINE_MESSAGE
293
+ }).optional().or(z6.literal("")),
294
+ ctaType: z6.enum(["Primary", "Secondary", "Ghost"]).default("Primary").optional(),
295
+ cta2Text: z6.string().max(50, "CTA 2 text too long").optional(),
296
+ cta2Url: z6.string().refine(isValidBlockUrl, {
297
+ message: BLOCK_URL_REFINE_MESSAGE
298
+ }).optional().or(z6.literal("")),
299
+ cta2Type: z6.enum(["Primary", "Secondary", "Ghost"]).default("Secondary").optional(),
276
300
  backgroundImage: ImageReferenceSchema.nullable().optional(),
277
301
  alignment: z6.enum(HeroAlignment).default("center")
278
302
  });
@@ -302,9 +326,39 @@ var LogoTrustBlockContentSchema = z7.object({
302
326
  });
303
327
  var LOGO_TRUST_BLOCK_SCHEMA_NAME = "logo-trust-block";
304
328
 
329
+ // ../../packages/cms-schema/src/blocks/schemas/nav-block.ts
330
+ import { z as z8 } from "zod";
331
+ var NavLinkSchema = z8.object({
332
+ label: z8.string().min(1, "Link label is required").max(40, "Label too long"),
333
+ url: z8.string().refine(isValidBlockUrl, {
334
+ message: BLOCK_URL_REFINE_MESSAGE
335
+ }),
336
+ hasDropdown: z8.boolean().default(false)
337
+ });
338
+ var NavBlockContentSchema = z8.object({
339
+ brandName: z8.string().min(1, "Brand name is required").max(40, "Brand name too long"),
340
+ brandUrl: z8.string().refine(isValidBlockUrl, {
341
+ message: BLOCK_URL_REFINE_MESSAGE
342
+ }),
343
+ logo: ImageReferenceSchema.nullable().optional(),
344
+ links: z8.array(NavLinkSchema).max(12, "Maximum 12 links"),
345
+ loginLabel: z8.string().max(30, "Label too long").optional(),
346
+ loginUrl: z8.string().refine(isValidBlockUrl, {
347
+ message: BLOCK_URL_REFINE_MESSAGE
348
+ }).optional(),
349
+ ctaLabel: z8.string().max(40, "Label too long").optional(),
350
+ ctaUrl: z8.string().refine(isValidBlockUrl, {
351
+ message: BLOCK_URL_REFINE_MESSAGE
352
+ }).optional(),
353
+ /** When true, nav is position:fixed and overlays page content. */
354
+ overlay: z8.boolean().default(true)
355
+ });
356
+ var NAV_BLOCK_SCHEMA_NAME = "nav-block";
357
+
305
358
  // ../../packages/cms-schema/src/blocks/registry.ts
306
359
  var BLOCK_SCHEMA_NAMES = [
307
360
  ARTICLE_BLOCK_SCHEMA_NAME,
361
+ NAV_BLOCK_SCHEMA_NAME,
308
362
  HERO_BLOCK_SCHEMA_NAME,
309
363
  FEATURES_BLOCK_SCHEMA_NAME,
310
364
  CTA_BLOCK_SCHEMA_NAME,
@@ -344,7 +398,7 @@ function normalizePath(path) {
344
398
  }
345
399
 
346
400
  // ../../packages/cms-schema/src/routing/path-validation.ts
347
- import { z as z8 } from "zod";
401
+ import { z as z9 } from "zod";
348
402
  var pathRegex = /^\/[\p{L}\p{N}\-_{}/:]*$/u;
349
403
  var placeholderSegmentRegex = /^(:[a-zA-Z0-9_]+|\{[a-zA-Z0-9_]+\})$/;
350
404
  function hasValidPlaceholderStructure(path) {
@@ -360,7 +414,7 @@ function hasValidPlaceholderStructure(path) {
360
414
  return placeholderSegmentRegex.test(segment);
361
415
  });
362
416
  }
363
- var pathSchema = z8.string().min(1, "Path is required").regex(
417
+ var pathSchema = z9.string().min(1, "Path is required").regex(
364
418
  pathRegex,
365
419
  "Path must start with / and contain only letters, numbers, hyphens, underscores, slashes, colons, and braces"
366
420
  ).refine(
@@ -369,14 +423,14 @@ var pathSchema = z8.string().min(1, "Path is required").regex(
369
423
  );
370
424
 
371
425
  // ../../packages/cms-schema/src/documents/schemas/language.ts
372
- import { z as z9 } from "zod";
373
- var LanguageSchema = z9.object({
426
+ import { z as z10 } from "zod";
427
+ var LanguageSchema = z10.object({
374
428
  /** 2-letter ISO 639-1 language code */
375
- code: z9.string().length(2, "Language code must be 2 characters"),
429
+ code: z10.string().length(2, "Language code must be 2 characters"),
376
430
  /** English name of the language */
377
- name: z9.string().min(1, "Language name required"),
431
+ name: z10.string().min(1, "Language name required"),
378
432
  /** Name in the language itself (optional but recommended) */
379
- nativeName: z9.string().optional()
433
+ nativeName: z10.string().optional()
380
434
  });
381
435
 
382
436
  // lib/block-renderer.tsx
@@ -394,14 +448,14 @@ function generateCmsOverlayScript(cmsParentOrigin) {
394
448
  var style = document.createElement('style');
395
449
  style.setAttribute('data-cms-overlay', '');
396
450
  style.textContent = \`
397
- [data-cms-block],
451
+ [data-cms-sentinel] + *,
398
452
  [data-cms-editable] {
399
453
  cursor: pointer;
400
454
  }
401
455
  [data-cms-editable] {
402
456
  border-radius: 2px;
403
457
  }
404
- [data-cms-editable]:not([data-cms-block]):hover {
458
+ [data-cms-editable]:hover {
405
459
  outline: 2px solid var(--component-accent, #A78BFA);
406
460
  outline-offset: 2px;
407
461
  }
@@ -522,28 +576,6 @@ function generateCmsOverlayScript(cmsParentOrigin) {
522
576
  \`;
523
577
  document.head.appendChild(style);
524
578
 
525
- function stampBlockElements() {
526
- var sentinels = document.querySelectorAll('[data-cms-sentinel]');
527
- sentinels.forEach(function(sentinel) {
528
- var blockEl = sentinel.nextElementSibling;
529
- if (!blockEl) return;
530
-
531
- var blockId = sentinel.getAttribute('data-block-id');
532
- var blockType = sentinel.getAttribute('data-block-type');
533
-
534
- blockEl.setAttribute('data-cms-block', '');
535
- blockEl.setAttribute('data-block-id', blockId);
536
- blockEl.setAttribute('data-block-type', blockType);
537
-
538
- injectEditableSpans(
539
- blockEl,
540
- blockId,
541
- blockType,
542
- sentinel.getAttribute('data-content-entries')
543
- );
544
- });
545
- }
546
-
547
579
  function injectEditableSpans(blockEl, blockId, blockType, rawEntries) {
548
580
  if (!rawEntries || blockEl.querySelector('[data-cms-editable][data-content-path]')) return;
549
581
 
@@ -597,15 +629,55 @@ function generateCmsOverlayScript(cmsParentOrigin) {
597
629
  }
598
630
  }
599
631
 
600
- stampBlockElements();
601
-
602
- var stampObserver = new MutationObserver(function(mutations) {
603
- var needsStamp = mutations.some(function(m) {
604
- return m.addedNodes.length > 0;
632
+ function injectAllEditableSpans() {
633
+ document.querySelectorAll('[data-cms-sentinel]').forEach(function(sentinel) {
634
+ var blockEl = sentinel.nextElementSibling;
635
+ if (!blockEl) return;
636
+ injectEditableSpans(
637
+ blockEl,
638
+ sentinel.getAttribute('data-block-id'),
639
+ sentinel.getAttribute('data-block-type'),
640
+ sentinel.getAttribute('data-content-entries')
641
+ );
605
642
  });
606
- if (needsStamp) stampBlockElements();
607
- });
608
- stampObserver.observe(document.body, { childList: true, subtree: true });
643
+ }
644
+
645
+ function getBlockRootFromSentinel(sentinel) {
646
+ return sentinel ? sentinel.nextElementSibling : null;
647
+ }
648
+
649
+ function getSentinelForBlock(blockEl) {
650
+ var prev = blockEl ? blockEl.previousElementSibling : null;
651
+ if (prev && prev.hasAttribute('data-cms-sentinel')) return prev;
652
+ return null;
653
+ }
654
+
655
+ function getBlockId(blockEl) {
656
+ var sentinel = getSentinelForBlock(blockEl);
657
+ return sentinel ? sentinel.getAttribute('data-block-id') : null;
658
+ }
659
+
660
+ function getBlockType(blockEl) {
661
+ var sentinel = getSentinelForBlock(blockEl);
662
+ return sentinel ? sentinel.getAttribute('data-block-type') : null;
663
+ }
664
+
665
+ function resolveBlockFromTarget(target) {
666
+ if (!target || !target.closest) return null;
667
+
668
+ var sentinels = document.querySelectorAll('[data-cms-sentinel]');
669
+ for (var i = 0; i < sentinels.length; i++) {
670
+ var root = getBlockRootFromSentinel(sentinels[i]);
671
+ if (root && root.contains(target)) return root;
672
+ }
673
+ return null;
674
+ }
675
+
676
+ var overlayUiReady = false;
677
+
678
+ function initOverlayUi() {
679
+ if (overlayUiReady) return;
680
+ overlayUiReady = true;
609
681
 
610
682
  var overlayRoot = document.createElement('div');
611
683
  overlayRoot.id = 'cms-overlay-root';
@@ -687,21 +759,23 @@ function generateCmsOverlayScript(cmsParentOrigin) {
687
759
  setTimeout(sendReadySignal, 1500);
688
760
 
689
761
  function getBlockElement(blockId) {
690
- // Match the real block element, not the hidden sentinel <span> that shares
691
- // the same data-block-id and precedes it in the DOM. The sentinel is
692
- // display:none (a 0x0 rect), so selecting it would paint the outline at the
693
- // top-left corner. Only the stamped block carries data-cms-block.
694
- return document.querySelector('[data-cms-block][data-block-id="' + blockId + '"]');
762
+ // Prefer stamped block roots when present; otherwise resolve via sentinel sibling.
763
+ var stamped = document.querySelector('[data-cms-block][data-block-id="' + blockId + '"]');
764
+ if (stamped) return stamped;
765
+ var sentinel = document.querySelector('[data-cms-sentinel][data-block-id="' + blockId + '"]');
766
+ return getBlockRootFromSentinel(sentinel);
695
767
  }
696
-
768
+
697
769
  function getAllBlocks() {
698
- return Array.from(document.querySelectorAll('[data-cms-block]'));
770
+ return Array.from(document.querySelectorAll('[data-cms-sentinel]'))
771
+ .map(getBlockRootFromSentinel)
772
+ .filter(Boolean);
699
773
  }
700
774
 
701
775
  function getBlockIndex(blockId) {
702
776
  var blocks = getAllBlocks();
703
777
  for (var i = 0; i < blocks.length; i++) {
704
- if (blocks[i].getAttribute('data-block-id') === blockId) return i;
778
+ if (getBlockId(blocks[i]) === blockId) return i;
705
779
  }
706
780
  return -1;
707
781
  }
@@ -735,7 +809,7 @@ function generateCmsOverlayScript(cmsParentOrigin) {
735
809
  outlineEl.style.width = rect.width + 'px';
736
810
  outlineEl.style.height = rect.height + 'px';
737
811
  if (label) {
738
- label.textContent = formatBlockType(el.getAttribute('data-block-type'));
812
+ label.textContent = formatBlockType(getBlockType(el));
739
813
  label.classList.add('cms-label-visible');
740
814
  }
741
815
  }
@@ -777,15 +851,15 @@ function generateCmsOverlayScript(cmsParentOrigin) {
777
851
 
778
852
  document.addEventListener('mouseover', function(e) {
779
853
  if (toolbarVisible) return;
780
- var block = e.target.closest('[data-cms-block]');
854
+ var block = resolveBlockFromTarget(e.target);
781
855
  if (block) {
782
856
  updateOutline(block, outline);
783
857
  }
784
858
  });
785
859
 
786
860
  document.addEventListener('mouseout', function(e) {
787
- var block = e.target.closest('[data-cms-block]');
788
- var related = e.relatedTarget ? e.relatedTarget.closest('[data-cms-block]') : null;
861
+ var block = resolveBlockFromTarget(e.target);
862
+ var related = e.relatedTarget ? resolveBlockFromTarget(e.relatedTarget) : null;
789
863
  if (block && block !== related) {
790
864
  outline.style.display = 'none';
791
865
  hideCursor();
@@ -794,7 +868,7 @@ function generateCmsOverlayScript(cmsParentOrigin) {
794
868
 
795
869
  document.addEventListener('mousemove', function(e) {
796
870
  if (toolbarVisible) return;
797
- var block = e.target.closest('[data-cms-block]');
871
+ var block = resolveBlockFromTarget(e.target);
798
872
  if (block) {
799
873
  showCursor(e.clientX, e.clientY);
800
874
  }
@@ -802,13 +876,13 @@ function generateCmsOverlayScript(cmsParentOrigin) {
802
876
 
803
877
  document.addEventListener('contextmenu', function(e) {
804
878
  if (e.target.closest('[data-cms-toolbar]')) return;
805
- var block = e.target.closest('[data-cms-block]');
879
+ var block = resolveBlockFromTarget(e.target);
806
880
  if (block) {
807
881
  if (toolbarVisible) return;
808
882
  e.preventDefault();
809
883
  hideCursor();
810
884
  outline.style.display = 'none';
811
- var blockId = block.getAttribute('data-block-id');
885
+ var blockId = getBlockId(block);
812
886
  showToolbar(e.clientX, e.clientY, blockId);
813
887
  }
814
888
  });
@@ -821,8 +895,8 @@ function generateCmsOverlayScript(cmsParentOrigin) {
821
895
  blockInteractiveActivation(e);
822
896
 
823
897
  if (toolbarVisible) {
824
- var activeBlock = e.target.closest('[data-cms-block]');
825
- if (!activeBlock || activeBlock.getAttribute('data-block-id') !== currentBlockId) {
898
+ var activeBlock = resolveBlockFromTarget(e.target);
899
+ if (!activeBlock || getBlockId(activeBlock) !== currentBlockId) {
826
900
  hideToolbar();
827
901
  }
828
902
  }
@@ -838,12 +912,12 @@ function generateCmsOverlayScript(cmsParentOrigin) {
838
912
  return;
839
913
  }
840
914
 
841
- var block = e.target.closest('[data-cms-block]');
915
+ var block = resolveBlockFromTarget(e.target);
842
916
  if (block) {
843
917
  postToParent({
844
918
  type: 'cms-editable-click',
845
- blockId: block.getAttribute('data-block-id'),
846
- blockType: block.getAttribute('data-block-type'),
919
+ blockId: getBlockId(block),
920
+ blockType: getBlockType(block),
847
921
  contentPath: null
848
922
  });
849
923
  }
@@ -897,6 +971,29 @@ function generateCmsOverlayScript(cmsParentOrigin) {
897
971
  }
898
972
  }
899
973
  });
974
+ }
975
+
976
+ function scheduleEditableInjection() {
977
+ requestAnimationFrame(function() {
978
+ requestAnimationFrame(injectAllEditableSpans);
979
+ });
980
+ }
981
+
982
+ var stampObserver = new MutationObserver(function(mutations) {
983
+ var needsInject = mutations.some(function(m) {
984
+ return m.addedNodes.length > 0;
985
+ });
986
+ if (needsInject) scheduleEditableInjection();
987
+ });
988
+ stampObserver.observe(document.body, { childList: true, subtree: true });
989
+
990
+ initOverlayUi();
991
+
992
+ if (document.readyState === 'complete') {
993
+ scheduleEditableInjection();
994
+ } else {
995
+ window.addEventListener('load', scheduleEditableInjection, { once: true });
996
+ }
900
997
  })();
901
998
  `;
902
999
  }
@@ -1161,7 +1258,8 @@ async function renderParametricRoute({
1161
1258
  try {
1162
1259
  const { route, resolvedParams } = await client.route.getByPath.query({
1163
1260
  websiteId,
1164
- path
1261
+ path,
1262
+ preview: previewMode
1165
1263
  });
1166
1264
  if (route.state !== "Live") {
1167
1265
  return { status: "not_found" };
@@ -1227,19 +1325,22 @@ async function renderParametricRoute({
1227
1325
  });
1228
1326
  }
1229
1327
  const routeParams = resolvedParams ? Object.fromEntries(
1230
- Object.entries(resolvedParams).map(([key, param]) => [
1231
- key,
1232
- {
1233
- value: param.value,
1234
- schemaName: param.schemaName,
1235
- document: {
1236
- id: param.document.id,
1237
- title: param.document.title,
1238
- content: param.document.content,
1239
- schema_name: param.document.schema_name
1328
+ Object.entries(resolvedParams).map(([key, param]) => {
1329
+ const documentContent = previewMode ? param.document.draft_content : param.document.published_content;
1330
+ return [
1331
+ key,
1332
+ {
1333
+ value: param.value,
1334
+ schemaName: param.schemaName,
1335
+ document: {
1336
+ id: param.document.id,
1337
+ title: param.document.title,
1338
+ content: documentContent ?? param.document.draft_content,
1339
+ schema_name: param.document.schema_name
1340
+ }
1240
1341
  }
1241
- }
1242
- ])
1342
+ ];
1343
+ })
1243
1344
  ) : void 0;
1244
1345
  return {
1245
1346
  status: "ok",