cms-renderer 0.6.12 → 0.7.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/dist/lib/block-renderer.d.ts +16 -12
- package/dist/lib/block-renderer.js +137 -48
- package/dist/lib/block-renderer.js.map +1 -1
- package/dist/lib/custom-schemas.js.map +1 -1
- package/dist/lib/docs-markdown.d.ts +2 -2
- package/dist/lib/parametric-route.d.ts +15 -2
- package/dist/lib/parametric-route.js +167 -53
- package/dist/lib/parametric-route.js.map +1 -1
- package/dist/lib/renderer.d.ts +12 -3
- package/dist/lib/renderer.js +178 -53
- package/dist/lib/renderer.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
interface DocsMarkdownProps {
|
|
@@ -13,6 +13,6 @@ interface DocsMarkdownProps {
|
|
|
13
13
|
}
|
|
14
14
|
declare function markdownStartsWithHeading(markdown: string): boolean;
|
|
15
15
|
declare function initMarkdown(): Promise<void>;
|
|
16
|
-
declare function DocsMarkdown({ content, className, renderImage, }: DocsMarkdownProps): Promise<
|
|
16
|
+
declare function DocsMarkdown({ content, className, renderImage, }: DocsMarkdownProps): Promise<react.JSX.Element>;
|
|
17
17
|
|
|
18
18
|
export { DocsMarkdown, type DocsMarkdownProps, initMarkdown, markdownStartsWithHeading };
|
|
@@ -25,6 +25,19 @@ type ParametricRouteProps = CmsConfig & {
|
|
|
25
25
|
};
|
|
26
26
|
registry?: Partial<BlockComponentRegistry>;
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Render-time options that are NOT part of the page props surface. `preview` is
|
|
30
|
+
* supplied only by the dedicated preview page wrapper (`ParametricRoutePreviewPage`),
|
|
31
|
+
* never by query params, so the production page can never accidentally opt into
|
|
32
|
+
* edit overlays or draft content.
|
|
33
|
+
*/
|
|
34
|
+
type RenderParametricRouteOptions = {
|
|
35
|
+
/**
|
|
36
|
+
* Renders the preview experience: forces the edit-mode overlay/hovers and pulls
|
|
37
|
+
* block content from `draft_content` instead of `published_content`.
|
|
38
|
+
*/
|
|
39
|
+
preview?: boolean;
|
|
40
|
+
};
|
|
28
41
|
type ParametricRouteResult = {
|
|
29
42
|
status: 'ok';
|
|
30
43
|
node: ReactNode;
|
|
@@ -40,6 +53,6 @@ declare function getWebsiteId(providedWebsiteId?: string): string;
|
|
|
40
53
|
* Renders CMS-backed blocks for a multi-segment path. Maps NOT_FOUND-style API
|
|
41
54
|
* outcomes to `{ status: 'not_found' }` instead of throwing.
|
|
42
55
|
*/
|
|
43
|
-
declare function renderParametricRoute({ params, searchParams, registry, apiKey, cmsUrl, websiteId: providedWebsiteId, }: ParametricRouteProps): Promise<ParametricRouteResult>;
|
|
56
|
+
declare function renderParametricRoute({ params, searchParams, registry, apiKey, cmsUrl, websiteId: providedWebsiteId, }: ParametricRouteProps, { preview }?: RenderParametricRouteOptions): Promise<ParametricRouteResult>;
|
|
44
57
|
|
|
45
|
-
export { type ParametricRouteProps, type ParametricRouteResult, getWebsiteId, renderParametricRoute };
|
|
58
|
+
export { type ParametricRouteProps, type ParametricRouteResult, type RenderParametricRouteOptions, getWebsiteId, renderParametricRoute };
|
|
@@ -402,7 +402,7 @@ function generateCmsOverlayScript(cmsParentOrigin) {
|
|
|
402
402
|
border-radius: 2px;
|
|
403
403
|
}
|
|
404
404
|
[data-cms-editable]:not([data-cms-block]):hover {
|
|
405
|
-
outline: 2px solid #
|
|
405
|
+
outline: 2px solid var(--component-accent, #A78BFA);
|
|
406
406
|
outline-offset: 2px;
|
|
407
407
|
}
|
|
408
408
|
#cms-overlay-root {
|
|
@@ -414,20 +414,43 @@ function generateCmsOverlayScript(cmsParentOrigin) {
|
|
|
414
414
|
pointer-events: none;
|
|
415
415
|
z-index: 99998;
|
|
416
416
|
}
|
|
417
|
-
#cms-overlay-root >
|
|
417
|
+
#cms-overlay-root > *:not(.cms-block-toolbar) {
|
|
418
418
|
pointer-events: none;
|
|
419
419
|
}
|
|
420
420
|
.cms-block-outline {
|
|
421
421
|
position: fixed;
|
|
422
|
-
|
|
422
|
+
box-sizing: border-box;
|
|
423
|
+
border: 4px solid var(--component-accent, #A78BFA);
|
|
423
424
|
border-radius: 4px;
|
|
424
425
|
pointer-events: none;
|
|
425
426
|
z-index: 99997;
|
|
426
427
|
transition: all 0.15s ease;
|
|
427
428
|
}
|
|
428
429
|
.cms-block-outline.cms-outline-selected {
|
|
429
|
-
border-color: #
|
|
430
|
-
border-width:
|
|
430
|
+
border-color: var(--component-accent, #A78BFA);
|
|
431
|
+
border-width: 4px;
|
|
432
|
+
}
|
|
433
|
+
.cms-block-label {
|
|
434
|
+
position: absolute;
|
|
435
|
+
top: 0;
|
|
436
|
+
left: 0;
|
|
437
|
+
display: none;
|
|
438
|
+
max-width: 240px;
|
|
439
|
+
padding: 2px 8px;
|
|
440
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
441
|
+
font-size: 12px;
|
|
442
|
+
font-weight: 600;
|
|
443
|
+
line-height: 1.4;
|
|
444
|
+
color: #fff;
|
|
445
|
+
background: var(--component-accent, #A78BFA);
|
|
446
|
+
border-radius: 4px 4px 4px 0;
|
|
447
|
+
white-space: nowrap;
|
|
448
|
+
overflow: hidden;
|
|
449
|
+
text-overflow: ellipsis;
|
|
450
|
+
pointer-events: none;
|
|
451
|
+
}
|
|
452
|
+
.cms-block-label.cms-label-visible {
|
|
453
|
+
display: block;
|
|
431
454
|
}
|
|
432
455
|
.cms-block-cursor {
|
|
433
456
|
position: fixed;
|
|
@@ -511,9 +534,69 @@ function generateCmsOverlayScript(cmsParentOrigin) {
|
|
|
511
534
|
blockEl.setAttribute('data-cms-block', '');
|
|
512
535
|
blockEl.setAttribute('data-block-id', blockId);
|
|
513
536
|
blockEl.setAttribute('data-block-type', blockType);
|
|
537
|
+
|
|
538
|
+
injectEditableSpans(
|
|
539
|
+
blockEl,
|
|
540
|
+
blockId,
|
|
541
|
+
blockType,
|
|
542
|
+
sentinel.getAttribute('data-content-entries')
|
|
543
|
+
);
|
|
514
544
|
});
|
|
515
545
|
}
|
|
516
546
|
|
|
547
|
+
function injectEditableSpans(blockEl, blockId, blockType, rawEntries) {
|
|
548
|
+
if (!rawEntries || blockEl.querySelector('[data-cms-editable][data-content-path]')) return;
|
|
549
|
+
|
|
550
|
+
var entries;
|
|
551
|
+
try {
|
|
552
|
+
entries = JSON.parse(rawEntries);
|
|
553
|
+
} catch (_) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (!Array.isArray(entries) || entries.length === 0) return;
|
|
557
|
+
|
|
558
|
+
var used = {};
|
|
559
|
+
var walker = document.createTreeWalker(blockEl, NodeFilter.SHOW_TEXT, null);
|
|
560
|
+
var textNodes = [];
|
|
561
|
+
var node = walker.nextNode();
|
|
562
|
+
while (node !== null) {
|
|
563
|
+
if (node.nodeValue && node.nodeValue.trim()) {
|
|
564
|
+
textNodes.push(node);
|
|
565
|
+
}
|
|
566
|
+
node = walker.nextNode();
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
for (var i = 0; i < textNodes.length; i++) {
|
|
570
|
+
var textNode = textNodes[i];
|
|
571
|
+
var parentEl = textNode.parentElement;
|
|
572
|
+
if (!parentEl || parentEl.closest('svg') || parentEl.closest('[data-cms-editable]')) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
var text = textNode.nodeValue;
|
|
577
|
+
if (!text) continue;
|
|
578
|
+
|
|
579
|
+
for (var j = 0; j < entries.length; j++) {
|
|
580
|
+
var entry = entries[j];
|
|
581
|
+
if (!entry || typeof entry.v !== 'string' || typeof entry.p !== 'string' || used[entry.p]) {
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (text.trim() !== entry.v.trim()) continue;
|
|
585
|
+
|
|
586
|
+
used[entry.p] = true;
|
|
587
|
+
var span = document.createElement('span');
|
|
588
|
+
span.setAttribute('data-cms-editable', '');
|
|
589
|
+
span.setAttribute('data-block-id', blockId);
|
|
590
|
+
span.setAttribute('data-block-type', blockType);
|
|
591
|
+
span.setAttribute('data-content-path', entry.p);
|
|
592
|
+
span.setAttribute('contenteditable', 'true');
|
|
593
|
+
parentEl.insertBefore(span, textNode);
|
|
594
|
+
span.appendChild(textNode);
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
517
600
|
stampBlockElements();
|
|
518
601
|
|
|
519
602
|
var stampObserver = new MutationObserver(function(mutations) {
|
|
@@ -532,15 +615,19 @@ function generateCmsOverlayScript(cmsParentOrigin) {
|
|
|
532
615
|
cursor.className = 'cms-block-cursor';
|
|
533
616
|
overlayRoot.appendChild(cursor);
|
|
534
617
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
618
|
+
function createOutline(extraClass) {
|
|
619
|
+
var el = document.createElement('div');
|
|
620
|
+
el.className = 'cms-block-outline' + (extraClass ? ' ' + extraClass : '');
|
|
621
|
+
el.style.display = 'none';
|
|
622
|
+
var label = document.createElement('span');
|
|
623
|
+
label.className = 'cms-block-label';
|
|
624
|
+
el.appendChild(label);
|
|
625
|
+
overlayRoot.appendChild(el);
|
|
626
|
+
return el;
|
|
627
|
+
}
|
|
539
628
|
|
|
540
|
-
var
|
|
541
|
-
selectedOutline
|
|
542
|
-
selectedOutline.style.display = 'none';
|
|
543
|
-
overlayRoot.appendChild(selectedOutline);
|
|
629
|
+
var outline = createOutline();
|
|
630
|
+
var selectedOutline = createOutline('cms-outline-selected');
|
|
544
631
|
|
|
545
632
|
var toolbar = document.createElement('div');
|
|
546
633
|
toolbar.className = 'cms-block-toolbar';
|
|
@@ -568,35 +655,19 @@ function generateCmsOverlayScript(cmsParentOrigin) {
|
|
|
568
655
|
var toolbarVisible = false;
|
|
569
656
|
var selectedBlockId = null;
|
|
570
657
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function preserveEditParamsOnNavigation(e) {
|
|
587
|
-
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
var anchor = e.target.closest('a[href]');
|
|
592
|
-
if (!anchor || (anchor.target && anchor.target !== '_self') || anchor.hasAttribute('download')) {
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
var href = anchor.getAttribute('href');
|
|
597
|
-
var decorated = decoratePreviewUrl(href);
|
|
598
|
-
if (decorated && decorated !== href) {
|
|
599
|
-
anchor.setAttribute('href', decorated);
|
|
658
|
+
// In edit mode we hijack every link/button activation. Following a link or
|
|
659
|
+
// firing a button's own handler makes inline editing on that element
|
|
660
|
+
// impossible (the click navigates away or triggers an action instead of
|
|
661
|
+
// placing the caret). We block the default action and stop the event from
|
|
662
|
+
// reaching the element's own listeners, while still letting our selection /
|
|
663
|
+
// edit routing run below.
|
|
664
|
+
function blockInteractiveActivation(e) {
|
|
665
|
+
var interactive = e.target.closest(
|
|
666
|
+
'a[href], button, [role="button"], input[type="submit"], input[type="button"], input[type="reset"]'
|
|
667
|
+
);
|
|
668
|
+
if (interactive) {
|
|
669
|
+
e.preventDefault();
|
|
670
|
+
e.stopPropagation();
|
|
600
671
|
}
|
|
601
672
|
}
|
|
602
673
|
|
|
@@ -631,9 +702,18 @@ function generateCmsOverlayScript(cmsParentOrigin) {
|
|
|
631
702
|
return -1;
|
|
632
703
|
}
|
|
633
704
|
|
|
705
|
+
function formatBlockType(type) {
|
|
706
|
+
if (!type) return 'Block';
|
|
707
|
+
return type
|
|
708
|
+
.replace(/[-_]+/g, ' ')
|
|
709
|
+
.replace(/\\b\\w/g, function(c) { return c.toUpperCase(); });
|
|
710
|
+
}
|
|
711
|
+
|
|
634
712
|
function updateOutline(el, outlineEl) {
|
|
713
|
+
var label = outlineEl.querySelector('.cms-block-label');
|
|
635
714
|
if (!el) {
|
|
636
715
|
outlineEl.style.display = 'none';
|
|
716
|
+
if (label) label.classList.remove('cms-label-visible');
|
|
637
717
|
return;
|
|
638
718
|
}
|
|
639
719
|
var rect = el.getBoundingClientRect();
|
|
@@ -642,6 +722,10 @@ function generateCmsOverlayScript(cmsParentOrigin) {
|
|
|
642
722
|
outlineEl.style.left = rect.left + 'px';
|
|
643
723
|
outlineEl.style.width = rect.width + 'px';
|
|
644
724
|
outlineEl.style.height = rect.height + 'px';
|
|
725
|
+
if (label) {
|
|
726
|
+
label.textContent = formatBlockType(el.getAttribute('data-block-type'));
|
|
727
|
+
label.classList.add('cms-label-visible');
|
|
728
|
+
}
|
|
645
729
|
}
|
|
646
730
|
|
|
647
731
|
function positionToolbar(x, y) {
|
|
@@ -718,17 +802,19 @@ function generateCmsOverlayScript(cmsParentOrigin) {
|
|
|
718
802
|
});
|
|
719
803
|
|
|
720
804
|
document.addEventListener('click', function(e) {
|
|
721
|
-
|
|
805
|
+
// The floating toolbar is interactive (and its buttons are real <button>s);
|
|
806
|
+
// bail out before any blocking so its own handlers run.
|
|
807
|
+
if (e.target.closest('[data-cms-toolbar]')) return;
|
|
722
808
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
809
|
+
blockInteractiveActivation(e);
|
|
810
|
+
|
|
811
|
+
if (toolbarVisible) {
|
|
812
|
+
var activeBlock = e.target.closest('[data-cms-block]');
|
|
813
|
+
if (!activeBlock || activeBlock.getAttribute('data-block-id') !== currentBlockId) {
|
|
726
814
|
hideToolbar();
|
|
727
815
|
}
|
|
728
816
|
}
|
|
729
817
|
|
|
730
|
-
if (e.target.closest('[data-cms-toolbar]')) return;
|
|
731
|
-
|
|
732
818
|
var editable = e.target.closest('[data-cms-editable]');
|
|
733
819
|
if (editable) {
|
|
734
820
|
postToParent({
|
|
@@ -859,6 +945,27 @@ function getCmsParentTargetOrigin(preferredCmsUrl, explicitParentOrigin) {
|
|
|
859
945
|
|
|
860
946
|
// lib/block-renderer.tsx
|
|
861
947
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
948
|
+
function extractContentValues(content, basePath = []) {
|
|
949
|
+
const map = /* @__PURE__ */ new Map();
|
|
950
|
+
function walk(obj, path) {
|
|
951
|
+
if (typeof obj === "string" && obj.trim() !== "") {
|
|
952
|
+
const contentPath = path.join(".");
|
|
953
|
+
const existing = map.get(obj) || [];
|
|
954
|
+
existing.push({ contentPath, value: obj });
|
|
955
|
+
map.set(obj, existing);
|
|
956
|
+
} else if (Array.isArray(obj)) {
|
|
957
|
+
for (let index = 0; index < obj.length; index++) {
|
|
958
|
+
walk(obj[index], [...path, String(index)]);
|
|
959
|
+
}
|
|
960
|
+
} else if (obj && typeof obj === "object") {
|
|
961
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
962
|
+
walk(value, [...path, key]);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
walk(content, basePath);
|
|
967
|
+
return map;
|
|
968
|
+
}
|
|
862
969
|
function CmsEditableInit({
|
|
863
970
|
cmsUrl,
|
|
864
971
|
cmsParentOrigin
|
|
@@ -907,6 +1014,7 @@ function BlockRenderer({
|
|
|
907
1014
|
block,
|
|
908
1015
|
registry,
|
|
909
1016
|
disableEditable,
|
|
1017
|
+
enableContentEditable,
|
|
910
1018
|
routeParams,
|
|
911
1019
|
path
|
|
912
1020
|
}) {
|
|
@@ -922,6 +1030,7 @@ function BlockRenderer({
|
|
|
922
1030
|
if (disableEditable) {
|
|
923
1031
|
return component;
|
|
924
1032
|
}
|
|
1033
|
+
const contentEntries = enableContentEditable ? [...extractContentValues(block.content).values()].flat().map(({ value, contentPath }) => ({ v: value, p: contentPath })) : void 0;
|
|
925
1034
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
926
1035
|
/* @__PURE__ */ jsx(
|
|
927
1036
|
"span",
|
|
@@ -929,6 +1038,7 @@ function BlockRenderer({
|
|
|
929
1038
|
"data-cms-sentinel": "",
|
|
930
1039
|
"data-block-id": block.id,
|
|
931
1040
|
"data-block-type": block.type,
|
|
1041
|
+
"data-content-entries": contentEntries ? JSON.stringify(contentEntries) : void 0,
|
|
932
1042
|
style: { display: "none" },
|
|
933
1043
|
"aria-hidden": "true"
|
|
934
1044
|
}
|
|
@@ -1010,7 +1120,7 @@ async function renderParametricRoute({
|
|
|
1010
1120
|
apiKey,
|
|
1011
1121
|
cmsUrl,
|
|
1012
1122
|
websiteId: providedWebsiteId
|
|
1013
|
-
}) {
|
|
1123
|
+
}, { preview = false } = {}) {
|
|
1014
1124
|
const websiteId = getWebsiteId(providedWebsiteId);
|
|
1015
1125
|
const { slug } = "then" in params ? await params : params;
|
|
1016
1126
|
const resolvedSearchParams = searchParams && "then" in searchParams ? await searchParams : searchParams;
|
|
@@ -1025,10 +1135,11 @@ async function renderParametricRoute({
|
|
|
1025
1135
|
}
|
|
1026
1136
|
}
|
|
1027
1137
|
}
|
|
1028
|
-
const editModeParam = resolvedSearchParams?.edit_mode;
|
|
1029
|
-
const editMode = editModeParam === true || editModeParam === "true" || editModeParam === "1";
|
|
1030
1138
|
const cmsParentOriginParam = resolvedSearchParams?.cms_parent_origin;
|
|
1031
1139
|
const cmsParentOrigin = typeof cmsParentOriginParam === "boolean" ? void 0 : Array.isArray(cmsParentOriginParam) ? cmsParentOriginParam[0] : cmsParentOriginParam;
|
|
1140
|
+
const previewMode = preview === true;
|
|
1141
|
+
const editModeParam = resolvedSearchParams?.edit_mode;
|
|
1142
|
+
const editMode = previewMode || editModeParam === true || editModeParam === "true" || editModeParam === "1";
|
|
1032
1143
|
const rawPath = `/${slug.join("/")}`;
|
|
1033
1144
|
const path = normalizePath(rawPath);
|
|
1034
1145
|
if (/\.[a-zA-Z0-9]+$/.test(path)) {
|
|
@@ -1068,7 +1179,9 @@ async function renderParametricRoute({
|
|
|
1068
1179
|
]);
|
|
1069
1180
|
const blocks = [];
|
|
1070
1181
|
for (const block of blockResults) {
|
|
1071
|
-
if (!block
|
|
1182
|
+
if (!block) continue;
|
|
1183
|
+
const blockContent = previewMode ? block.draft_content : block.published_content;
|
|
1184
|
+
if (blockContent == null) continue;
|
|
1072
1185
|
let content = null;
|
|
1073
1186
|
if (aiPreviewIndex !== null) {
|
|
1074
1187
|
const generatedBlock = generatedBlocks[block.id];
|
|
@@ -1078,7 +1191,7 @@ async function renderParametricRoute({
|
|
|
1078
1191
|
content = variants[variantIndex].content;
|
|
1079
1192
|
}
|
|
1080
1193
|
}
|
|
1081
|
-
content = content ??
|
|
1194
|
+
content = content ?? blockContent;
|
|
1082
1195
|
if (!content) continue;
|
|
1083
1196
|
if (block.schema_name === "article") {
|
|
1084
1197
|
const article = normalizeArticleContent(content);
|
|
@@ -1126,6 +1239,7 @@ async function renderParametricRoute({
|
|
|
1126
1239
|
block,
|
|
1127
1240
|
registry: registry ?? {},
|
|
1128
1241
|
disableEditable: !editMode,
|
|
1242
|
+
enableContentEditable: previewMode,
|
|
1129
1243
|
routeParams,
|
|
1130
1244
|
path
|
|
1131
1245
|
},
|