@blinkk/root-cms 3.0.5 → 3.1.1

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.
Files changed (155) hide show
  1. package/README.md +77 -0
  2. package/dist/app.js +7 -4
  3. package/dist/browser-client/browser-client.d.ts +148 -0
  4. package/dist/browser-client/embed-window.d.ts +66 -0
  5. package/dist/browser-client/emitter.d.ts +11 -0
  6. package/dist/browser-client.js +377 -0
  7. package/dist/{chunk-RXWDB3K4.js → chunk-4VZTYUQ7.js} +1 -1
  8. package/dist/{chunk-N4K6ICFR.js → chunk-AFBSITEO.js} +4 -6
  9. package/dist/{chunk-PNLLZXVY.js → chunk-AJDMNTZQ.js} +2 -2
  10. package/dist/{chunk-VSPPZPMP.js → chunk-GXV4UQ6Z.js} +15 -11
  11. package/dist/chunk-J72JK4KR.js +8 -0
  12. package/dist/cli.js +2 -1
  13. package/dist/client.js +2 -1
  14. package/dist/core/client.d.ts +5 -0
  15. package/dist/core/plugin.d.ts +17 -0
  16. package/dist/core.js +4 -2
  17. package/dist/functions.js +4 -3
  18. package/dist/plugin.js +16 -4
  19. package/dist/shared/embed-protocol.d.ts +72 -0
  20. package/dist/ui/AIPage-EL6DQYII.js +1 -0
  21. package/dist/ui/AssetsPage-5S6PNMF6.js +1 -0
  22. package/dist/ui/CollectionPage-CU3VDEXA.js +1 -0
  23. package/dist/ui/ComparePage-FTZLYO2O.js +1 -0
  24. package/dist/ui/DataPage-XIXFKXDN.js +1 -0
  25. package/dist/ui/DataSourcePage-ORIEQH3Y.js +1 -0
  26. package/dist/ui/DocTranslationsPage-4OVBIPSG.js +1 -0
  27. package/dist/ui/DocumentPage-3JPFJF3E.js +1 -0
  28. package/dist/ui/EditDataSourcePage-HIGZZQJC.js +1 -0
  29. package/dist/ui/EditReleasePage-KUHBU2HQ.js +1 -0
  30. package/dist/ui/EmbeddedAIPage-CJWZE5FC.js +1 -0
  31. package/dist/ui/EmbeddedDocumentPage-6XQHYIAO.js +1 -0
  32. package/dist/ui/{ImageEditorDialog-4H665KK2.js → ImageEditorDialog-GGS4VFEA.js} +2 -2
  33. package/dist/ui/LogsPage-4DVGYXZY.js +1 -0
  34. package/dist/ui/NewDataSourcePage-7TUUE6ZL.js +1 -0
  35. package/dist/ui/NewReleasePage-6UT36HDW.js +1 -0
  36. package/dist/ui/NotFoundPage-AW6UC6II.js +1 -0
  37. package/dist/ui/ProjectPage-US5FJGFK.js +1 -0
  38. package/dist/ui/ReleasePage-W2R3DIFX.js +1 -0
  39. package/dist/ui/ReleasesPage-HBBUGE7D.js +1 -0
  40. package/dist/ui/SettingsPage-7JQQILUV.js +1 -0
  41. package/dist/ui/SidebarToolsPage-MIYXKHF2.js +1 -0
  42. package/dist/ui/TaskPage-JYUQEGRB.js +1 -0
  43. package/dist/ui/TasksPage-4VFQF243.js +1 -0
  44. package/dist/ui/TranslationsArbPage-AJLM5VSD.js +2 -0
  45. package/dist/ui/TranslationsEditPage-RTTGTIGI.js +1 -0
  46. package/dist/ui/{TranslationsPage-WIDVSTYF.js → TranslationsPage-HPYAJQ4Q.js} +31 -31
  47. package/dist/ui/{ai-tools-5E5ELLZX.js → ai-tools-4DWMFQS6.js} +1 -1
  48. package/dist/ui/{chunk-JYFLU7DM.js → chunk-2KSMCQ6H.js} +2 -2
  49. package/dist/ui/{chunk-HKINZLMW.js → chunk-344GZOGG.js} +45 -45
  50. package/dist/ui/{chunk-CGCKJFL4.js → chunk-3E6CASIN.js} +1 -1
  51. package/dist/ui/chunk-3T4ZWQR4.js +7 -0
  52. package/dist/ui/chunk-4ISFTE2G.js +5 -0
  53. package/dist/ui/chunk-5AVRMJVW.js +1 -0
  54. package/dist/ui/chunk-764W6634.js +1 -0
  55. package/dist/ui/chunk-76ZGLKLT.js +1 -0
  56. package/dist/ui/{chunk-SKEP5WYH.js → chunk-7SK47HHZ.js} +3 -3
  57. package/dist/ui/chunk-AZCGXQTL.js +1 -0
  58. package/dist/ui/{chunk-W3BJ2EZX.js → chunk-B6M55OFE.js} +1 -1
  59. package/dist/ui/{chunk-KYDOSLTO.js → chunk-CAY5NM4Z.js} +1 -1
  60. package/dist/ui/{chunk-4KE2SZKH.js → chunk-D3UIZHRK.js} +1 -1
  61. package/dist/ui/chunk-E76ECIOX.js +81 -0
  62. package/dist/ui/chunk-FBSBL476.js +1 -0
  63. package/dist/ui/chunk-FFHV5S6A.js +1 -0
  64. package/dist/ui/{chunk-X2WHBGTP.js → chunk-FJLNXXE3.js} +1 -1
  65. package/dist/ui/chunk-FSVNOZ6F.js +1 -0
  66. package/dist/ui/{chunk-BZIRGB4W.js → chunk-GDXNSFLE.js} +1 -1
  67. package/dist/ui/{chunk-C5NH23G4.js → chunk-GQ5IRK43.js} +1 -1
  68. package/dist/ui/{chunk-DEGMKEHP.js → chunk-GU24U5RM.js} +1 -1
  69. package/dist/ui/{chunk-HRGYNHZ3.js → chunk-HARRKJFZ.js} +1 -1
  70. package/dist/ui/{chunk-PHBMVORG.js → chunk-HO3W4RVG.js} +1 -1
  71. package/dist/ui/{chunk-62UWGWON.js → chunk-J5E457OI.js} +1 -1
  72. package/dist/ui/{chunk-W3HIZQ4Z.js → chunk-JS6TK4OP.js} +1 -1
  73. package/dist/ui/{chunk-54DVDYU6.js → chunk-JTIQBW33.js} +1 -1
  74. package/dist/ui/{chunk-ORXEMIQC.js → chunk-KRNQ6L35.js} +1 -1
  75. package/dist/ui/chunk-LROR6N3B.js +7 -0
  76. package/dist/ui/{chunk-NZHF733K.js.LEGAL.txt → chunk-LROR6N3B.js.LEGAL.txt} +6 -1
  77. package/dist/ui/{chunk-E2RCCK2T.js → chunk-M3T56ZRP.js} +1 -1
  78. package/dist/ui/chunk-MXGHX55S.js +141 -0
  79. package/dist/ui/{chunk-KFAZBEPV.js → chunk-MZTOPPWQ.js} +1 -1
  80. package/dist/ui/{chunk-JHBAMPGB.js → chunk-N3JSLTMV.js} +1 -1
  81. package/dist/ui/{chunk-L4RHGQUG.js → chunk-NSOTG64N.js} +1 -1
  82. package/dist/ui/{chunk-HLMBCVRX.js → chunk-O5P3CPJ4.js} +1 -1
  83. package/dist/ui/chunk-OWQL5DVE.js +1 -0
  84. package/dist/ui/{chunk-P3NETZZP.js → chunk-OXVYTSIZ.js} +1 -1
  85. package/dist/ui/chunk-P2Z66DHV.js +1 -0
  86. package/dist/ui/chunk-QEE7XS6O.js +1 -0
  87. package/dist/ui/chunk-QHJOTXYB.js +1 -0
  88. package/dist/ui/{chunk-3IYZCUPI.js → chunk-R6B3W5AJ.js} +1 -1
  89. package/dist/ui/{chunk-QGBR3J2Y.js → chunk-RAXWL656.js} +1 -1
  90. package/dist/ui/chunk-SRX7EWL4.js +1 -0
  91. package/dist/ui/chunk-TDUBSLEC.js +1 -0
  92. package/dist/ui/chunk-TFB3VGWP.js +1 -0
  93. package/dist/ui/{chunk-767BAQ53.js → chunk-U7PWJV6F.js} +1 -1
  94. package/dist/ui/{chunk-PWKJDYE2.js → chunk-UUVAD4U4.js} +1 -1
  95. package/dist/ui/chunk-V4CTOC6E.js +1 -0
  96. package/dist/ui/{chunk-EGEB6N4J.js → chunk-VD6G5NV4.js} +11 -11
  97. package/dist/ui/chunk-W5DOO6ID.js +1 -0
  98. package/dist/ui/{chunk-HHMG665Z.js → chunk-WFQU7V3W.js} +1 -1
  99. package/dist/ui/{chunk-SQRKKWRG.js → chunk-WKF7CSWS.js} +1 -1
  100. package/dist/ui/{chunk-4W73RW5I.js → chunk-WXT7LTQM.js} +1 -1
  101. package/dist/ui/{chunk-VMT7JFAM.js → chunk-XT5FNJCX.js} +1 -1
  102. package/dist/ui/chunk-YLSRRNSE.js +1 -0
  103. package/dist/ui/chunk-ZN2Y5OUF.js +3 -0
  104. package/dist/ui/gcs-ECSDFCIU.js +1 -0
  105. package/dist/ui/ui.css +1 -1
  106. package/dist/ui/ui.js +1 -1
  107. package/package.json +14 -10
  108. package/dist/ui/AIPage-F3NNYM5E.js +0 -1
  109. package/dist/ui/AssetsPage-DZQHAENZ.js +0 -1
  110. package/dist/ui/CollectionPage-WVVG62NR.js +0 -1
  111. package/dist/ui/ComparePage-CRDHP45W.js +0 -1
  112. package/dist/ui/DataPage-WMLBCPGF.js +0 -1
  113. package/dist/ui/DataSourcePage-POZAQFPU.js +0 -1
  114. package/dist/ui/DocTranslationsPage-UR5D2RF2.js +0 -1
  115. package/dist/ui/DocumentPage-SQ7ROBMC.js +0 -1
  116. package/dist/ui/EditDataSourcePage-PUA2AJ4C.js +0 -1
  117. package/dist/ui/EditReleasePage-CENP46EO.js +0 -1
  118. package/dist/ui/LogsPage-FZRN6MBJ.js +0 -1
  119. package/dist/ui/NewDataSourcePage-FTILYFJY.js +0 -1
  120. package/dist/ui/NewReleasePage-MHJC66KK.js +0 -1
  121. package/dist/ui/NotFoundPage-EP2OE4JV.js +0 -1
  122. package/dist/ui/ProjectPage-6J7QCG75.js +0 -1
  123. package/dist/ui/ReleasePage-IZ3DNGT4.js +0 -1
  124. package/dist/ui/ReleasesPage-RWFOJ5VG.js +0 -1
  125. package/dist/ui/SettingsPage-M4SIDBTI.js +0 -1
  126. package/dist/ui/SidebarToolsPage-GAX3XJ7W.js +0 -1
  127. package/dist/ui/TaskPage-UCATTNZ5.js +0 -1
  128. package/dist/ui/TasksPage-4AQLQ4IT.js +0 -1
  129. package/dist/ui/TranslationsArbPage-JGQGATCS.js +0 -2
  130. package/dist/ui/TranslationsEditPage-7AGZGHKS.js +0 -1
  131. package/dist/ui/chunk-7MTDTPYB.js +0 -1
  132. package/dist/ui/chunk-7UIUAHC4.js +0 -1
  133. package/dist/ui/chunk-AEWL2RWR.js +0 -141
  134. package/dist/ui/chunk-AFWBSIPL.js +0 -1
  135. package/dist/ui/chunk-AL3TN2KI.js +0 -1
  136. package/dist/ui/chunk-DUJYIOLE.js +0 -5
  137. package/dist/ui/chunk-I6ICVKEC.js +0 -1
  138. package/dist/ui/chunk-JKCZGT6B.js +0 -1
  139. package/dist/ui/chunk-JZFTZ4X4.js +0 -87
  140. package/dist/ui/chunk-KKEWCXVV.js +0 -1
  141. package/dist/ui/chunk-NZHF733K.js +0 -7
  142. package/dist/ui/chunk-ONTGW2VA.js +0 -1
  143. package/dist/ui/chunk-P4U2LUTF.js +0 -1
  144. package/dist/ui/chunk-PJA7YP4A.js +0 -1
  145. package/dist/ui/chunk-RSVO46S3.js +0 -1
  146. package/dist/ui/chunk-TJBVMFNQ.js +0 -1
  147. package/dist/ui/chunk-XOQ3KDSR.js +0 -3
  148. package/dist/ui/chunk-YFA6U2CV.js +0 -1
  149. package/dist/ui/gcs-L33FIRZU.js +0 -1
  150. /package/dist/ui/{ImageEditorDialog-4H665KK2.js.LEGAL.txt → ImageEditorDialog-GGS4VFEA.js.LEGAL.txt} +0 -0
  151. /package/dist/ui/{chunk-SKEP5WYH.js.LEGAL.txt → chunk-7SK47HHZ.js.LEGAL.txt} +0 -0
  152. /package/dist/ui/{chunk-JZFTZ4X4.js.LEGAL.txt → chunk-E76ECIOX.js.LEGAL.txt} +0 -0
  153. /package/dist/ui/{chunk-AEWL2RWR.js.LEGAL.txt → chunk-MXGHX55S.js.LEGAL.txt} +0 -0
  154. /package/dist/ui/{chunk-EGEB6N4J.js.LEGAL.txt → chunk-VD6G5NV4.js.LEGAL.txt} +0 -0
  155. /package/dist/ui/{prerender-Q4YSDCPB.js → prerender-M47IWV5Q.js} +0 -0
@@ -0,0 +1,377 @@
1
+ import {
2
+ normalizeSlug
3
+ } from "./chunk-J72JK4KR.js";
4
+ import "./chunk-MLKGABMK.js";
5
+
6
+ // shared/embed-protocol.ts
7
+ var SaveState = /* @__PURE__ */ ((SaveState3) => {
8
+ SaveState3["NO_CHANGES"] = "NO_CHANGES";
9
+ SaveState3["UPDATES_PENDING"] = "UPDATE_PENDING";
10
+ SaveState3["SAVING"] = "SAVING";
11
+ SaveState3["SAVED"] = "SAVED";
12
+ SaveState3["ERROR"] = "ERROR";
13
+ return SaveState3;
14
+ })(SaveState || {});
15
+ var EMBED_MESSAGE_TYPES = ["ready", "saved", "published", "error"];
16
+ function isRootEmbedMessage(data) {
17
+ const root = data?.root;
18
+ return typeof root === "object" && root !== null && EMBED_MESSAGE_TYPES.includes(root.type);
19
+ }
20
+ function isHighlightNodeMessage(data) {
21
+ const req = data?.highlightNode;
22
+ return typeof req === "object" && req !== null && (typeof req.deepKey === "string" || req.deepKey === null);
23
+ }
24
+
25
+ // browser-client/emitter.ts
26
+ var Emitter = class {
27
+ constructor() {
28
+ this.listeners = /* @__PURE__ */ new Map();
29
+ }
30
+ /** Subscribes to an event. Returns an unsubscribe function. */
31
+ on(type, cb) {
32
+ let set = this.listeners.get(type);
33
+ if (!set) {
34
+ set = /* @__PURE__ */ new Set();
35
+ this.listeners.set(type, set);
36
+ }
37
+ set.add(cb);
38
+ return () => this.off(type, cb);
39
+ }
40
+ off(type, cb) {
41
+ this.listeners.get(type)?.delete(cb);
42
+ }
43
+ emit(type, payload) {
44
+ this.listeners.get(type)?.forEach((cb) => cb(payload));
45
+ }
46
+ removeAll() {
47
+ this.listeners.clear();
48
+ }
49
+ };
50
+
51
+ // browser-client/embed-window.ts
52
+ var POPUP_POLL_INTERVAL = 500;
53
+ var DEFAULT_POPUP_WIDTH = 480;
54
+ var DEFAULT_POPUP_HEIGHT = 720;
55
+ var EmbedWindow = class {
56
+ constructor(url, cmsOrigin, options) {
57
+ /** The iframe element (iframe mode only). */
58
+ this.element = null;
59
+ /** The pop-up window (popup mode only). */
60
+ this.window = null;
61
+ this.emitter = new Emitter();
62
+ this.ready = false;
63
+ this.closed = false;
64
+ this.outbox = [];
65
+ this.pollTimer = null;
66
+ this.onMessage = (event) => {
67
+ if (event.origin !== this.cmsOrigin) {
68
+ return;
69
+ }
70
+ if (!event.source || event.source !== this.contentWindow()) {
71
+ return;
72
+ }
73
+ if (!isRootEmbedMessage(event.data)) {
74
+ return;
75
+ }
76
+ const root = event.data.root;
77
+ if (root.type === "ready") {
78
+ this.ready = true;
79
+ const outbox = this.outbox;
80
+ this.outbox = [];
81
+ outbox.forEach((message) => this.post(message));
82
+ }
83
+ this.emitter.emit(root.type, root);
84
+ };
85
+ this.url = url;
86
+ this.cmsOrigin = cmsOrigin;
87
+ window.addEventListener("message", this.onMessage);
88
+ const mode = options?.mode || "popup";
89
+ if (mode === "iframe") {
90
+ if (!options?.container) {
91
+ throw new Error('`container` is required when mode is "iframe"');
92
+ }
93
+ const iframe = document.createElement("iframe");
94
+ iframe.className = "root-cms-embed";
95
+ iframe.src = url;
96
+ options.container.appendChild(iframe);
97
+ this.element = iframe;
98
+ } else {
99
+ const width = options?.width ?? DEFAULT_POPUP_WIDTH;
100
+ const height = options?.height ?? DEFAULT_POPUP_HEIGHT;
101
+ const popup = window.open(
102
+ url,
103
+ "_blank",
104
+ `popup,width=${width},height=${height}`
105
+ );
106
+ if (!popup) {
107
+ queueMicrotask(() => {
108
+ this.emitter.emit("error", {
109
+ error: "popup blocked (open the editor from a user gesture, e.g. a click handler)"
110
+ });
111
+ this.close();
112
+ });
113
+ } else {
114
+ this.window = popup;
115
+ this.pollTimer = setInterval(() => {
116
+ if (popup.closed) {
117
+ this.close();
118
+ }
119
+ }, POPUP_POLL_INTERVAL);
120
+ }
121
+ }
122
+ }
123
+ on(type, cb) {
124
+ return this.emitter.on(type, cb);
125
+ }
126
+ /**
127
+ * Posts a message to the embedded window, targeted at the CMS origin.
128
+ * Messages are buffered until the embedded page signals `ready`.
129
+ */
130
+ post(message) {
131
+ if (this.closed) {
132
+ return;
133
+ }
134
+ if (!this.ready) {
135
+ this.outbox.push(message);
136
+ return;
137
+ }
138
+ this.contentWindow()?.postMessage(message, this.cmsOrigin);
139
+ }
140
+ /** Reloads the embedded page. */
141
+ reload() {
142
+ if (this.closed) {
143
+ return;
144
+ }
145
+ this.ready = false;
146
+ if (this.element) {
147
+ this.element.src = this.url;
148
+ } else if (this.window) {
149
+ this.window.location.href = this.url;
150
+ }
151
+ }
152
+ /** Closes the embedded window. Safe to call multiple times. */
153
+ close() {
154
+ if (this.closed) {
155
+ return;
156
+ }
157
+ this.closed = true;
158
+ window.removeEventListener("message", this.onMessage);
159
+ if (this.pollTimer !== null) {
160
+ clearInterval(this.pollTimer);
161
+ this.pollTimer = null;
162
+ }
163
+ this.element?.remove();
164
+ if (this.window && !this.window.closed) {
165
+ this.window.close();
166
+ }
167
+ this.emitter.emit("close", void 0);
168
+ this.emitter.removeAll();
169
+ }
170
+ contentWindow() {
171
+ return this.element ? this.element.contentWindow : this.window;
172
+ }
173
+ };
174
+
175
+ // browser-client/browser-client.ts
176
+ function isDotSegment(segment) {
177
+ return segment === "." || segment === "..";
178
+ }
179
+ var EmbeddedEditor = class {
180
+ constructor(docId, embedWindow) {
181
+ this.docId = docId;
182
+ this.embedWindow = embedWindow;
183
+ }
184
+ /** The iframe element (iframe mode only). */
185
+ get element() {
186
+ return this.embedWindow.element;
187
+ }
188
+ /** The pop-up window (popup mode only). */
189
+ get window() {
190
+ return this.embedWindow.window;
191
+ }
192
+ /** Subscribes to an editor event. Returns an unsubscribe function. */
193
+ on(type, cb) {
194
+ return this.embedWindow.on(type, cb);
195
+ }
196
+ /** Scrolls the editor to (focuses) a field, e.g. `hero.title`. */
197
+ focusField(deepKey) {
198
+ const message = { scrollToDeeplink: { deepKey } };
199
+ this.embedWindow.post(message);
200
+ }
201
+ /** Reloads the embedded editor. */
202
+ reload() {
203
+ this.embedWindow.reload();
204
+ }
205
+ /** Closes the embedded editor, discarding any unsaved changes. */
206
+ close() {
207
+ this.embedWindow.close();
208
+ }
209
+ /** Closes the embedded editor and reloads the host page. */
210
+ closeAndReload() {
211
+ this.close();
212
+ window.location.reload();
213
+ }
214
+ };
215
+ var EmbeddedAI = class {
216
+ constructor(embedWindow) {
217
+ this.embedWindow = embedWindow;
218
+ }
219
+ /** The iframe element (iframe mode only). */
220
+ get element() {
221
+ return this.embedWindow.element;
222
+ }
223
+ /** The pop-up window (popup mode only). */
224
+ get window() {
225
+ return this.embedWindow.window;
226
+ }
227
+ /** Subscribes to an event. Returns an unsubscribe function. */
228
+ on(type, cb) {
229
+ return this.embedWindow.on(type, cb);
230
+ }
231
+ /** Reloads the embedded chat (starts a fresh chat). */
232
+ reload() {
233
+ this.embedWindow.reload();
234
+ }
235
+ /** Closes the embedded chat. */
236
+ close() {
237
+ this.embedWindow.close();
238
+ }
239
+ /** Closes the embedded chat and reloads the host page. */
240
+ closeAndReload() {
241
+ this.close();
242
+ window.location.reload();
243
+ }
244
+ };
245
+ var PreviewConnection = class {
246
+ constructor() {
247
+ this.emitter = new Emitter();
248
+ this.onMessage = (event) => {
249
+ if (event.origin !== window.location.origin) {
250
+ return;
251
+ }
252
+ if (!isHighlightNodeMessage(event.data)) {
253
+ return;
254
+ }
255
+ const req = event.data.highlightNode;
256
+ this.emitter.emit("highlight", {
257
+ deepKey: req.deepKey,
258
+ scroll: req.options?.scroll ?? false
259
+ });
260
+ };
261
+ this.isEmbedded = RootCMSBrowserClient.isInPreviewIframe();
262
+ window.addEventListener("message", this.onMessage);
263
+ }
264
+ /**
265
+ * Requests that the doc editor scroll to (focus) a field, e.g.
266
+ * `hero.title`. Used to implement "click to edit".
267
+ */
268
+ focusField(deepKey) {
269
+ if (window.parent === window) {
270
+ return;
271
+ }
272
+ const message = { scrollToDeeplink: { deepKey } };
273
+ window.parent.postMessage(message, window.location.origin);
274
+ }
275
+ /**
276
+ * Subscribes to field highlight requests from the doc editor (sent when the
277
+ * user hovers/focuses a field). Returns an unsubscribe function.
278
+ */
279
+ on(type, cb) {
280
+ return this.emitter.on(type, cb);
281
+ }
282
+ /** Removes the message listener and all subscriptions. */
283
+ disconnect() {
284
+ window.removeEventListener("message", this.onMessage);
285
+ this.emitter.removeAll();
286
+ }
287
+ };
288
+ var RootCMSBrowserClient = class {
289
+ constructor(options) {
290
+ let url;
291
+ try {
292
+ url = new URL(options.cmsOrigin);
293
+ } catch {
294
+ throw new Error(
295
+ `invalid cmsOrigin: "${options?.cmsOrigin}" (expected e.g. "https://cms.example.com")`
296
+ );
297
+ }
298
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
299
+ throw new Error(
300
+ `invalid cmsOrigin: "${options?.cmsOrigin}" (expected an http(s) origin, e.g. "https://cms.example.com")`
301
+ );
302
+ }
303
+ this.cmsOrigin = url.origin;
304
+ }
305
+ /**
306
+ * Opens the headless doc editor for a doc (e.g. `Pages/about`) in a pop-up
307
+ * or iframe. In popup mode, call from a user gesture (e.g. a click handler)
308
+ * to avoid pop-up blockers.
309
+ *
310
+ * The slug portion of the docId is normalized the same way the CMS
311
+ * normalizes slugs, so a URL-like path also works, e.g. `Pages/about/foo`
312
+ * -> `Pages/about--foo`.
313
+ */
314
+ openEditor(docId, options) {
315
+ const [collection, ...slugParts] = docId.split("/");
316
+ const slug = normalizeSlug(slugParts.join("/"));
317
+ if (!collection || !slug || isDotSegment(collection) || isDotSegment(slug)) {
318
+ throw new Error(
319
+ `invalid docId: "${docId}" (expected "<collection>/<slug>")`
320
+ );
321
+ }
322
+ const normalizedDocId = `${collection}/${slug}`;
323
+ const url = new URL(
324
+ `${this.cmsOrigin}/cms/embed/content/${encodeURIComponent(
325
+ collection
326
+ )}/${encodeURIComponent(slug)}`
327
+ );
328
+ if (options?.deeplink) {
329
+ url.searchParams.set("deeplink", options.deeplink);
330
+ }
331
+ const embedWindow = new EmbedWindow(
332
+ url.toString(),
333
+ this.cmsOrigin,
334
+ options
335
+ );
336
+ return new EmbeddedEditor(normalizedDocId, embedWindow);
337
+ }
338
+ /**
339
+ * Opens the headless Root AI chat in a pop-up or iframe, optionally with a
340
+ * doc as context. In popup mode, call from a user gesture (e.g. a click
341
+ * handler) to avoid pop-up blockers.
342
+ */
343
+ openRootAI(options) {
344
+ const url = new URL(`${this.cmsOrigin}/cms/embed/ai`);
345
+ if (options?.docId) {
346
+ url.searchParams.set("docId", options.docId);
347
+ }
348
+ const embedWindow = new EmbedWindow(
349
+ url.toString(),
350
+ this.cmsOrigin,
351
+ options
352
+ );
353
+ return new EmbeddedAI(embedWindow);
354
+ }
355
+ /**
356
+ * Connects to the doc editor from a site rendered inside the in-CMS preview
357
+ * pane, enabling "click to edit" ({@link PreviewConnection.focusField}) and
358
+ * field highlighting (`on('highlight', ...)`).
359
+ */
360
+ static connectPreview() {
361
+ return new PreviewConnection();
362
+ }
363
+ /** Returns whether this page is rendered inside the in-CMS preview pane. */
364
+ static isInPreviewIframe() {
365
+ if (window.parent === window) {
366
+ return false;
367
+ }
368
+ return document.referrer.startsWith(`${window.location.origin}/cms`);
369
+ }
370
+ };
371
+ export {
372
+ EmbeddedAI,
373
+ EmbeddedEditor,
374
+ PreviewConnection,
375
+ RootCMSBrowserClient,
376
+ SaveState
377
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getCmsPlugin
3
- } from "./chunk-N4K6ICFR.js";
3
+ } from "./chunk-AFBSITEO.js";
4
4
 
5
5
  // core/search-index.ts
6
6
  import fs from "node:fs";
@@ -1,3 +1,7 @@
1
+ import {
2
+ normalizeSlug
3
+ } from "./chunk-J72JK4KR.js";
4
+
1
5
  // core/client.ts
2
6
  import crypto from "node:crypto";
3
7
  import {
@@ -5,11 +9,6 @@ import {
5
9
  Timestamp as Timestamp2
6
10
  } from "firebase-admin/firestore";
7
11
 
8
- // shared/slug.ts
9
- function normalizeSlug(slug) {
10
- return slug.replace(/^[\s/]*/g, "").replace(/[\s/]*$/g, "").replace(/^\/+|\/+$/g, "").replaceAll("/", "--");
11
- }
12
-
13
12
  // core/translations-manager.ts
14
13
  import {
15
14
  FieldValue,
@@ -2432,7 +2431,6 @@ var BatchResponse = class {
2432
2431
  };
2433
2432
 
2434
2433
  export {
2435
- normalizeSlug,
2436
2434
  TranslationsManager,
2437
2435
  buildTranslationsDbPath,
2438
2436
  buildTranslationsLocaleDocDbPath,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  SearchIndexService
3
- } from "./chunk-RXWDB3K4.js";
3
+ } from "./chunk-4VZTYUQ7.js";
4
4
  import {
5
5
  RootCMSClient,
6
6
  getCmsPlugin
7
- } from "./chunk-N4K6ICFR.js";
7
+ } from "./chunk-AFBSITEO.js";
8
8
 
9
9
  // core/cron.ts
10
10
  import { Timestamp as Timestamp2 } from "firebase-admin/firestore";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  SearchIndexService
3
- } from "./chunk-RXWDB3K4.js";
3
+ } from "./chunk-4VZTYUQ7.js";
4
4
  import {
5
5
  normalizeSlug
6
- } from "./chunk-N4K6ICFR.js";
6
+ } from "./chunk-J72JK4KR.js";
7
7
 
8
8
  // core/ai.ts
9
9
  import crypto from "node:crypto";
@@ -1198,7 +1198,7 @@ function extractJsonFromResponse(responseText) {
1198
1198
  // package.json
1199
1199
  var package_default = {
1200
1200
  name: "@blinkk/root-cms",
1201
- version: "3.0.5",
1201
+ version: "3.1.1",
1202
1202
  author: "s@blinkk.com",
1203
1203
  license: "MIT",
1204
1204
  engines: {
@@ -1223,6 +1223,10 @@ var package_default = {
1223
1223
  types: "./dist/core/core.d.ts",
1224
1224
  import: "./dist/core.js"
1225
1225
  },
1226
+ "./browser-client": {
1227
+ types: "./dist/browser-client/browser-client.d.ts",
1228
+ import: "./dist/browser-client.js"
1229
+ },
1226
1230
  "./client": {
1227
1231
  types: "./dist/core/client.d.ts",
1228
1232
  import: "./dist/client.js"
@@ -1273,11 +1277,11 @@ var package_default = {
1273
1277
  "@ai-sdk/openai": "3.0.60",
1274
1278
  "@ai-sdk/openai-compatible": "2.0.46",
1275
1279
  "@ai-sdk/react": "3.0.177",
1276
- ai: "6.0.175",
1277
- zod: "4.4.3",
1278
1280
  "@google-cloud/firestore": "7.11.3",
1279
1281
  "@hello-pangea/dnd": "18.0.1",
1282
+ "@mantine/spotlight": "4.2.12",
1280
1283
  "@types/cli-progress": "3.11.6",
1284
+ ai: "6.0.175",
1281
1285
  "body-parser": "1.20.2",
1282
1286
  "cli-progress": "3.12.0",
1283
1287
  commander: "11.0.0",
@@ -1287,14 +1291,14 @@ var package_default = {
1287
1291
  "date-fns-tz": "3.2.0",
1288
1292
  diff: "8.0.2",
1289
1293
  "dts-dom": "3.7.0",
1290
- "@mantine/spotlight": "4.2.12",
1291
1294
  "fnv-plus": "1.3.1",
1292
1295
  jsonwebtoken: "9.0.2",
1293
1296
  kleur: "4.1.5",
1294
1297
  minisearch: "7.2.0",
1295
1298
  "sanitize-html": "2.13.1",
1296
1299
  sirv: "2.0.3",
1297
- "tiny-glob": "0.2.9"
1300
+ "tiny-glob": "0.2.9",
1301
+ zod: "4.4.3"
1298
1302
  },
1299
1303
  "//": "NOTE(stevenle): due to compat issues with mantine and preact, mantine is pinned to v4.2.12",
1300
1304
  devDependencies: {
@@ -1339,8 +1343,8 @@ var package_default = {
1339
1343
  "@types/jsonwebtoken": "9.0.1",
1340
1344
  "@types/node": "24.3.1",
1341
1345
  "@types/sanitize-html": "2.13.0",
1342
- "@vitest/browser": "4.1.6",
1343
- "@vitest/browser-playwright": "4.1.6",
1346
+ "@vitest/browser": "4.1.8",
1347
+ "@vitest/browser-playwright": "4.1.8",
1344
1348
  concurrently: "7.6.0",
1345
1349
  esbuild: "0.28.0",
1346
1350
  firebase: "12.2.1",
@@ -1355,7 +1359,7 @@ var package_default = {
1355
1359
  "mdast-util-from-markdown": "2.0.1",
1356
1360
  "mdast-util-gfm": "3.0.0",
1357
1361
  "micromark-extension-gfm": "3.0.0",
1358
- playwright: "1.56.1",
1362
+ playwright: "1.60.0",
1359
1363
  preact: "10.27.1",
1360
1364
  "preact-iso": "2.11.1",
1361
1365
  "preact-render-to-string": "6.6.7",
@@ -1369,7 +1373,7 @@ var package_default = {
1369
1373
  yjs: "13.6.27"
1370
1374
  },
1371
1375
  peerDependencies: {
1372
- "@blinkk/root": "3.0.5",
1376
+ "@blinkk/root": "3.1.1",
1373
1377
  "firebase-admin": ">=11",
1374
1378
  "firebase-functions": ">=4"
1375
1379
  },
@@ -0,0 +1,8 @@
1
+ // shared/slug.ts
2
+ function normalizeSlug(slug) {
3
+ return slug.replace(/^[\s/]*/g, "").replace(/[\s/]*$/g, "").replace(/^\/+|\/+$/g, "").replaceAll("/", "--");
4
+ }
5
+
6
+ export {
7
+ normalizeSlug
8
+ };
package/dist/cli.js CHANGED
@@ -5,7 +5,8 @@ import {
5
5
  RootCMSClient,
6
6
  getCmsPlugin,
7
7
  unmarshalData
8
- } from "./chunk-N4K6ICFR.js";
8
+ } from "./chunk-AFBSITEO.js";
9
+ import "./chunk-J72JK4KR.js";
9
10
  import "./chunk-MLKGABMK.js";
10
11
 
11
12
  // cli/cli.ts
package/dist/client.js CHANGED
@@ -12,7 +12,8 @@ import {
12
12
  translationsForLocale,
13
13
  unmarshalArray,
14
14
  unmarshalData
15
- } from "./chunk-N4K6ICFR.js";
15
+ } from "./chunk-AFBSITEO.js";
16
+ import "./chunk-J72JK4KR.js";
16
17
  import "./chunk-MLKGABMK.js";
17
18
  export {
18
19
  BatchRequest,
@@ -27,6 +27,11 @@ export interface Doc<Fields = any> {
27
27
  until?: Timestamp;
28
28
  };
29
29
  locales?: string[];
30
+ /**
31
+ * Reverse index of asset library ids embedded in the doc's fields,
32
+ * (re)computed whenever the doc draft is saved in the CMS UI.
33
+ */
34
+ assets?: string[];
30
35
  };
31
36
  fields: Fields;
32
37
  }
@@ -345,6 +345,23 @@ export type CMSPluginOptions = {
345
345
  * override this by setting their own `variant`. Defaults to `'dropdown'`.
346
346
  */
347
347
  defaultOneOfVariant?: 'dropdown' | 'picker';
348
+ /**
349
+ * Locales to exclude from translation import and export (CSV, Google Sheets,
350
+ * and any registered translation services). Each entry is matched against
351
+ * the locale id and supports wildcards, where `*` matches any number of
352
+ * characters and `?` matches a single character. Matching is
353
+ * case-insensitive. Excluded locales are omitted from exports and ignored
354
+ * when importing.
355
+ *
356
+ * Example:
357
+ * ```ts
358
+ * cmsPlugin({
359
+ * // Exclude pseudo-locales like `ALL_es`, `ALL_fr`, etc.
360
+ * excludeLocalesFromTranslations: ['ALL_*'],
361
+ * });
362
+ * ```
363
+ */
364
+ excludeLocalesFromTranslations?: string[];
348
365
  };
349
366
  export type CMSPlugin = Plugin & {
350
367
  name: 'root-cms';
package/dist/core.js CHANGED
@@ -10,13 +10,15 @@ import {
10
10
  marshalArray,
11
11
  marshalData,
12
12
  normalizeData,
13
- normalizeSlug,
14
13
  parseDocId,
15
14
  toArrayObject,
16
15
  translationsForLocale,
17
16
  unmarshalArray,
18
17
  unmarshalData
19
- } from "./chunk-N4K6ICFR.js";
18
+ } from "./chunk-AFBSITEO.js";
19
+ import {
20
+ normalizeSlug
21
+ } from "./chunk-J72JK4KR.js";
20
22
  import {
21
23
  __export
22
24
  } from "./chunk-MLKGABMK.js";
package/dist/functions.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  runCronJobs
3
- } from "./chunk-PNLLZXVY.js";
4
- import "./chunk-RXWDB3K4.js";
5
- import "./chunk-N4K6ICFR.js";
3
+ } from "./chunk-AJDMNTZQ.js";
4
+ import "./chunk-4VZTYUQ7.js";
5
+ import "./chunk-AFBSITEO.js";
6
+ import "./chunk-J72JK4KR.js";
6
7
  import "./chunk-MLKGABMK.js";
7
8
 
8
9
  // core/functions.ts
package/dist/plugin.js CHANGED
@@ -13,18 +13,19 @@ import {
13
13
  serializeAiConfig,
14
14
  summarizeDiff,
15
15
  translateString
16
- } from "./chunk-VSPPZPMP.js";
16
+ } from "./chunk-GXV4UQ6Z.js";
17
17
  import {
18
18
  runCronJobs
19
- } from "./chunk-PNLLZXVY.js";
19
+ } from "./chunk-AJDMNTZQ.js";
20
20
  import {
21
21
  SearchIndexService
22
- } from "./chunk-RXWDB3K4.js";
22
+ } from "./chunk-4VZTYUQ7.js";
23
23
  import {
24
24
  RootCMSClient,
25
25
  parseDocId,
26
26
  unmarshalData
27
- } from "./chunk-N4K6ICFR.js";
27
+ } from "./chunk-AFBSITEO.js";
28
+ import "./chunk-J72JK4KR.js";
28
29
  import "./chunk-MLKGABMK.js";
29
30
 
30
31
  // core/plugin.ts
@@ -221,11 +222,22 @@ function isRecord(value) {
221
222
  function isDocVersion(value) {
222
223
  return value === "draft" || value === "published";
223
224
  }
225
+ function withNoTransform(cacheControl) {
226
+ const value = cacheControl?.trim() || "no-cache";
227
+ if (/(?:^|,)\s*no-transform\s*(?:,|$)/i.test(value)) {
228
+ return value;
229
+ }
230
+ return `${value}, no-transform`;
231
+ }
224
232
  async function pipeWebResponse(webResponse, res) {
225
233
  res.status(webResponse.status);
226
234
  webResponse.headers.forEach((value, key) => {
227
235
  res.setHeader(key, value);
228
236
  });
237
+ res.setHeader(
238
+ "Cache-Control",
239
+ withNoTransform(webResponse.headers.get("cache-control"))
240
+ );
229
241
  res.setHeader("X-Accel-Buffering", "no");
230
242
  if (!webResponse.body) {
231
243
  res.end();