cms-renderer 0.2.5 → 0.2.9

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.
@@ -1,22 +1,19 @@
1
1
  import * as React from 'react';
2
2
  import { Metadata } from 'next';
3
+ import { CmsConfig } from './cms-api.js';
3
4
  import { BlockComponentRegistry } from './types.js';
5
+ import '@repo/cms-schema/trpc';
6
+ import '@trpc/client';
4
7
  import '@repo/cms-schema/blocks';
5
8
 
6
- type PageProps = {
9
+ type PageProps = CmsConfig & {
7
10
  params: Promise<{
8
11
  slug: string[];
9
12
  }>;
10
13
  searchParams?: Promise<{
11
14
  [key: string]: string | string[] | undefined;
12
15
  }>;
13
- /** CMS API base URL (e.g., 'http://localhost:3000') */
14
- cmsUrl: string;
15
16
  registry?: Partial<BlockComponentRegistry>;
16
- /** API key for CMS API authentication */
17
- apiKey?: string;
18
- /** Website ID (required if not using env variables) */
19
- websiteId?: string;
20
17
  };
21
18
  /**
22
19
  * Force dynamic rendering to ensure routes are always fresh.
@@ -341,65 +341,66 @@ function renderToWalkableTree(node, keyPrefix = "") {
341
341
  }
342
342
  return node;
343
343
  }
344
- function BlockRenderer({ block, registry, disableEditable }) {
345
- const Component = Object.hasOwn(registry, block.type) ? registry[block.type] : void 0;
344
+ function BlockRenderer({
345
+ block,
346
+ registry,
347
+ disableEditable,
348
+ routeParams
349
+ }) {
350
+ const Component = registry[block.type];
346
351
  if (!Component) {
347
352
  if (process.env.NODE_ENV === "development") {
348
353
  console.warn(`[BlockRenderer] Unknown block type: ${block.type}`);
349
354
  }
350
355
  return null;
351
356
  }
352
- const component = /* @__PURE__ */ jsx(Component, { content: block.content });
357
+ const component = /* @__PURE__ */ jsx(Component, { content: block.content, routeParams });
353
358
  if (disableEditable) {
354
359
  return component;
355
360
  }
356
- const renderedTree = renderToWalkableTree(component);
357
361
  const contentValueMap = extractContentValues(block.content);
358
- const usedPaths = /* @__PURE__ */ new Set();
359
- let isRoot = true;
360
- const wrappedComponent = walkReactNode(renderedTree, {
361
- onText: ({ value, key, path }) => {
362
- const matches = contentValueMap.get(value);
363
- if (!matches || matches.length === 0) {
364
- return value;
365
- }
366
- const match = matches.find((m) => !usedPaths.has(m.contentPath)) ?? matches[0];
367
- if (!match) return value;
368
- usedPaths.add(match.contentPath);
369
- const spanKey = key ?? `${block.id}-${match.contentPath}-${path.join("-")}`;
370
- return /* @__PURE__ */ jsx(
371
- "span",
372
- {
373
- "data-cms-editable": true,
374
- "data-block-id": block.id,
375
- "data-block-type": block.type,
376
- "data-content-path": match.contentPath,
377
- children: value
378
- },
379
- spanKey
380
- );
381
- },
382
- onElement: ({ element, path }) => {
383
- if (isRoot && path.length === 0) {
384
- isRoot = false;
385
- const elProps = element.props;
386
- const existingChildren = elProps?.children;
387
- return React.cloneElement(
388
- element,
389
- {
390
- "data-cms-block": true,
391
- "data-block-id": block.id,
392
- "data-block-type": block.type
393
- },
394
- existingChildren,
395
- /* @__PURE__ */ jsx(BlockToolbar, { blockId: block.id }, "cms-toolbar")
396
- );
397
- }
398
- return element;
362
+ let renderedComponent = component;
363
+ let needsClientSideSpans = true;
364
+ try {
365
+ const renderedTree = renderToWalkableTree(component);
366
+ const isWalkable = React.isValidElement(renderedTree) && typeof renderedTree.type === "string";
367
+ if (isWalkable) {
368
+ const usedPaths = /* @__PURE__ */ new Set();
369
+ renderedComponent = walkReactNode(renderedTree, {
370
+ onText: ({ value, key, path }) => {
371
+ const matches = contentValueMap.get(value);
372
+ if (!matches || matches.length === 0) return value;
373
+ const match = matches.find((m) => !usedPaths.has(m.contentPath)) ?? matches[0];
374
+ if (!match) return value;
375
+ usedPaths.add(match.contentPath);
376
+ const spanKey = key ?? `${block.id}-${match.contentPath}-${path.join("-")}`;
377
+ return /* @__PURE__ */ jsx(
378
+ "span",
379
+ {
380
+ "data-cms-editable": true,
381
+ "data-block-id": block.id,
382
+ "data-block-type": block.type,
383
+ "data-content-path": match.contentPath,
384
+ children: value
385
+ },
386
+ spanKey
387
+ );
388
+ }
389
+ });
390
+ needsClientSideSpans = false;
399
391
  }
400
- });
401
- return /* @__PURE__ */ jsxs("div", { style: { display: "contents" }, children: [
402
- /* @__PURE__ */ jsx("style", { children: `
392
+ } catch {
393
+ }
394
+ const contentEntries = needsClientSideSpans ? Array.from(contentValueMap.entries()).map(([value, matches]) => ({ v: value, p: matches[0]?.contentPath })).filter((e) => !!e.p) : [];
395
+ return /* @__PURE__ */ jsxs(
396
+ "div",
397
+ {
398
+ "data-cms-block": true,
399
+ "data-block-id": block.id,
400
+ "data-block-type": block.type,
401
+ style: { position: "relative" },
402
+ children: [
403
+ /* @__PURE__ */ jsx("style", { children: `
403
404
  [data-cms-block] {
404
405
  position: relative;
405
406
  }
@@ -469,73 +470,128 @@ function BlockRenderer({ block, registry, disableEditable }) {
469
470
  height: 16px;
470
471
  }
471
472
  ` }),
472
- /* @__PURE__ */ jsx(
473
- "script",
474
- {
475
- dangerouslySetInnerHTML: {
476
- __html: `
473
+ /* @__PURE__ */ jsx(
474
+ "script",
475
+ {
476
+ dangerouslySetInnerHTML: {
477
+ __html: `
477
478
  (function() {
478
- if (window.__cmsEditableInitialized) return;
479
- window.__cmsEditableInitialized = true;
479
+ if (!window.__cmsEditableInitialized) {
480
+ window.__cmsEditableInitialized = true;
480
481
 
481
- document.addEventListener('click', function(e) {
482
- // Ignore toolbar clicks
483
- if (e.target.closest('.cms-block-toolbar')) {
484
- return;
485
- }
482
+ document.addEventListener('click', function(e) {
483
+ if (e.target.closest('.cms-block-toolbar')) return;
486
484
 
487
- // Check for editable text click first (more specific)
488
- var editableTarget = e.target.closest('[data-cms-editable]');
489
- if (editableTarget) {
490
- var message = {
491
- type: 'cms-editable-click',
492
- blockId: editableTarget.getAttribute('data-block-id'),
493
- blockType: editableTarget.getAttribute('data-block-type'),
494
- contentPath: editableTarget.getAttribute('data-content-path')
495
- };
485
+ var editableTarget = e.target.closest('[data-cms-editable]');
486
+ if (editableTarget) {
487
+ var message = {
488
+ type: 'cms-editable-click',
489
+ blockId: editableTarget.getAttribute('data-block-id'),
490
+ blockType: editableTarget.getAttribute('data-block-type'),
491
+ contentPath: editableTarget.getAttribute('data-content-path')
492
+ };
493
+ if (window.parent && window.parent !== window) {
494
+ window.parent.postMessage(message, '*');
495
+ }
496
+ return;
497
+ }
496
498
 
497
- if (window.parent && window.parent !== window) {
498
- window.parent.postMessage(message, '*');
499
+ var blockTarget = e.target.closest('[data-cms-block]');
500
+ if (blockTarget) {
501
+ var message = {
502
+ type: 'cms-editable-click',
503
+ blockId: blockTarget.getAttribute('data-block-id'),
504
+ blockType: blockTarget.getAttribute('data-block-type'),
505
+ contentPath: null
506
+ };
507
+ if (window.parent && window.parent !== window) {
508
+ window.parent.postMessage(message, '*');
509
+ }
499
510
  }
500
- return;
511
+ });
512
+ }
513
+ })();
514
+ ${contentEntries.length > 0 ? `
515
+ (function() {
516
+ var blockId = ${JSON.stringify(block.id)};
517
+ var blockType = ${JSON.stringify(block.type)};
518
+ var entries = ${JSON.stringify(contentEntries)};
519
+
520
+ function injectSpans() {
521
+ var block = document.querySelector('[data-block-id="' + blockId + '"]');
522
+ if (!block || block.getAttribute('data-cms-spans-injected')) return;
523
+ block.setAttribute('data-cms-spans-injected', 'true');
524
+
525
+ var used = {};
526
+ var walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT, null);
527
+ var textNodes = [];
528
+ var n;
529
+ while (n = walker.nextNode()) {
530
+ if (n.parentNode && n.parentNode.closest && n.parentNode.closest('.cms-block-toolbar')) continue;
531
+ if (n.nodeValue && n.nodeValue.trim()) textNodes.push(n);
501
532
  }
502
533
 
503
- // Check for block-level click
504
- var blockTarget = e.target.closest('[data-cms-block]');
505
- if (blockTarget) {
506
- var message = {
507
- type: 'cms-editable-click',
508
- blockId: blockTarget.getAttribute('data-block-id'),
509
- blockType: blockTarget.getAttribute('data-block-type'),
510
- contentPath: null
511
- };
534
+ for (var i = 0; i < textNodes.length; i++) {
535
+ var node = textNodes[i];
536
+ var text = node.nodeValue;
537
+ if (!text) continue;
512
538
 
513
- if (window.parent && window.parent !== window) {
514
- window.parent.postMessage(message, '*');
539
+ for (var j = 0; j < entries.length; j++) {
540
+ var e = entries[j];
541
+ if (used[e.p]) continue;
542
+ if (text.indexOf(e.v) !== -1 && text.trim() === e.v.trim()) {
543
+ used[e.p] = true;
544
+ var span = document.createElement('span');
545
+ span.setAttribute('data-cms-editable', '');
546
+ span.setAttribute('data-block-id', blockId);
547
+ span.setAttribute('data-block-type', blockType);
548
+ span.setAttribute('data-content-path', e.p);
549
+ node.parentNode.insertBefore(span, node);
550
+ span.appendChild(node);
551
+ break;
552
+ }
515
553
  }
516
554
  }
517
- });
555
+ }
556
+
557
+ if (document.readyState === 'complete') {
558
+ requestAnimationFrame(injectSpans);
559
+ } else {
560
+ window.addEventListener('load', function() {
561
+ requestAnimationFrame(injectSpans);
562
+ });
563
+ }
518
564
  })();
565
+ ` : ""}
519
566
  `
520
- }
521
- }
522
- ),
523
- wrappedComponent
524
- ] }, block.id);
567
+ }
568
+ }
569
+ ),
570
+ renderedComponent,
571
+ /* @__PURE__ */ jsx(BlockToolbar, { blockId: block.id }, "cms-toolbar")
572
+ ]
573
+ },
574
+ block.id
575
+ );
525
576
  }
526
577
 
527
578
  // lib/cms-api.ts
528
579
  import { createTRPCClient, httpBatchLink } from "@trpc/client";
529
580
  import superjson from "superjson";
530
581
  function getCmsApiUrl(cmsUrl) {
531
- return `${cmsUrl}/api/trpc`;
582
+ return new URL("/api/trpc", cmsUrl).toString();
532
583
  }
533
- function createFetchWithApiKey(apiKey) {
584
+ function createFetchWithApiKey(apiKey, websiteId) {
534
585
  return async (url, options) => {
535
586
  let finalUrl = url;
587
+ const urlObj = new URL(url.toString());
536
588
  if (apiKey) {
537
- const urlObj = new URL(url.toString());
538
589
  urlObj.searchParams.set("api_key", apiKey);
590
+ }
591
+ if (websiteId) {
592
+ urlObj.searchParams.set("website_id", websiteId);
593
+ }
594
+ if (apiKey || websiteId) {
539
595
  finalUrl = urlObj.toString();
540
596
  }
541
597
  const response = await fetch(finalUrl, options);
@@ -544,13 +600,12 @@ function createFetchWithApiKey(apiKey) {
544
600
  }
545
601
  function createCmsClient(options) {
546
602
  const url = getCmsApiUrl(options.cmsUrl);
547
- console.log("[CMS API] Creating client with URL:", url);
548
603
  return createTRPCClient({
549
604
  links: [
550
605
  httpBatchLink({
551
606
  url,
552
607
  transformer: superjson,
553
- fetch: createFetchWithApiKey(options.apiKey)
608
+ fetch: createFetchWithApiKey(options.apiKey, options.websiteId)
554
609
  })
555
610
  ]
556
611
  });
@@ -606,7 +661,7 @@ async function ParametricRoutePage({
606
661
  const path = normalizePath(rawPath);
607
662
  const client = getCmsClient({ apiKey, cmsUrl });
608
663
  try {
609
- const { route } = await client.route.getByPath.query({ websiteId, path });
664
+ const { route, resolvedParams } = await client.route.getByPath.query({ websiteId, path });
610
665
  if (route.state !== "Live") {
611
666
  console.error(`Route found but not Live. Path: ${path}, State: ${route.state}`);
612
667
  notFound();
@@ -663,12 +718,28 @@ async function ParametricRoutePage({
663
718
  content
664
719
  });
665
720
  }
721
+ const routeParams = resolvedParams ? Object.fromEntries(
722
+ Object.entries(resolvedParams).map(([key, param]) => [
723
+ key,
724
+ {
725
+ value: param.value,
726
+ schemaName: param.schemaName,
727
+ document: {
728
+ id: param.document.id,
729
+ title: param.document.title,
730
+ content: param.document.content,
731
+ schema_name: param.document.schema_name
732
+ }
733
+ }
734
+ ])
735
+ ) : void 0;
666
736
  return /* @__PURE__ */ jsx2("main", { children: blocks.map((block) => /* @__PURE__ */ jsx2(
667
737
  BlockRenderer,
668
738
  {
669
739
  block,
670
740
  registry: registry ?? {},
671
- disableEditable: !editMode
741
+ disableEditable: !editMode,
742
+ routeParams
672
743
  },
673
744
  block.id
674
745
  )) });