@cmssy/react 4.4.0 → 4.6.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/index.cjs CHANGED
@@ -184,163 +184,6 @@ async function resolveBlocks(blocks, loaderMap, locale, defaultLocale, context,
184
184
  })
185
185
  );
186
186
  }
187
- async function CmssyServerPage({
188
- page,
189
- blocks,
190
- locale = "en",
191
- defaultLocale = "en",
192
- enabledLocales,
193
- forms,
194
- auth,
195
- workspace
196
- }) {
197
- if (!page) return null;
198
- const map = buildBlockMap(blocks);
199
- const loaderMap = buildLoaderMap(blocks);
200
- const context = buildBlockContext(
201
- locale,
202
- defaultLocale,
203
- enabledLocales,
204
- false,
205
- forms,
206
- { auth, workspace }
207
- );
208
- const resolved = await resolveBlocks(
209
- page.blocks,
210
- loaderMap,
211
- locale,
212
- defaultLocale,
213
- context,
214
- enabledLocales
215
- );
216
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: page.blocks.map(
217
- (block, i) => renderResolvedBlock(block, map, locale, defaultLocale, {
218
- context,
219
- data: resolved[i]?.data,
220
- resolvedContent: resolved[i]?.content,
221
- enabledLocales
222
- })
223
- ) });
224
- }
225
- async function CmssyServerLayout({
226
- groups,
227
- blocks,
228
- position,
229
- locale = "en",
230
- defaultLocale = "en",
231
- enabledLocales
232
- }) {
233
- const group = groups.find((g) => g.position === position);
234
- const layoutBlocks = group ? group.blocks.filter((b) => b.isActive).slice().sort((a, b) => a.order - b.order) : [];
235
- if (layoutBlocks.length === 0) return null;
236
- const map = buildBlockMap(blocks);
237
- const loaderMap = buildLoaderMap(blocks);
238
- const context = buildBlockContext(locale, defaultLocale, enabledLocales);
239
- const resolved = await resolveBlocks(
240
- layoutBlocks,
241
- loaderMap,
242
- locale,
243
- defaultLocale,
244
- context,
245
- enabledLocales
246
- );
247
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: layoutBlocks.map(
248
- (block, i) => renderResolvedBlock(block, map, locale, defaultLocale, {
249
- context,
250
- data: resolved[i]?.data,
251
- resolvedContent: resolved[i]?.content,
252
- enabledLocales
253
- })
254
- ) });
255
- }
256
-
257
- // src/bridge/protocol.ts
258
- var PROTOCOL_VERSION = 2;
259
- function isProtocolCompatible(version) {
260
- return version === PROTOCOL_VERSION;
261
- }
262
-
263
- // src/bridge/messages.ts
264
- function normalizeOrigin(origin) {
265
- const trimmed = origin.trim();
266
- if (trimmed === "*") return "*";
267
- try {
268
- return new URL(trimmed).origin;
269
- } catch {
270
- return trimmed;
271
- }
272
- }
273
- function postToEditor(target, editorOrigin, message) {
274
- target.postMessage(message, normalizeOrigin(editorOrigin));
275
- }
276
- function isOriginAllowed(origin, allowed) {
277
- const list = Array.isArray(allowed) ? allowed : [allowed];
278
- const actual = normalizeOrigin(origin);
279
- return list.some((candidate) => {
280
- const expected = normalizeOrigin(candidate);
281
- return expected === "*" || expected === actual;
282
- });
283
- }
284
- function isObject(value) {
285
- return typeof value === "object" && value !== null && !Array.isArray(value);
286
- }
287
- function parseEditorMessage(data, origin, expectedOrigin) {
288
- if (!isOriginAllowed(origin, expectedOrigin)) return null;
289
- if (!isObject(data)) return null;
290
- switch (data.type) {
291
- case "cmssy:select":
292
- return typeof data.blockId === "string" && data.protocolVersion === PROTOCOL_VERSION ? {
293
- type: "cmssy:select",
294
- protocolVersion: PROTOCOL_VERSION,
295
- blockId: data.blockId
296
- } : null;
297
- case "cmssy:patch":
298
- return typeof data.blockId === "string" && isObject(data.content) && data.protocolVersion === PROTOCOL_VERSION ? {
299
- type: "cmssy:patch",
300
- blockId: data.blockId,
301
- content: data.content,
302
- protocolVersion: PROTOCOL_VERSION,
303
- ...isObject(data.style) ? { style: data.style } : {},
304
- ...isObject(data.advanced) ? { advanced: data.advanced } : {},
305
- ...typeof data.layoutPosition === "string" ? { layoutPosition: data.layoutPosition } : {}
306
- } : null;
307
- case "cmssy:parent-ready":
308
- return data.protocolVersion === PROTOCOL_VERSION ? { type: "cmssy:parent-ready", protocolVersion: PROTOCOL_VERSION } : null;
309
- case "cmssy:insert":
310
- return typeof data.blockId === "string" && typeof data.blockType === "string" && isObject(data.content) && typeof data.index === "number" && data.protocolVersion === PROTOCOL_VERSION ? {
311
- type: "cmssy:insert",
312
- protocolVersion: PROTOCOL_VERSION,
313
- blockId: data.blockId,
314
- blockType: data.blockType,
315
- content: data.content,
316
- ...isObject(data.style) ? { style: data.style } : {},
317
- ...isObject(data.advanced) ? { advanced: data.advanced } : {},
318
- index: data.index
319
- } : null;
320
- case "cmssy:reorder":
321
- return Array.isArray(data.blockIds) && data.blockIds.every((id) => typeof id === "string") && data.protocolVersion === PROTOCOL_VERSION ? {
322
- type: "cmssy:reorder",
323
- protocolVersion: PROTOCOL_VERSION,
324
- blockIds: data.blockIds
325
- } : null;
326
- case "cmssy:remove":
327
- return typeof data.blockId === "string" && data.protocolVersion === PROTOCOL_VERSION ? {
328
- type: "cmssy:remove",
329
- protocolVersion: PROTOCOL_VERSION,
330
- blockId: data.blockId
331
- } : null;
332
- case "cmssy:drag-over":
333
- return typeof data.y === "number" && data.protocolVersion === PROTOCOL_VERSION ? {
334
- type: "cmssy:drag-over",
335
- protocolVersion: PROTOCOL_VERSION,
336
- y: data.y
337
- } : null;
338
- case "cmssy:drag-end":
339
- return data.protocolVersion === PROTOCOL_VERSION ? { type: "cmssy:drag-end", protocolVersion: PROTOCOL_VERSION } : null;
340
- default:
341
- return null;
342
- }
343
- }
344
187
 
345
188
  // src/data/http.ts
346
189
  var CmssyRequestError = class extends Error {
@@ -717,6 +560,247 @@ var SUBMIT_FORM_MUTATION = `mutation SubmitForm($formId: ID!, $input: SubmitForm
717
560
  }
718
561
  }`;
719
562
 
563
+ // src/data/site-locales.ts
564
+ var TTL_MS = 6e4;
565
+ var MAX_ENTRIES = 64;
566
+ var cache = /* @__PURE__ */ new Map();
567
+ async function resolveSiteLocales(config, options) {
568
+ const key = `${resolveApiUrl(config.apiUrl)}::${config.org}::${config.workspaceSlug}`;
569
+ const cached = cache.get(key);
570
+ if (cached && cached.expires > Date.now()) return cached.value;
571
+ cache.delete(key);
572
+ let value;
573
+ try {
574
+ const data = await graphqlRequest(
575
+ config,
576
+ SITE_CONFIG_QUERY,
577
+ { workspaceSlug: config.workspaceSlug },
578
+ { ...options, public: true, retry: options?.retry ?? {} },
579
+ "site config"
580
+ );
581
+ const siteConfig = data.public?.siteConfig ?? null;
582
+ const defaultLocale = siteConfig?.defaultLanguage || "en";
583
+ const enabled = siteConfig?.enabledLanguages ?? [];
584
+ value = {
585
+ defaultLocale,
586
+ locales: enabled.length > 0 ? enabled : [defaultLocale]
587
+ };
588
+ } catch {
589
+ value = { defaultLocale: "en", locales: ["en"] };
590
+ }
591
+ if (cache.size >= MAX_ENTRIES) cache.clear();
592
+ cache.set(key, { value, expires: Date.now() + TTL_MS });
593
+ return value;
594
+ }
595
+ function splitLocaleFromPath(path, siteLocales) {
596
+ const first = path?.[0];
597
+ if (first && first !== siteLocales.defaultLocale && siteLocales.locales.includes(first)) {
598
+ return { locale: first, path: path.slice(1) };
599
+ }
600
+ return { locale: siteLocales.defaultLocale, path };
601
+ }
602
+
603
+ // src/components/resolve-render-locale.ts
604
+ var warned2 = false;
605
+ async function resolveRenderLocale({
606
+ locale,
607
+ defaultLocale,
608
+ enabledLocales,
609
+ config
610
+ }) {
611
+ if (locale && defaultLocale) return { locale, defaultLocale, enabledLocales };
612
+ if (config) {
613
+ const site = await resolveSiteLocales(config);
614
+ return {
615
+ locale: locale ?? defaultLocale ?? site.defaultLocale,
616
+ defaultLocale: defaultLocale ?? site.defaultLocale,
617
+ enabledLocales: enabledLocales ?? site.locales
618
+ };
619
+ }
620
+ if (process.env.NODE_ENV !== "production" && !warned2) {
621
+ warned2 = true;
622
+ console.warn(
623
+ '[cmssy] Rendering in "en": no locale was passed and no config was given to look the workspace\'s default language up. Pass `locale` (from the route) or `config`, or a workspace whose default language is not English will render its chrome in the wrong one.'
624
+ );
625
+ }
626
+ return {
627
+ locale: locale ?? defaultLocale ?? "en",
628
+ defaultLocale: defaultLocale ?? "en",
629
+ enabledLocales
630
+ };
631
+ }
632
+ async function CmssyServerPage({
633
+ page,
634
+ blocks,
635
+ locale: localeProp,
636
+ defaultLocale: defaultLocaleProp,
637
+ enabledLocales: enabledLocalesProp,
638
+ config,
639
+ forms,
640
+ auth,
641
+ workspace
642
+ }) {
643
+ if (!page) return null;
644
+ const { locale, defaultLocale, enabledLocales } = await resolveRenderLocale({
645
+ locale: localeProp,
646
+ defaultLocale: defaultLocaleProp,
647
+ enabledLocales: enabledLocalesProp,
648
+ config
649
+ });
650
+ const map = buildBlockMap(blocks);
651
+ const loaderMap = buildLoaderMap(blocks);
652
+ const context = buildBlockContext(
653
+ locale,
654
+ defaultLocale,
655
+ enabledLocales,
656
+ false,
657
+ forms,
658
+ { auth, workspace }
659
+ );
660
+ const resolved = await resolveBlocks(
661
+ page.blocks,
662
+ loaderMap,
663
+ locale,
664
+ defaultLocale,
665
+ context,
666
+ enabledLocales
667
+ );
668
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: page.blocks.map(
669
+ (block, i) => renderResolvedBlock(block, map, locale, defaultLocale, {
670
+ context,
671
+ data: resolved[i]?.data,
672
+ resolvedContent: resolved[i]?.content,
673
+ enabledLocales
674
+ })
675
+ ) });
676
+ }
677
+ async function CmssyServerLayout({
678
+ groups,
679
+ blocks,
680
+ position,
681
+ locale: localeProp,
682
+ defaultLocale: defaultLocaleProp,
683
+ enabledLocales: enabledLocalesProp,
684
+ config
685
+ }) {
686
+ const { locale, defaultLocale, enabledLocales } = await resolveRenderLocale({
687
+ locale: localeProp,
688
+ defaultLocale: defaultLocaleProp,
689
+ enabledLocales: enabledLocalesProp,
690
+ config
691
+ });
692
+ const group = groups.find((g) => g.position === position);
693
+ const layoutBlocks = group ? group.blocks.filter((b) => b.isActive).slice().sort((a, b) => a.order - b.order) : [];
694
+ if (layoutBlocks.length === 0) return null;
695
+ const map = buildBlockMap(blocks);
696
+ const loaderMap = buildLoaderMap(blocks);
697
+ const context = buildBlockContext(locale, defaultLocale, enabledLocales);
698
+ const resolved = await resolveBlocks(
699
+ layoutBlocks,
700
+ loaderMap,
701
+ locale,
702
+ defaultLocale,
703
+ context,
704
+ enabledLocales
705
+ );
706
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: layoutBlocks.map(
707
+ (block, i) => renderResolvedBlock(block, map, locale, defaultLocale, {
708
+ context,
709
+ data: resolved[i]?.data,
710
+ resolvedContent: resolved[i]?.content,
711
+ enabledLocales
712
+ })
713
+ ) });
714
+ }
715
+
716
+ // src/bridge/protocol.ts
717
+ var PROTOCOL_VERSION = 2;
718
+ function isProtocolCompatible(version) {
719
+ return version === PROTOCOL_VERSION;
720
+ }
721
+
722
+ // src/bridge/messages.ts
723
+ function normalizeOrigin(origin) {
724
+ const trimmed = origin.trim();
725
+ if (trimmed === "*") return "*";
726
+ try {
727
+ return new URL(trimmed).origin;
728
+ } catch {
729
+ return trimmed;
730
+ }
731
+ }
732
+ function postToEditor(target, editorOrigin, message) {
733
+ target.postMessage(message, normalizeOrigin(editorOrigin));
734
+ }
735
+ function isOriginAllowed(origin, allowed) {
736
+ const list = Array.isArray(allowed) ? allowed : [allowed];
737
+ const actual = normalizeOrigin(origin);
738
+ return list.some((candidate) => {
739
+ const expected = normalizeOrigin(candidate);
740
+ return expected === "*" || expected === actual;
741
+ });
742
+ }
743
+ function isObject(value) {
744
+ return typeof value === "object" && value !== null && !Array.isArray(value);
745
+ }
746
+ function parseEditorMessage(data, origin, expectedOrigin) {
747
+ if (!isOriginAllowed(origin, expectedOrigin)) return null;
748
+ if (!isObject(data)) return null;
749
+ switch (data.type) {
750
+ case "cmssy:select":
751
+ return typeof data.blockId === "string" && data.protocolVersion === PROTOCOL_VERSION ? {
752
+ type: "cmssy:select",
753
+ protocolVersion: PROTOCOL_VERSION,
754
+ blockId: data.blockId
755
+ } : null;
756
+ case "cmssy:patch":
757
+ return typeof data.blockId === "string" && isObject(data.content) && data.protocolVersion === PROTOCOL_VERSION ? {
758
+ type: "cmssy:patch",
759
+ blockId: data.blockId,
760
+ content: data.content,
761
+ protocolVersion: PROTOCOL_VERSION,
762
+ ...isObject(data.style) ? { style: data.style } : {},
763
+ ...isObject(data.advanced) ? { advanced: data.advanced } : {},
764
+ ...typeof data.layoutPosition === "string" ? { layoutPosition: data.layoutPosition } : {}
765
+ } : null;
766
+ case "cmssy:parent-ready":
767
+ return data.protocolVersion === PROTOCOL_VERSION ? { type: "cmssy:parent-ready", protocolVersion: PROTOCOL_VERSION } : null;
768
+ case "cmssy:insert":
769
+ return typeof data.blockId === "string" && typeof data.blockType === "string" && isObject(data.content) && typeof data.index === "number" && data.protocolVersion === PROTOCOL_VERSION ? {
770
+ type: "cmssy:insert",
771
+ protocolVersion: PROTOCOL_VERSION,
772
+ blockId: data.blockId,
773
+ blockType: data.blockType,
774
+ content: data.content,
775
+ ...isObject(data.style) ? { style: data.style } : {},
776
+ ...isObject(data.advanced) ? { advanced: data.advanced } : {},
777
+ index: data.index
778
+ } : null;
779
+ case "cmssy:reorder":
780
+ return Array.isArray(data.blockIds) && data.blockIds.every((id) => typeof id === "string") && data.protocolVersion === PROTOCOL_VERSION ? {
781
+ type: "cmssy:reorder",
782
+ protocolVersion: PROTOCOL_VERSION,
783
+ blockIds: data.blockIds
784
+ } : null;
785
+ case "cmssy:remove":
786
+ return typeof data.blockId === "string" && data.protocolVersion === PROTOCOL_VERSION ? {
787
+ type: "cmssy:remove",
788
+ protocolVersion: PROTOCOL_VERSION,
789
+ blockId: data.blockId
790
+ } : null;
791
+ case "cmssy:drag-over":
792
+ return typeof data.y === "number" && data.protocolVersion === PROTOCOL_VERSION ? {
793
+ type: "cmssy:drag-over",
794
+ protocolVersion: PROTOCOL_VERSION,
795
+ y: data.y
796
+ } : null;
797
+ case "cmssy:drag-end":
798
+ return data.protocolVersion === PROTOCOL_VERSION ? { type: "cmssy:drag-end", protocolVersion: PROTOCOL_VERSION } : null;
799
+ default:
800
+ return null;
801
+ }
802
+ }
803
+
720
804
  // src/data/settings-client.ts
721
805
  async function fetchSiteConfig(config, options = {}) {
722
806
  const data = await graphqlRequest(
@@ -824,46 +908,6 @@ async function resolveForms(config, blocks, locale, defaultLocale, options) {
824
908
  return forms;
825
909
  }
826
910
 
827
- // src/data/site-locales.ts
828
- var TTL_MS = 6e4;
829
- var MAX_ENTRIES = 64;
830
- var cache = /* @__PURE__ */ new Map();
831
- async function resolveSiteLocales(config, options) {
832
- const key = `${resolveApiUrl(config.apiUrl)}::${config.org}::${config.workspaceSlug}`;
833
- const cached = cache.get(key);
834
- if (cached && cached.expires > Date.now()) return cached.value;
835
- cache.delete(key);
836
- let value;
837
- try {
838
- const data = await graphqlRequest(
839
- config,
840
- SITE_CONFIG_QUERY,
841
- { workspaceSlug: config.workspaceSlug },
842
- { ...options, public: true, retry: options?.retry ?? {} },
843
- "site config"
844
- );
845
- const siteConfig = data.public?.siteConfig ?? null;
846
- const defaultLocale = siteConfig?.defaultLanguage || "en";
847
- const enabled = siteConfig?.enabledLanguages ?? [];
848
- value = {
849
- defaultLocale,
850
- locales: enabled.length > 0 ? enabled : [defaultLocale]
851
- };
852
- } catch {
853
- value = { defaultLocale: "en", locales: ["en"] };
854
- }
855
- if (cache.size >= MAX_ENTRIES) cache.clear();
856
- cache.set(key, { value, expires: Date.now() + TTL_MS });
857
- return value;
858
- }
859
- function splitLocaleFromPath(path, siteLocales) {
860
- const first = path?.[0];
861
- if (first && first !== siteLocales.defaultLocale && siteLocales.locales.includes(first)) {
862
- return { locale: first, path: path.slice(1) };
863
- }
864
- return { locale: siteLocales.defaultLocale, path };
865
- }
866
-
867
911
  // src/data/localize-href.ts
868
912
  var PROTOCOL_OR_RELATIVE = /^([a-z][a-z0-9+.-]*:|\/\/)/i;
869
913
  function isExternalHref(href) {
package/dist/index.d.cts CHANGED
@@ -191,6 +191,11 @@ interface CmssyServerPageProps {
191
191
  defaultLocale?: string;
192
192
  /** All languages enabled on the workspace; exposed to blocks via context.locale.enabled. */
193
193
  enabledLocales?: string[];
194
+ /**
195
+ * The workspace, so the languages can be looked up when they are not passed.
196
+ * Without it the SDK has to guess, and its guess is "en".
197
+ */
198
+ config?: CmssyClientConfig;
194
199
  /** Form definitions referenced by page blocks, exposed via context.forms. */
195
200
  forms?: Record<string, CmssyFormDefinition>;
196
201
  /** Member auth state, exposed via context.auth. Resolved by createCmssyPage. */
@@ -203,23 +208,29 @@ interface CmssyServerPageProps {
203
208
  * loader server-side before rendering, so it must be rendered in a server
204
209
  * component tree (as `createCmssyPage` does) - not in a client component.
205
210
  */
206
- declare function CmssyServerPage({ page, blocks, locale, defaultLocale, enabledLocales, forms, auth, workspace, }: CmssyServerPageProps): Promise<react_jsx_runtime.JSX.Element | null>;
211
+ declare function CmssyServerPage({ page, blocks, locale: localeProp, defaultLocale: defaultLocaleProp, enabledLocales: enabledLocalesProp, config, forms, auth, workspace, }: CmssyServerPageProps): Promise<react_jsx_runtime.JSX.Element | null>;
207
212
 
208
213
  interface CmssyServerLayoutProps {
209
214
  groups: CmssyLayoutGroup[];
210
215
  blocks: BlockDefinition[];
211
216
  position: string;
217
+ /** The language to render in. Omit it and the workspace's default is used. */
212
218
  locale?: string;
213
219
  defaultLocale?: string;
214
220
  /** All languages enabled on the workspace; exposed to blocks via context.locale.enabled. */
215
221
  enabledLocales?: string[];
222
+ /**
223
+ * The workspace, so the languages can be looked up when they are not passed.
224
+ * Without it the SDK has to guess, and its guess is "en".
225
+ */
226
+ config?: CmssyClientConfig;
216
227
  }
217
228
  /**
218
229
  * Async React Server Component. Like CmssyServerPage it runs each block's
219
230
  * loader server-side before rendering, so a header block can list categories
220
231
  * the same way a page block can. Must be rendered in a server component tree.
221
232
  */
222
- declare function CmssyServerLayout({ groups, blocks, position, locale, defaultLocale, enabledLocales, }: CmssyServerLayoutProps): Promise<react_jsx_runtime.JSX.Element | null>;
233
+ declare function CmssyServerLayout({ groups, blocks, position, locale: localeProp, defaultLocale: defaultLocaleProp, enabledLocales: enabledLocalesProp, config, }: CmssyServerLayoutProps): Promise<react_jsx_runtime.JSX.Element | null>;
223
234
 
224
235
  interface PostTarget {
225
236
  postMessage: (message: unknown, targetOrigin: string) => void;
package/dist/index.d.ts CHANGED
@@ -191,6 +191,11 @@ interface CmssyServerPageProps {
191
191
  defaultLocale?: string;
192
192
  /** All languages enabled on the workspace; exposed to blocks via context.locale.enabled. */
193
193
  enabledLocales?: string[];
194
+ /**
195
+ * The workspace, so the languages can be looked up when they are not passed.
196
+ * Without it the SDK has to guess, and its guess is "en".
197
+ */
198
+ config?: CmssyClientConfig;
194
199
  /** Form definitions referenced by page blocks, exposed via context.forms. */
195
200
  forms?: Record<string, CmssyFormDefinition>;
196
201
  /** Member auth state, exposed via context.auth. Resolved by createCmssyPage. */
@@ -203,23 +208,29 @@ interface CmssyServerPageProps {
203
208
  * loader server-side before rendering, so it must be rendered in a server
204
209
  * component tree (as `createCmssyPage` does) - not in a client component.
205
210
  */
206
- declare function CmssyServerPage({ page, blocks, locale, defaultLocale, enabledLocales, forms, auth, workspace, }: CmssyServerPageProps): Promise<react_jsx_runtime.JSX.Element | null>;
211
+ declare function CmssyServerPage({ page, blocks, locale: localeProp, defaultLocale: defaultLocaleProp, enabledLocales: enabledLocalesProp, config, forms, auth, workspace, }: CmssyServerPageProps): Promise<react_jsx_runtime.JSX.Element | null>;
207
212
 
208
213
  interface CmssyServerLayoutProps {
209
214
  groups: CmssyLayoutGroup[];
210
215
  blocks: BlockDefinition[];
211
216
  position: string;
217
+ /** The language to render in. Omit it and the workspace's default is used. */
212
218
  locale?: string;
213
219
  defaultLocale?: string;
214
220
  /** All languages enabled on the workspace; exposed to blocks via context.locale.enabled. */
215
221
  enabledLocales?: string[];
222
+ /**
223
+ * The workspace, so the languages can be looked up when they are not passed.
224
+ * Without it the SDK has to guess, and its guess is "en".
225
+ */
226
+ config?: CmssyClientConfig;
216
227
  }
217
228
  /**
218
229
  * Async React Server Component. Like CmssyServerPage it runs each block's
219
230
  * loader server-side before rendering, so a header block can list categories
220
231
  * the same way a page block can. Must be rendered in a server component tree.
221
232
  */
222
- declare function CmssyServerLayout({ groups, blocks, position, locale, defaultLocale, enabledLocales, }: CmssyServerLayoutProps): Promise<react_jsx_runtime.JSX.Element | null>;
233
+ declare function CmssyServerLayout({ groups, blocks, position, locale: localeProp, defaultLocale: defaultLocaleProp, enabledLocales: enabledLocalesProp, config, }: CmssyServerLayoutProps): Promise<react_jsx_runtime.JSX.Element | null>;
223
234
 
224
235
  interface PostTarget {
225
236
  postMessage: (message: unknown, targetOrigin: string) => void;
package/dist/index.js CHANGED
@@ -182,163 +182,6 @@ async function resolveBlocks(blocks, loaderMap, locale, defaultLocale, context,
182
182
  })
183
183
  );
184
184
  }
185
- async function CmssyServerPage({
186
- page,
187
- blocks,
188
- locale = "en",
189
- defaultLocale = "en",
190
- enabledLocales,
191
- forms,
192
- auth,
193
- workspace
194
- }) {
195
- if (!page) return null;
196
- const map = buildBlockMap(blocks);
197
- const loaderMap = buildLoaderMap(blocks);
198
- const context = buildBlockContext(
199
- locale,
200
- defaultLocale,
201
- enabledLocales,
202
- false,
203
- forms,
204
- { auth, workspace }
205
- );
206
- const resolved = await resolveBlocks(
207
- page.blocks,
208
- loaderMap,
209
- locale,
210
- defaultLocale,
211
- context,
212
- enabledLocales
213
- );
214
- return /* @__PURE__ */ jsx(Fragment, { children: page.blocks.map(
215
- (block, i) => renderResolvedBlock(block, map, locale, defaultLocale, {
216
- context,
217
- data: resolved[i]?.data,
218
- resolvedContent: resolved[i]?.content,
219
- enabledLocales
220
- })
221
- ) });
222
- }
223
- async function CmssyServerLayout({
224
- groups,
225
- blocks,
226
- position,
227
- locale = "en",
228
- defaultLocale = "en",
229
- enabledLocales
230
- }) {
231
- const group = groups.find((g) => g.position === position);
232
- const layoutBlocks = group ? group.blocks.filter((b) => b.isActive).slice().sort((a, b) => a.order - b.order) : [];
233
- if (layoutBlocks.length === 0) return null;
234
- const map = buildBlockMap(blocks);
235
- const loaderMap = buildLoaderMap(blocks);
236
- const context = buildBlockContext(locale, defaultLocale, enabledLocales);
237
- const resolved = await resolveBlocks(
238
- layoutBlocks,
239
- loaderMap,
240
- locale,
241
- defaultLocale,
242
- context,
243
- enabledLocales
244
- );
245
- return /* @__PURE__ */ jsx(Fragment, { children: layoutBlocks.map(
246
- (block, i) => renderResolvedBlock(block, map, locale, defaultLocale, {
247
- context,
248
- data: resolved[i]?.data,
249
- resolvedContent: resolved[i]?.content,
250
- enabledLocales
251
- })
252
- ) });
253
- }
254
-
255
- // src/bridge/protocol.ts
256
- var PROTOCOL_VERSION = 2;
257
- function isProtocolCompatible(version) {
258
- return version === PROTOCOL_VERSION;
259
- }
260
-
261
- // src/bridge/messages.ts
262
- function normalizeOrigin(origin) {
263
- const trimmed = origin.trim();
264
- if (trimmed === "*") return "*";
265
- try {
266
- return new URL(trimmed).origin;
267
- } catch {
268
- return trimmed;
269
- }
270
- }
271
- function postToEditor(target, editorOrigin, message) {
272
- target.postMessage(message, normalizeOrigin(editorOrigin));
273
- }
274
- function isOriginAllowed(origin, allowed) {
275
- const list = Array.isArray(allowed) ? allowed : [allowed];
276
- const actual = normalizeOrigin(origin);
277
- return list.some((candidate) => {
278
- const expected = normalizeOrigin(candidate);
279
- return expected === "*" || expected === actual;
280
- });
281
- }
282
- function isObject(value) {
283
- return typeof value === "object" && value !== null && !Array.isArray(value);
284
- }
285
- function parseEditorMessage(data, origin, expectedOrigin) {
286
- if (!isOriginAllowed(origin, expectedOrigin)) return null;
287
- if (!isObject(data)) return null;
288
- switch (data.type) {
289
- case "cmssy:select":
290
- return typeof data.blockId === "string" && data.protocolVersion === PROTOCOL_VERSION ? {
291
- type: "cmssy:select",
292
- protocolVersion: PROTOCOL_VERSION,
293
- blockId: data.blockId
294
- } : null;
295
- case "cmssy:patch":
296
- return typeof data.blockId === "string" && isObject(data.content) && data.protocolVersion === PROTOCOL_VERSION ? {
297
- type: "cmssy:patch",
298
- blockId: data.blockId,
299
- content: data.content,
300
- protocolVersion: PROTOCOL_VERSION,
301
- ...isObject(data.style) ? { style: data.style } : {},
302
- ...isObject(data.advanced) ? { advanced: data.advanced } : {},
303
- ...typeof data.layoutPosition === "string" ? { layoutPosition: data.layoutPosition } : {}
304
- } : null;
305
- case "cmssy:parent-ready":
306
- return data.protocolVersion === PROTOCOL_VERSION ? { type: "cmssy:parent-ready", protocolVersion: PROTOCOL_VERSION } : null;
307
- case "cmssy:insert":
308
- return typeof data.blockId === "string" && typeof data.blockType === "string" && isObject(data.content) && typeof data.index === "number" && data.protocolVersion === PROTOCOL_VERSION ? {
309
- type: "cmssy:insert",
310
- protocolVersion: PROTOCOL_VERSION,
311
- blockId: data.blockId,
312
- blockType: data.blockType,
313
- content: data.content,
314
- ...isObject(data.style) ? { style: data.style } : {},
315
- ...isObject(data.advanced) ? { advanced: data.advanced } : {},
316
- index: data.index
317
- } : null;
318
- case "cmssy:reorder":
319
- return Array.isArray(data.blockIds) && data.blockIds.every((id) => typeof id === "string") && data.protocolVersion === PROTOCOL_VERSION ? {
320
- type: "cmssy:reorder",
321
- protocolVersion: PROTOCOL_VERSION,
322
- blockIds: data.blockIds
323
- } : null;
324
- case "cmssy:remove":
325
- return typeof data.blockId === "string" && data.protocolVersion === PROTOCOL_VERSION ? {
326
- type: "cmssy:remove",
327
- protocolVersion: PROTOCOL_VERSION,
328
- blockId: data.blockId
329
- } : null;
330
- case "cmssy:drag-over":
331
- return typeof data.y === "number" && data.protocolVersion === PROTOCOL_VERSION ? {
332
- type: "cmssy:drag-over",
333
- protocolVersion: PROTOCOL_VERSION,
334
- y: data.y
335
- } : null;
336
- case "cmssy:drag-end":
337
- return data.protocolVersion === PROTOCOL_VERSION ? { type: "cmssy:drag-end", protocolVersion: PROTOCOL_VERSION } : null;
338
- default:
339
- return null;
340
- }
341
- }
342
185
 
343
186
  // src/data/http.ts
344
187
  var CmssyRequestError = class extends Error {
@@ -715,6 +558,247 @@ var SUBMIT_FORM_MUTATION = `mutation SubmitForm($formId: ID!, $input: SubmitForm
715
558
  }
716
559
  }`;
717
560
 
561
+ // src/data/site-locales.ts
562
+ var TTL_MS = 6e4;
563
+ var MAX_ENTRIES = 64;
564
+ var cache = /* @__PURE__ */ new Map();
565
+ async function resolveSiteLocales(config, options) {
566
+ const key = `${resolveApiUrl(config.apiUrl)}::${config.org}::${config.workspaceSlug}`;
567
+ const cached = cache.get(key);
568
+ if (cached && cached.expires > Date.now()) return cached.value;
569
+ cache.delete(key);
570
+ let value;
571
+ try {
572
+ const data = await graphqlRequest(
573
+ config,
574
+ SITE_CONFIG_QUERY,
575
+ { workspaceSlug: config.workspaceSlug },
576
+ { ...options, public: true, retry: options?.retry ?? {} },
577
+ "site config"
578
+ );
579
+ const siteConfig = data.public?.siteConfig ?? null;
580
+ const defaultLocale = siteConfig?.defaultLanguage || "en";
581
+ const enabled = siteConfig?.enabledLanguages ?? [];
582
+ value = {
583
+ defaultLocale,
584
+ locales: enabled.length > 0 ? enabled : [defaultLocale]
585
+ };
586
+ } catch {
587
+ value = { defaultLocale: "en", locales: ["en"] };
588
+ }
589
+ if (cache.size >= MAX_ENTRIES) cache.clear();
590
+ cache.set(key, { value, expires: Date.now() + TTL_MS });
591
+ return value;
592
+ }
593
+ function splitLocaleFromPath(path, siteLocales) {
594
+ const first = path?.[0];
595
+ if (first && first !== siteLocales.defaultLocale && siteLocales.locales.includes(first)) {
596
+ return { locale: first, path: path.slice(1) };
597
+ }
598
+ return { locale: siteLocales.defaultLocale, path };
599
+ }
600
+
601
+ // src/components/resolve-render-locale.ts
602
+ var warned2 = false;
603
+ async function resolveRenderLocale({
604
+ locale,
605
+ defaultLocale,
606
+ enabledLocales,
607
+ config
608
+ }) {
609
+ if (locale && defaultLocale) return { locale, defaultLocale, enabledLocales };
610
+ if (config) {
611
+ const site = await resolveSiteLocales(config);
612
+ return {
613
+ locale: locale ?? defaultLocale ?? site.defaultLocale,
614
+ defaultLocale: defaultLocale ?? site.defaultLocale,
615
+ enabledLocales: enabledLocales ?? site.locales
616
+ };
617
+ }
618
+ if (process.env.NODE_ENV !== "production" && !warned2) {
619
+ warned2 = true;
620
+ console.warn(
621
+ '[cmssy] Rendering in "en": no locale was passed and no config was given to look the workspace\'s default language up. Pass `locale` (from the route) or `config`, or a workspace whose default language is not English will render its chrome in the wrong one.'
622
+ );
623
+ }
624
+ return {
625
+ locale: locale ?? defaultLocale ?? "en",
626
+ defaultLocale: defaultLocale ?? "en",
627
+ enabledLocales
628
+ };
629
+ }
630
+ async function CmssyServerPage({
631
+ page,
632
+ blocks,
633
+ locale: localeProp,
634
+ defaultLocale: defaultLocaleProp,
635
+ enabledLocales: enabledLocalesProp,
636
+ config,
637
+ forms,
638
+ auth,
639
+ workspace
640
+ }) {
641
+ if (!page) return null;
642
+ const { locale, defaultLocale, enabledLocales } = await resolveRenderLocale({
643
+ locale: localeProp,
644
+ defaultLocale: defaultLocaleProp,
645
+ enabledLocales: enabledLocalesProp,
646
+ config
647
+ });
648
+ const map = buildBlockMap(blocks);
649
+ const loaderMap = buildLoaderMap(blocks);
650
+ const context = buildBlockContext(
651
+ locale,
652
+ defaultLocale,
653
+ enabledLocales,
654
+ false,
655
+ forms,
656
+ { auth, workspace }
657
+ );
658
+ const resolved = await resolveBlocks(
659
+ page.blocks,
660
+ loaderMap,
661
+ locale,
662
+ defaultLocale,
663
+ context,
664
+ enabledLocales
665
+ );
666
+ return /* @__PURE__ */ jsx(Fragment, { children: page.blocks.map(
667
+ (block, i) => renderResolvedBlock(block, map, locale, defaultLocale, {
668
+ context,
669
+ data: resolved[i]?.data,
670
+ resolvedContent: resolved[i]?.content,
671
+ enabledLocales
672
+ })
673
+ ) });
674
+ }
675
+ async function CmssyServerLayout({
676
+ groups,
677
+ blocks,
678
+ position,
679
+ locale: localeProp,
680
+ defaultLocale: defaultLocaleProp,
681
+ enabledLocales: enabledLocalesProp,
682
+ config
683
+ }) {
684
+ const { locale, defaultLocale, enabledLocales } = await resolveRenderLocale({
685
+ locale: localeProp,
686
+ defaultLocale: defaultLocaleProp,
687
+ enabledLocales: enabledLocalesProp,
688
+ config
689
+ });
690
+ const group = groups.find((g) => g.position === position);
691
+ const layoutBlocks = group ? group.blocks.filter((b) => b.isActive).slice().sort((a, b) => a.order - b.order) : [];
692
+ if (layoutBlocks.length === 0) return null;
693
+ const map = buildBlockMap(blocks);
694
+ const loaderMap = buildLoaderMap(blocks);
695
+ const context = buildBlockContext(locale, defaultLocale, enabledLocales);
696
+ const resolved = await resolveBlocks(
697
+ layoutBlocks,
698
+ loaderMap,
699
+ locale,
700
+ defaultLocale,
701
+ context,
702
+ enabledLocales
703
+ );
704
+ return /* @__PURE__ */ jsx(Fragment, { children: layoutBlocks.map(
705
+ (block, i) => renderResolvedBlock(block, map, locale, defaultLocale, {
706
+ context,
707
+ data: resolved[i]?.data,
708
+ resolvedContent: resolved[i]?.content,
709
+ enabledLocales
710
+ })
711
+ ) });
712
+ }
713
+
714
+ // src/bridge/protocol.ts
715
+ var PROTOCOL_VERSION = 2;
716
+ function isProtocolCompatible(version) {
717
+ return version === PROTOCOL_VERSION;
718
+ }
719
+
720
+ // src/bridge/messages.ts
721
+ function normalizeOrigin(origin) {
722
+ const trimmed = origin.trim();
723
+ if (trimmed === "*") return "*";
724
+ try {
725
+ return new URL(trimmed).origin;
726
+ } catch {
727
+ return trimmed;
728
+ }
729
+ }
730
+ function postToEditor(target, editorOrigin, message) {
731
+ target.postMessage(message, normalizeOrigin(editorOrigin));
732
+ }
733
+ function isOriginAllowed(origin, allowed) {
734
+ const list = Array.isArray(allowed) ? allowed : [allowed];
735
+ const actual = normalizeOrigin(origin);
736
+ return list.some((candidate) => {
737
+ const expected = normalizeOrigin(candidate);
738
+ return expected === "*" || expected === actual;
739
+ });
740
+ }
741
+ function isObject(value) {
742
+ return typeof value === "object" && value !== null && !Array.isArray(value);
743
+ }
744
+ function parseEditorMessage(data, origin, expectedOrigin) {
745
+ if (!isOriginAllowed(origin, expectedOrigin)) return null;
746
+ if (!isObject(data)) return null;
747
+ switch (data.type) {
748
+ case "cmssy:select":
749
+ return typeof data.blockId === "string" && data.protocolVersion === PROTOCOL_VERSION ? {
750
+ type: "cmssy:select",
751
+ protocolVersion: PROTOCOL_VERSION,
752
+ blockId: data.blockId
753
+ } : null;
754
+ case "cmssy:patch":
755
+ return typeof data.blockId === "string" && isObject(data.content) && data.protocolVersion === PROTOCOL_VERSION ? {
756
+ type: "cmssy:patch",
757
+ blockId: data.blockId,
758
+ content: data.content,
759
+ protocolVersion: PROTOCOL_VERSION,
760
+ ...isObject(data.style) ? { style: data.style } : {},
761
+ ...isObject(data.advanced) ? { advanced: data.advanced } : {},
762
+ ...typeof data.layoutPosition === "string" ? { layoutPosition: data.layoutPosition } : {}
763
+ } : null;
764
+ case "cmssy:parent-ready":
765
+ return data.protocolVersion === PROTOCOL_VERSION ? { type: "cmssy:parent-ready", protocolVersion: PROTOCOL_VERSION } : null;
766
+ case "cmssy:insert":
767
+ return typeof data.blockId === "string" && typeof data.blockType === "string" && isObject(data.content) && typeof data.index === "number" && data.protocolVersion === PROTOCOL_VERSION ? {
768
+ type: "cmssy:insert",
769
+ protocolVersion: PROTOCOL_VERSION,
770
+ blockId: data.blockId,
771
+ blockType: data.blockType,
772
+ content: data.content,
773
+ ...isObject(data.style) ? { style: data.style } : {},
774
+ ...isObject(data.advanced) ? { advanced: data.advanced } : {},
775
+ index: data.index
776
+ } : null;
777
+ case "cmssy:reorder":
778
+ return Array.isArray(data.blockIds) && data.blockIds.every((id) => typeof id === "string") && data.protocolVersion === PROTOCOL_VERSION ? {
779
+ type: "cmssy:reorder",
780
+ protocolVersion: PROTOCOL_VERSION,
781
+ blockIds: data.blockIds
782
+ } : null;
783
+ case "cmssy:remove":
784
+ return typeof data.blockId === "string" && data.protocolVersion === PROTOCOL_VERSION ? {
785
+ type: "cmssy:remove",
786
+ protocolVersion: PROTOCOL_VERSION,
787
+ blockId: data.blockId
788
+ } : null;
789
+ case "cmssy:drag-over":
790
+ return typeof data.y === "number" && data.protocolVersion === PROTOCOL_VERSION ? {
791
+ type: "cmssy:drag-over",
792
+ protocolVersion: PROTOCOL_VERSION,
793
+ y: data.y
794
+ } : null;
795
+ case "cmssy:drag-end":
796
+ return data.protocolVersion === PROTOCOL_VERSION ? { type: "cmssy:drag-end", protocolVersion: PROTOCOL_VERSION } : null;
797
+ default:
798
+ return null;
799
+ }
800
+ }
801
+
718
802
  // src/data/settings-client.ts
719
803
  async function fetchSiteConfig(config, options = {}) {
720
804
  const data = await graphqlRequest(
@@ -822,46 +906,6 @@ async function resolveForms(config, blocks, locale, defaultLocale, options) {
822
906
  return forms;
823
907
  }
824
908
 
825
- // src/data/site-locales.ts
826
- var TTL_MS = 6e4;
827
- var MAX_ENTRIES = 64;
828
- var cache = /* @__PURE__ */ new Map();
829
- async function resolveSiteLocales(config, options) {
830
- const key = `${resolveApiUrl(config.apiUrl)}::${config.org}::${config.workspaceSlug}`;
831
- const cached = cache.get(key);
832
- if (cached && cached.expires > Date.now()) return cached.value;
833
- cache.delete(key);
834
- let value;
835
- try {
836
- const data = await graphqlRequest(
837
- config,
838
- SITE_CONFIG_QUERY,
839
- { workspaceSlug: config.workspaceSlug },
840
- { ...options, public: true, retry: options?.retry ?? {} },
841
- "site config"
842
- );
843
- const siteConfig = data.public?.siteConfig ?? null;
844
- const defaultLocale = siteConfig?.defaultLanguage || "en";
845
- const enabled = siteConfig?.enabledLanguages ?? [];
846
- value = {
847
- defaultLocale,
848
- locales: enabled.length > 0 ? enabled : [defaultLocale]
849
- };
850
- } catch {
851
- value = { defaultLocale: "en", locales: ["en"] };
852
- }
853
- if (cache.size >= MAX_ENTRIES) cache.clear();
854
- cache.set(key, { value, expires: Date.now() + TTL_MS });
855
- return value;
856
- }
857
- function splitLocaleFromPath(path, siteLocales) {
858
- const first = path?.[0];
859
- if (first && first !== siteLocales.defaultLocale && siteLocales.locales.includes(first)) {
860
- return { locale: first, path: path.slice(1) };
861
- }
862
- return { locale: siteLocales.defaultLocale, path };
863
- }
864
-
865
909
  // src/data/localize-href.ts
866
910
  var PROTOCOL_OR_RELATIVE = /^([a-z][a-z0-9+.-]*:|\/\/)/i;
867
911
  function isExternalHref(href) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/react",
3
- "version": "4.4.0",
3
+ "version": "4.6.0",
4
4
  "description": "React blocks, renderers, data client and editor bridge for cmssy headless sites",
5
5
  "keywords": [
6
6
  "cmssy",