@cmssy/react 0.1.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/client.cjs +696 -0
- package/dist/client.d.cts +77 -0
- package/dist/client.d.ts +77 -0
- package/dist/client.js +690 -0
- package/dist/index.cjs +529 -0
- package/dist/index.d.cts +166 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +503 -0
- package/dist/registry-BoxAyw4_.d.cts +189 -0
- package/dist/registry-BoxAyw4_.d.ts +189 -0
- package/package.json +47 -0
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/registry.ts
|
|
8
|
+
function buildBlockMap(blocks) {
|
|
9
|
+
const map = /* @__PURE__ */ Object.create(null);
|
|
10
|
+
for (const block of blocks) map[block.type] = block.component;
|
|
11
|
+
return map;
|
|
12
|
+
}
|
|
13
|
+
function blocksToSchemas(blocks) {
|
|
14
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
15
|
+
for (const block of blocks) {
|
|
16
|
+
const schema = {};
|
|
17
|
+
for (const [key, def] of Object.entries(block.props)) {
|
|
18
|
+
schema[key] = { ...def, label: def.label || key };
|
|
19
|
+
}
|
|
20
|
+
out[block.type] = schema;
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function blocksToMeta(blocks, defaults = {}) {
|
|
25
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
26
|
+
for (const block of blocks) {
|
|
27
|
+
const category = block.category ?? defaults.category;
|
|
28
|
+
out[block.type] = {
|
|
29
|
+
label: block.label ?? block.type,
|
|
30
|
+
...category ? { category } : {},
|
|
31
|
+
...block.icon ? { icon: block.icon } : {},
|
|
32
|
+
...block.layoutPositions ? { layoutPositions: block.layoutPositions } : {}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/bridge/protocol.ts
|
|
39
|
+
var PROTOCOL_VERSION = 1;
|
|
40
|
+
|
|
41
|
+
// src/bridge/messages.ts
|
|
42
|
+
function normalizeOrigin(origin) {
|
|
43
|
+
if (origin === "*") return "*";
|
|
44
|
+
try {
|
|
45
|
+
return new URL(origin).origin;
|
|
46
|
+
} catch {
|
|
47
|
+
return origin;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function postToEditor(target, editorOrigin, message) {
|
|
51
|
+
target.postMessage(message, normalizeOrigin(editorOrigin));
|
|
52
|
+
}
|
|
53
|
+
function isObject(value) {
|
|
54
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
function parseEditorMessage(data, origin, expectedOrigin) {
|
|
57
|
+
const expected = normalizeOrigin(expectedOrigin);
|
|
58
|
+
if (expected !== "*" && origin !== expected) return null;
|
|
59
|
+
if (!isObject(data)) return null;
|
|
60
|
+
switch (data.type) {
|
|
61
|
+
case "cmssy:select":
|
|
62
|
+
return typeof data.blockId === "string" && data.protocolVersion === PROTOCOL_VERSION ? {
|
|
63
|
+
type: "cmssy:select",
|
|
64
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
65
|
+
blockId: data.blockId
|
|
66
|
+
} : null;
|
|
67
|
+
case "cmssy:patch":
|
|
68
|
+
return typeof data.blockId === "string" && isObject(data.content) && data.protocolVersion === PROTOCOL_VERSION ? {
|
|
69
|
+
type: "cmssy:patch",
|
|
70
|
+
blockId: data.blockId,
|
|
71
|
+
content: data.content,
|
|
72
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
73
|
+
...typeof data.layoutPosition === "string" ? { layoutPosition: data.layoutPosition } : {}
|
|
74
|
+
} : null;
|
|
75
|
+
case "cmssy:parent-ready":
|
|
76
|
+
return data.protocolVersion === PROTOCOL_VERSION ? { type: "cmssy:parent-ready", protocolVersion: PROTOCOL_VERSION } : null;
|
|
77
|
+
case "cmssy:insert":
|
|
78
|
+
return typeof data.blockId === "string" && typeof data.blockType === "string" && isObject(data.content) && typeof data.index === "number" && data.protocolVersion === PROTOCOL_VERSION ? {
|
|
79
|
+
type: "cmssy:insert",
|
|
80
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
81
|
+
blockId: data.blockId,
|
|
82
|
+
blockType: data.blockType,
|
|
83
|
+
content: data.content,
|
|
84
|
+
index: data.index
|
|
85
|
+
} : null;
|
|
86
|
+
case "cmssy:reorder":
|
|
87
|
+
return Array.isArray(data.blockIds) && data.blockIds.every((id) => typeof id === "string") && data.protocolVersion === PROTOCOL_VERSION ? {
|
|
88
|
+
type: "cmssy:reorder",
|
|
89
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
90
|
+
blockIds: data.blockIds
|
|
91
|
+
} : null;
|
|
92
|
+
case "cmssy:remove":
|
|
93
|
+
return typeof data.blockId === "string" && data.protocolVersion === PROTOCOL_VERSION ? {
|
|
94
|
+
type: "cmssy:remove",
|
|
95
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
96
|
+
blockId: data.blockId
|
|
97
|
+
} : null;
|
|
98
|
+
case "cmssy:drag-over":
|
|
99
|
+
return typeof data.y === "number" && data.protocolVersion === PROTOCOL_VERSION ? {
|
|
100
|
+
type: "cmssy:drag-over",
|
|
101
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
102
|
+
y: data.y
|
|
103
|
+
} : null;
|
|
104
|
+
case "cmssy:drag-end":
|
|
105
|
+
return data.protocolVersion === PROTOCOL_VERSION ? { type: "cmssy:drag-end", protocolVersion: PROTOCOL_VERSION } : null;
|
|
106
|
+
default:
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/bridge/use-edit-bridge.tsx
|
|
112
|
+
var ZERO_RECT = { x: 0, y: 0, width: 0, height: 0 };
|
|
113
|
+
function collectRects() {
|
|
114
|
+
const rects = /* @__PURE__ */ new Map();
|
|
115
|
+
if (typeof document === "undefined") return rects;
|
|
116
|
+
for (const el of document.querySelectorAll("[data-block-id]")) {
|
|
117
|
+
const id = el.getAttribute("data-block-id");
|
|
118
|
+
if (id && !rects.has(id)) {
|
|
119
|
+
const r = el.getBoundingClientRect();
|
|
120
|
+
rects.set(id, { x: r.x, y: r.y, width: r.width, height: r.height });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return rects;
|
|
124
|
+
}
|
|
125
|
+
function collectLayoutBlocks(rects, pageIds) {
|
|
126
|
+
const out = [];
|
|
127
|
+
if (typeof document === "undefined") return out;
|
|
128
|
+
const seen = /* @__PURE__ */ new Set();
|
|
129
|
+
for (const el of document.querySelectorAll("[data-layout-position]")) {
|
|
130
|
+
const id = el.getAttribute("data-block-id");
|
|
131
|
+
const type = el.getAttribute("data-block-type");
|
|
132
|
+
const layoutPosition = el.getAttribute("data-layout-position");
|
|
133
|
+
if (id && type && layoutPosition !== null && !pageIds.has(id) && !seen.has(id)) {
|
|
134
|
+
seen.add(id);
|
|
135
|
+
out.push({
|
|
136
|
+
id,
|
|
137
|
+
type,
|
|
138
|
+
layoutPosition,
|
|
139
|
+
bounds: rects.get(id) ?? ZERO_RECT
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
function useEditBridge(page, config) {
|
|
146
|
+
const [patches, setPatches] = react.useState({});
|
|
147
|
+
const [selected, setSelected] = react.useState(null);
|
|
148
|
+
const [inserted, setInserted] = react.useState([]);
|
|
149
|
+
const [order, setOrder] = react.useState(null);
|
|
150
|
+
const [removed, setRemoved] = react.useState([]);
|
|
151
|
+
const selectedIdRef = react.useRef(null);
|
|
152
|
+
const { id: pageId, blocks } = page;
|
|
153
|
+
const blocksKey = blocks.map((b) => `${b.id}:${b.type}`).join("|");
|
|
154
|
+
react.useEffect(() => {
|
|
155
|
+
setPatches({});
|
|
156
|
+
setSelected(null);
|
|
157
|
+
setInserted([]);
|
|
158
|
+
setOrder(null);
|
|
159
|
+
setRemoved([]);
|
|
160
|
+
selectedIdRef.current = null;
|
|
161
|
+
}, [pageId, blocksKey]);
|
|
162
|
+
react.useEffect(() => {
|
|
163
|
+
if (typeof window === "undefined" || window.parent === window) return;
|
|
164
|
+
const { editorOrigin } = config;
|
|
165
|
+
if (editorOrigin === "*" && typeof console !== "undefined") {
|
|
166
|
+
console.warn(
|
|
167
|
+
"[cmssy] editorOrigin '*' disables origin checks - dev only, do not use in production"
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
const sendReady = () => {
|
|
171
|
+
try {
|
|
172
|
+
const rects = collectRects();
|
|
173
|
+
const pageIds = new Set(blocks.map((b) => b.id));
|
|
174
|
+
postToEditor(window.parent, editorOrigin, {
|
|
175
|
+
type: "cmssy:ready",
|
|
176
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
177
|
+
blocks: [
|
|
178
|
+
...blocks.map((b) => ({
|
|
179
|
+
id: b.id,
|
|
180
|
+
type: b.type,
|
|
181
|
+
bounds: rects.get(b.id) ?? ZERO_RECT
|
|
182
|
+
})),
|
|
183
|
+
...collectLayoutBlocks(rects, pageIds)
|
|
184
|
+
],
|
|
185
|
+
schemas: config.schemas ?? /* @__PURE__ */ Object.create(null),
|
|
186
|
+
blockMeta: config.blockMeta ?? /* @__PURE__ */ Object.create(null)
|
|
187
|
+
});
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (typeof console !== "undefined") {
|
|
190
|
+
console.warn("[cmssy] failed to post to editor", error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const handler = (event) => {
|
|
195
|
+
if (event.source && event.source !== window.parent) return;
|
|
196
|
+
const message = parseEditorMessage(
|
|
197
|
+
event.data,
|
|
198
|
+
event.origin,
|
|
199
|
+
editorOrigin
|
|
200
|
+
);
|
|
201
|
+
if (!message) return;
|
|
202
|
+
if (message.type === "cmssy:patch") {
|
|
203
|
+
if (message.layoutPosition !== void 0) return;
|
|
204
|
+
setPatches((prev) => ({
|
|
205
|
+
...prev,
|
|
206
|
+
[message.blockId]: { ...prev[message.blockId], ...message.content }
|
|
207
|
+
}));
|
|
208
|
+
} else if (message.type === "cmssy:select") {
|
|
209
|
+
setSelected(message.blockId);
|
|
210
|
+
selectedIdRef.current = message.blockId;
|
|
211
|
+
} else if (message.type === "cmssy:insert") {
|
|
212
|
+
setInserted((prev) => {
|
|
213
|
+
const next = prev.filter((b) => b.blockId !== message.blockId);
|
|
214
|
+
next.push({
|
|
215
|
+
blockId: message.blockId,
|
|
216
|
+
blockType: message.blockType,
|
|
217
|
+
content: message.content,
|
|
218
|
+
index: message.index
|
|
219
|
+
});
|
|
220
|
+
return next;
|
|
221
|
+
});
|
|
222
|
+
} else if (message.type === "cmssy:reorder") {
|
|
223
|
+
setOrder(message.blockIds);
|
|
224
|
+
} else if (message.type === "cmssy:remove") {
|
|
225
|
+
setRemoved(
|
|
226
|
+
(prev) => prev.includes(message.blockId) ? prev : [...prev, message.blockId]
|
|
227
|
+
);
|
|
228
|
+
} else if (message.type === "cmssy:parent-ready") {
|
|
229
|
+
sendReady();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
const onClick = (event) => {
|
|
233
|
+
const target = event.target;
|
|
234
|
+
const el = target?.closest?.("[data-block-id]");
|
|
235
|
+
const id = el?.getAttribute("data-block-id");
|
|
236
|
+
if (!id || !el) return;
|
|
237
|
+
selectedIdRef.current = id;
|
|
238
|
+
if (target?.closest?.("a[href]")) event.preventDefault();
|
|
239
|
+
const r = el.getBoundingClientRect();
|
|
240
|
+
const layoutPosition = el.getAttribute("data-layout-position");
|
|
241
|
+
try {
|
|
242
|
+
postToEditor(window.parent, editorOrigin, {
|
|
243
|
+
type: "cmssy:click",
|
|
244
|
+
blockId: id,
|
|
245
|
+
rect: { x: r.x, y: r.y, width: r.width, height: r.height },
|
|
246
|
+
...layoutPosition !== null ? { layoutPosition } : {}
|
|
247
|
+
});
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
let boundsRaf = 0;
|
|
252
|
+
let boundsPending = false;
|
|
253
|
+
const emitSelectedBounds = () => {
|
|
254
|
+
if (boundsPending || !selectedIdRef.current) return;
|
|
255
|
+
boundsPending = true;
|
|
256
|
+
boundsRaf = requestAnimationFrame(() => {
|
|
257
|
+
boundsPending = false;
|
|
258
|
+
boundsRaf = 0;
|
|
259
|
+
const id = selectedIdRef.current;
|
|
260
|
+
if (!id) return;
|
|
261
|
+
const el = document.querySelector(
|
|
262
|
+
`[data-block-id="${id.replace(/["\\]/g, "\\$&")}"]`
|
|
263
|
+
);
|
|
264
|
+
if (!el) return;
|
|
265
|
+
const r = el.getBoundingClientRect();
|
|
266
|
+
try {
|
|
267
|
+
postToEditor(window.parent, editorOrigin, {
|
|
268
|
+
type: "cmssy:bounds",
|
|
269
|
+
blockId: id,
|
|
270
|
+
rect: { x: r.x, y: r.y, width: r.width, height: r.height }
|
|
271
|
+
});
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
window.addEventListener("message", handler);
|
|
277
|
+
document.addEventListener("click", onClick);
|
|
278
|
+
window.addEventListener("scroll", emitSelectedBounds, {
|
|
279
|
+
capture: true,
|
|
280
|
+
passive: true
|
|
281
|
+
});
|
|
282
|
+
window.addEventListener("resize", emitSelectedBounds);
|
|
283
|
+
sendReady();
|
|
284
|
+
return () => {
|
|
285
|
+
if (boundsRaf) cancelAnimationFrame(boundsRaf);
|
|
286
|
+
window.removeEventListener("message", handler);
|
|
287
|
+
document.removeEventListener("click", onClick);
|
|
288
|
+
window.removeEventListener("scroll", emitSelectedBounds, {
|
|
289
|
+
capture: true
|
|
290
|
+
});
|
|
291
|
+
window.removeEventListener("resize", emitSelectedBounds);
|
|
292
|
+
};
|
|
293
|
+
}, [config.editorOrigin, pageId, blocksKey]);
|
|
294
|
+
return { patches, selected, inserted, order, removed };
|
|
295
|
+
}
|
|
296
|
+
var MOVE_MIME = "application/x-cmssy-move";
|
|
297
|
+
function visible(el) {
|
|
298
|
+
return el.offsetParent !== null || el.getClientRects().length > 0;
|
|
299
|
+
}
|
|
300
|
+
function blockElements() {
|
|
301
|
+
return Array.from(
|
|
302
|
+
document.querySelectorAll(
|
|
303
|
+
"[data-block-id]:not([data-layout-position])"
|
|
304
|
+
)
|
|
305
|
+
).filter(visible);
|
|
306
|
+
}
|
|
307
|
+
function computeDropTarget(clientY) {
|
|
308
|
+
const els = blockElements();
|
|
309
|
+
for (let i = 0; i < els.length; i++) {
|
|
310
|
+
const r = els[i].getBoundingClientRect();
|
|
311
|
+
if (clientY < r.top + r.height / 2) {
|
|
312
|
+
return { index: i, y: r.top };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const last = els[els.length - 1];
|
|
316
|
+
return {
|
|
317
|
+
index: els.length,
|
|
318
|
+
y: last ? last.getBoundingClientRect().bottom : 0
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function useDragAgent(config) {
|
|
322
|
+
const [dropY, setDropY] = react.useState(null);
|
|
323
|
+
react.useEffect(() => {
|
|
324
|
+
if (typeof window === "undefined" || window.parent === window) return;
|
|
325
|
+
const { editorOrigin } = config;
|
|
326
|
+
if (editorOrigin === "*" && typeof console !== "undefined") {
|
|
327
|
+
console.warn(
|
|
328
|
+
"[cmssy] editorOrigin '*' disables origin checks - dev only, do not use in production"
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
let movingId = null;
|
|
332
|
+
let lastDropY = null;
|
|
333
|
+
const updateDropY = (y) => {
|
|
334
|
+
if (y === lastDropY) return;
|
|
335
|
+
lastDropY = y;
|
|
336
|
+
setDropY(y);
|
|
337
|
+
};
|
|
338
|
+
const onDragStart = (event) => {
|
|
339
|
+
const blockEl = event.target?.closest("[data-block-id]");
|
|
340
|
+
const id = blockEl?.getAttribute("data-block-id");
|
|
341
|
+
if (!id || !event.dataTransfer) return;
|
|
342
|
+
movingId = id;
|
|
343
|
+
event.dataTransfer.setData(MOVE_MIME, id);
|
|
344
|
+
event.dataTransfer.effectAllowed = "move";
|
|
345
|
+
};
|
|
346
|
+
const onDragOver = (event) => {
|
|
347
|
+
if (!movingId) return;
|
|
348
|
+
event.preventDefault();
|
|
349
|
+
updateDropY(computeDropTarget(event.clientY).y);
|
|
350
|
+
};
|
|
351
|
+
const onDrop = (event) => {
|
|
352
|
+
if (!movingId) return;
|
|
353
|
+
event.preventDefault();
|
|
354
|
+
const { index } = computeDropTarget(event.clientY);
|
|
355
|
+
const blockId = movingId;
|
|
356
|
+
movingId = null;
|
|
357
|
+
updateDropY(null);
|
|
358
|
+
try {
|
|
359
|
+
postToEditor(window.parent, editorOrigin, {
|
|
360
|
+
type: "cmssy:move",
|
|
361
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
362
|
+
blockId,
|
|
363
|
+
index
|
|
364
|
+
});
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
const onDragEnd = () => {
|
|
369
|
+
movingId = null;
|
|
370
|
+
updateDropY(null);
|
|
371
|
+
};
|
|
372
|
+
const onMessage = (event) => {
|
|
373
|
+
if (event.source && event.source !== window.parent) return;
|
|
374
|
+
const message = parseEditorMessage(
|
|
375
|
+
event.data,
|
|
376
|
+
event.origin,
|
|
377
|
+
editorOrigin
|
|
378
|
+
);
|
|
379
|
+
if (!message) return;
|
|
380
|
+
if (message.type === "cmssy:drag-over") {
|
|
381
|
+
const edge = 64;
|
|
382
|
+
const step = 20;
|
|
383
|
+
if (message.y < edge) {
|
|
384
|
+
window.scrollBy(0, -step);
|
|
385
|
+
} else if (message.y > window.innerHeight - edge) {
|
|
386
|
+
window.scrollBy(0, step);
|
|
387
|
+
}
|
|
388
|
+
const { index, y } = computeDropTarget(message.y);
|
|
389
|
+
updateDropY(y);
|
|
390
|
+
try {
|
|
391
|
+
postToEditor(window.parent, editorOrigin, {
|
|
392
|
+
type: "cmssy:drag-index",
|
|
393
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
394
|
+
index
|
|
395
|
+
});
|
|
396
|
+
} catch {
|
|
397
|
+
}
|
|
398
|
+
} else if (message.type === "cmssy:drag-end") {
|
|
399
|
+
updateDropY(null);
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
document.addEventListener("dragstart", onDragStart);
|
|
403
|
+
document.addEventListener("dragover", onDragOver);
|
|
404
|
+
document.addEventListener("drop", onDrop);
|
|
405
|
+
document.addEventListener("dragend", onDragEnd);
|
|
406
|
+
window.addEventListener("message", onMessage);
|
|
407
|
+
return () => {
|
|
408
|
+
document.removeEventListener("dragstart", onDragStart);
|
|
409
|
+
document.removeEventListener("dragover", onDragOver);
|
|
410
|
+
document.removeEventListener("drop", onDrop);
|
|
411
|
+
document.removeEventListener("dragend", onDragEnd);
|
|
412
|
+
window.removeEventListener("message", onMessage);
|
|
413
|
+
};
|
|
414
|
+
}, [config.editorOrigin]);
|
|
415
|
+
return { dropY };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/content/get-block-content.ts
|
|
419
|
+
function isPlainObject(value) {
|
|
420
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
421
|
+
}
|
|
422
|
+
function looksLikeLocaleKey(key) {
|
|
423
|
+
return /^[a-z]{2}(-[A-Za-z]{2})?$/.test(key);
|
|
424
|
+
}
|
|
425
|
+
function getBlockContentForLanguage(content, locale, defaultLocale = "en", availableLocales) {
|
|
426
|
+
if (!isPlainObject(content)) return {};
|
|
427
|
+
const isLocale = looksLikeLocaleKey;
|
|
428
|
+
const localeEntries = Object.entries(content).filter(
|
|
429
|
+
([key, value]) => isLocale(key) && isPlainObject(value)
|
|
430
|
+
);
|
|
431
|
+
if (localeEntries.length === 0) return { ...content };
|
|
432
|
+
const localeMap = Object.fromEntries(localeEntries);
|
|
433
|
+
const nonTranslatable = {};
|
|
434
|
+
for (const [key, value] of Object.entries(content)) {
|
|
435
|
+
if (!(isLocale(key) && isPlainObject(value))) nonTranslatable[key] = value;
|
|
436
|
+
}
|
|
437
|
+
const fallbackKey = Object.keys(localeMap)[0];
|
|
438
|
+
const chosen = localeMap[locale] ?? localeMap[defaultLocale] ?? localeMap[fallbackKey];
|
|
439
|
+
return { ...nonTranslatable, ...chosen };
|
|
440
|
+
}
|
|
441
|
+
var WARN_CAP = 256;
|
|
442
|
+
var warned = /* @__PURE__ */ new Set();
|
|
443
|
+
function UnknownBlock({ type }) {
|
|
444
|
+
if (typeof window !== "undefined" && !warned.has(type)) {
|
|
445
|
+
if (warned.size >= WARN_CAP) warned.clear();
|
|
446
|
+
warned.add(type);
|
|
447
|
+
console.warn(`[cmssy] no component registered for block type "${type}"`);
|
|
448
|
+
}
|
|
449
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cmssy-unknown-block": type });
|
|
450
|
+
}
|
|
451
|
+
function CmssyBlock({
|
|
452
|
+
block,
|
|
453
|
+
locale,
|
|
454
|
+
defaultLocale,
|
|
455
|
+
blockMap,
|
|
456
|
+
patchedContent,
|
|
457
|
+
editable,
|
|
458
|
+
layoutPosition
|
|
459
|
+
}) {
|
|
460
|
+
const Component = Object.hasOwn(blockMap, block.type) ? blockMap[block.type] : void 0;
|
|
461
|
+
const base = getBlockContentForLanguage(block.content, locale, defaultLocale);
|
|
462
|
+
const content = patchedContent ? { ...base, ...patchedContent } : base;
|
|
463
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
464
|
+
"div",
|
|
465
|
+
{
|
|
466
|
+
"data-block-id": block.id,
|
|
467
|
+
"data-block-type": block.type,
|
|
468
|
+
"data-layout-position": layoutPosition,
|
|
469
|
+
draggable: editable || void 0,
|
|
470
|
+
style: Component ? void 0 : { display: "none" },
|
|
471
|
+
children: Component ? react.createElement(Component, { content }) : /* @__PURE__ */ jsxRuntime.jsx(UnknownBlock, { type: block.type })
|
|
472
|
+
}
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
function CmssyEditablePage({
|
|
476
|
+
page,
|
|
477
|
+
blocks,
|
|
478
|
+
locale = "en",
|
|
479
|
+
defaultLocale = "en",
|
|
480
|
+
edit,
|
|
481
|
+
category
|
|
482
|
+
}) {
|
|
483
|
+
if (!Array.isArray(blocks)) {
|
|
484
|
+
throw new Error(
|
|
485
|
+
"cmssy: CmssyEditablePage requires a blocks array \u2014 pass your defineBlock(...) array"
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
if (!page) return null;
|
|
489
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
490
|
+
EditableBlocks,
|
|
491
|
+
{
|
|
492
|
+
page,
|
|
493
|
+
blocks,
|
|
494
|
+
locale,
|
|
495
|
+
defaultLocale,
|
|
496
|
+
edit,
|
|
497
|
+
category
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
function EditableBlocks({
|
|
502
|
+
page,
|
|
503
|
+
blocks,
|
|
504
|
+
locale,
|
|
505
|
+
defaultLocale,
|
|
506
|
+
edit,
|
|
507
|
+
category
|
|
508
|
+
}) {
|
|
509
|
+
const blockMap = react.useMemo(() => buildBlockMap(blocks), [blocks]);
|
|
510
|
+
const bridgeConfig = react.useMemo(
|
|
511
|
+
() => ({
|
|
512
|
+
...edit,
|
|
513
|
+
schemas: edit.schemas ?? blocksToSchemas(blocks),
|
|
514
|
+
blockMeta: edit.blockMeta ?? blocksToMeta(blocks, { category })
|
|
515
|
+
}),
|
|
516
|
+
[edit, blocks, category]
|
|
517
|
+
);
|
|
518
|
+
const { patches, inserted, order, removed } = useEditBridge(
|
|
519
|
+
page,
|
|
520
|
+
bridgeConfig
|
|
521
|
+
);
|
|
522
|
+
const { dropY } = useDragAgent(bridgeConfig);
|
|
523
|
+
const renderBlocks = react.useMemo(() => {
|
|
524
|
+
const removedSet = new Set(removed);
|
|
525
|
+
const merged = page.blocks.filter((b) => !removedSet.has(b.id));
|
|
526
|
+
const sorted = [...inserted].filter((ins) => !removedSet.has(ins.blockId)).sort((a, b) => a.index - b.index);
|
|
527
|
+
for (const ins of sorted) {
|
|
528
|
+
const at = Math.max(0, Math.min(ins.index, merged.length));
|
|
529
|
+
merged.splice(at, 0, {
|
|
530
|
+
id: ins.blockId,
|
|
531
|
+
type: ins.blockType,
|
|
532
|
+
content: ins.content
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
if (order) {
|
|
536
|
+
const rank = new Map(order.map((id, i) => [id, i]));
|
|
537
|
+
const fallback = order.length;
|
|
538
|
+
merged.sort(
|
|
539
|
+
(a, b) => (rank.get(a.id) ?? fallback) - (rank.get(b.id) ?? fallback)
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
return merged;
|
|
543
|
+
}, [page.blocks, inserted, order, removed]);
|
|
544
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
545
|
+
renderBlocks.map((block) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
546
|
+
CmssyBlock,
|
|
547
|
+
{
|
|
548
|
+
block,
|
|
549
|
+
locale,
|
|
550
|
+
defaultLocale,
|
|
551
|
+
patchedContent: patches[block.id],
|
|
552
|
+
blockMap,
|
|
553
|
+
editable: true
|
|
554
|
+
},
|
|
555
|
+
block.id
|
|
556
|
+
)),
|
|
557
|
+
dropY !== null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
558
|
+
"div",
|
|
559
|
+
{
|
|
560
|
+
style: {
|
|
561
|
+
position: "fixed",
|
|
562
|
+
left: 0,
|
|
563
|
+
right: 0,
|
|
564
|
+
top: dropY,
|
|
565
|
+
height: 2,
|
|
566
|
+
background: "#3b82f6",
|
|
567
|
+
zIndex: 2147483647,
|
|
568
|
+
pointerEvents: "none"
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
)
|
|
572
|
+
] });
|
|
573
|
+
}
|
|
574
|
+
function CmssyLazyEditor({ load, ...props }) {
|
|
575
|
+
const [loaded, setLoaded] = react.useState(null);
|
|
576
|
+
react.useEffect(() => {
|
|
577
|
+
let active = true;
|
|
578
|
+
setLoaded(null);
|
|
579
|
+
(async () => {
|
|
580
|
+
try {
|
|
581
|
+
const m = await load();
|
|
582
|
+
if (!active) return;
|
|
583
|
+
if (!Array.isArray(m.blocks)) {
|
|
584
|
+
throw new Error(
|
|
585
|
+
"cmssy: CmssyLazyEditor load() must resolve to { blocks: BlockDefinition[] }"
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
setLoaded({ blocks: m.blocks, category: m.category });
|
|
589
|
+
} catch (err) {
|
|
590
|
+
if (typeof console !== "undefined") {
|
|
591
|
+
console.error("[cmssy] CmssyLazyEditor failed to load blocks", err);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
})();
|
|
595
|
+
return () => {
|
|
596
|
+
active = false;
|
|
597
|
+
};
|
|
598
|
+
}, [load]);
|
|
599
|
+
if (!loaded) return null;
|
|
600
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
601
|
+
CmssyEditablePage,
|
|
602
|
+
{
|
|
603
|
+
...props,
|
|
604
|
+
blocks: loaded.blocks,
|
|
605
|
+
category: loaded.category
|
|
606
|
+
}
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
function useLayoutPatchBridge(position, config) {
|
|
610
|
+
const [patches, setPatches] = react.useState({});
|
|
611
|
+
react.useEffect(() => {
|
|
612
|
+
setPatches({});
|
|
613
|
+
if (typeof window === "undefined" || window.parent === window) return;
|
|
614
|
+
const { editorOrigin } = config;
|
|
615
|
+
const handler = (event) => {
|
|
616
|
+
if (event.source && event.source !== window.parent) return;
|
|
617
|
+
const message = parseEditorMessage(
|
|
618
|
+
event.data,
|
|
619
|
+
event.origin,
|
|
620
|
+
editorOrigin
|
|
621
|
+
);
|
|
622
|
+
if (!message) return;
|
|
623
|
+
if (message.type === "cmssy:patch" && message.layoutPosition === position) {
|
|
624
|
+
setPatches((prev) => ({
|
|
625
|
+
...prev,
|
|
626
|
+
[message.blockId]: { ...prev[message.blockId], ...message.content }
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
window.addEventListener("message", handler);
|
|
631
|
+
return () => window.removeEventListener("message", handler);
|
|
632
|
+
}, [config.editorOrigin, position]);
|
|
633
|
+
return patches;
|
|
634
|
+
}
|
|
635
|
+
function CmssyEditableLayout({
|
|
636
|
+
groups,
|
|
637
|
+
blocks,
|
|
638
|
+
position,
|
|
639
|
+
locale = "en",
|
|
640
|
+
defaultLocale = "en",
|
|
641
|
+
edit
|
|
642
|
+
}) {
|
|
643
|
+
const blockMap = react.useMemo(() => buildBlockMap(blocks), [blocks]);
|
|
644
|
+
const layoutBlocks = react.useMemo(() => {
|
|
645
|
+
const group = groups.find((g) => g.position === position);
|
|
646
|
+
return group ? group.blocks.filter((b) => b.isActive).slice().sort((a, b) => a.order - b.order) : [];
|
|
647
|
+
}, [groups, position]);
|
|
648
|
+
const patches = useLayoutPatchBridge(position, edit);
|
|
649
|
+
if (layoutBlocks.length === 0) return null;
|
|
650
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: layoutBlocks.map((block) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
651
|
+
CmssyBlock,
|
|
652
|
+
{
|
|
653
|
+
block,
|
|
654
|
+
locale,
|
|
655
|
+
defaultLocale,
|
|
656
|
+
blockMap,
|
|
657
|
+
patchedContent: patches[block.id],
|
|
658
|
+
layoutPosition: position
|
|
659
|
+
},
|
|
660
|
+
block.id
|
|
661
|
+
)) });
|
|
662
|
+
}
|
|
663
|
+
function CmssyLazyLayout({ load, ...props }) {
|
|
664
|
+
const [blocks, setBlocks] = react.useState(null);
|
|
665
|
+
react.useEffect(() => {
|
|
666
|
+
let active = true;
|
|
667
|
+
setBlocks(null);
|
|
668
|
+
(async () => {
|
|
669
|
+
try {
|
|
670
|
+
const m = await load();
|
|
671
|
+
if (!active) return;
|
|
672
|
+
if (!Array.isArray(m.blocks)) {
|
|
673
|
+
throw new Error(
|
|
674
|
+
"cmssy: CmssyLazyLayout load() must resolve to { blocks: BlockDefinition[] }"
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
setBlocks(m.blocks);
|
|
678
|
+
} catch (err) {
|
|
679
|
+
if (typeof console !== "undefined") {
|
|
680
|
+
console.error("[cmssy] CmssyLazyLayout failed to load blocks", err);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
})();
|
|
684
|
+
return () => {
|
|
685
|
+
active = false;
|
|
686
|
+
};
|
|
687
|
+
}, [load]);
|
|
688
|
+
if (!blocks) return null;
|
|
689
|
+
return /* @__PURE__ */ jsxRuntime.jsx(CmssyEditableLayout, { ...props, blocks });
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
exports.CmssyEditableLayout = CmssyEditableLayout;
|
|
693
|
+
exports.CmssyEditablePage = CmssyEditablePage;
|
|
694
|
+
exports.CmssyLazyEditor = CmssyLazyEditor;
|
|
695
|
+
exports.CmssyLazyLayout = CmssyLazyLayout;
|
|
696
|
+
exports.useEditBridge = useEditBridge;
|