@paulpaulstudio/strapi-render 0.1.1 → 0.3.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
@@ -4,12 +4,30 @@
4
4
  var react = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
- var EditModeContext = react.createContext({ enabled: false, token: null });
7
+ var noop = () => {
8
+ };
9
+ var EditModeContext = react.createContext({
10
+ enabled: false,
11
+ token: null,
12
+ changes: /* @__PURE__ */ new Map(),
13
+ isDirty: false,
14
+ isCommitting: false,
15
+ lastCommitError: null,
16
+ setChange: noop,
17
+ clearChange: noop,
18
+ commit: noop,
19
+ openMediaPicker: noop,
20
+ onMediaPicked: () => noop
21
+ });
8
22
  function useStrapiEditMode() {
9
23
  return react.useContext(EditModeContext);
10
24
  }
11
25
  function StrapiEditModeProvider({ children, enabled: enabledOverride }) {
12
26
  const [urlState, setUrlState] = react.useState({ enabled: false, token: null });
27
+ const [changes, setChanges] = react.useState(/* @__PURE__ */ new Map());
28
+ const [isCommitting, setIsCommitting] = react.useState(false);
29
+ const [lastCommitError, setLastCommitError] = react.useState(null);
30
+ const mediaPickListeners = react.useRef(/* @__PURE__ */ new Map());
13
31
  react.useEffect(() => {
14
32
  if (typeof window === "undefined") return;
15
33
  if (enabledOverride !== void 0) {
@@ -33,10 +51,7 @@ function StrapiEditModeProvider({ children, enabled: enabledOverride }) {
33
51
  if (!urlState.enabled) return;
34
52
  if (window.parent === window) return;
35
53
  try {
36
- window.parent.postMessage(
37
- { type: "pp:edit:ready", href: window.location.href },
38
- "*"
39
- );
54
+ window.parent.postMessage({ type: "pp:edit:ready", href: window.location.href }, "*");
40
55
  } catch {
41
56
  }
42
57
  }, [urlState.enabled]);
@@ -45,91 +60,278 @@ function StrapiEditModeProvider({ children, enabled: enabledOverride }) {
45
60
  if (!urlState.enabled) return;
46
61
  function onMsg(e) {
47
62
  const d = e.data;
48
- if (d && typeof d === "object" && d.type === "pp:edit:reload") {
63
+ if (!d || typeof d !== "object") return;
64
+ if (d.type === "pp:edit:reload") {
49
65
  window.location.reload();
66
+ return;
67
+ }
68
+ if (d.type === "pp:edit:commit-result") {
69
+ setIsCommitting(false);
70
+ if (d.ok) {
71
+ setChanges(/* @__PURE__ */ new Map());
72
+ setLastCommitError(null);
73
+ } else {
74
+ setLastCommitError(d.error || "Speichern fehlgeschlagen");
75
+ }
76
+ return;
77
+ }
78
+ if (d.type === "pp:edit:media-picked") {
79
+ const listener = mediaPickListeners.current.get(d.path);
80
+ if (listener) {
81
+ listener(d.value);
82
+ mediaPickListeners.current.delete(d.path);
83
+ }
84
+ return;
50
85
  }
51
86
  }
52
87
  window.addEventListener("message", onMsg);
53
88
  return () => window.removeEventListener("message", onMsg);
54
89
  }, [urlState.enabled]);
55
- const value = react.useMemo(() => urlState, [urlState]);
56
- return /* @__PURE__ */ jsxRuntime.jsx(EditModeContext.Provider, { value, children });
57
- }
58
- var EDIT_ATTR_STYLE = {
59
- outline: "1px dashed transparent",
60
- outlineOffset: "2px",
61
- cursor: "pointer",
62
- transition: "outline-color 0.15s ease"
63
- };
64
- var EDIT_HOVER_STYLE = {
65
- outlineColor: "#FA501E"
66
- };
67
- function StrapiField({ path, type, value, children, className }) {
68
- const { enabled } = useStrapiEditMode();
69
- const onClick = react.useCallback(
70
- (e) => {
71
- e.preventDefault();
72
- e.stopPropagation();
73
- if (typeof window === "undefined" || window.parent === window) return;
74
- const target = e.currentTarget;
75
- const rect = target.getBoundingClientRect();
90
+ const setChange = react.useCallback((path, fieldType, value2) => {
91
+ setChanges((prev) => {
92
+ const next = new Map(prev);
93
+ next.set(path, { path, fieldType, value: value2 });
94
+ return next;
95
+ });
96
+ setLastCommitError(null);
97
+ if (typeof window !== "undefined" && window.parent !== window) {
76
98
  try {
77
- window.parent.postMessage(
78
- {
79
- type: "pp:edit:click",
80
- path,
81
- fieldType: type,
82
- rect: {
83
- top: rect.top,
84
- left: rect.left,
85
- width: rect.width,
86
- height: rect.height
87
- },
88
- currentValue: value
89
- },
90
- "*"
91
- );
99
+ window.parent.postMessage({ type: "pp:edit:change", path, fieldType, value: value2 }, "*");
92
100
  } catch {
93
101
  }
94
- },
95
- [path, type, value]
102
+ }
103
+ }, []);
104
+ const clearChange = react.useCallback((path) => {
105
+ setChanges((prev) => {
106
+ const next = new Map(prev);
107
+ next.delete(path);
108
+ return next;
109
+ });
110
+ }, []);
111
+ const commit = react.useCallback(() => {
112
+ if (changes.size === 0) return;
113
+ if (typeof window === "undefined" || window.parent === window) return;
114
+ setIsCommitting(true);
115
+ setLastCommitError(null);
116
+ try {
117
+ window.parent.postMessage(
118
+ {
119
+ type: "pp:edit:commit-request",
120
+ changes: Array.from(changes.values())
121
+ },
122
+ "*"
123
+ );
124
+ } catch (err) {
125
+ setIsCommitting(false);
126
+ setLastCommitError(err instanceof Error ? err.message : String(err));
127
+ }
128
+ }, [changes]);
129
+ const openMediaPicker = react.useCallback((path, multiple, allowedTypes, currentValue) => {
130
+ if (typeof window === "undefined" || window.parent === window) return;
131
+ try {
132
+ window.parent.postMessage(
133
+ {
134
+ type: "pp:edit:open-media-picker",
135
+ path,
136
+ multiple,
137
+ allowedTypes,
138
+ currentValue
139
+ },
140
+ "*"
141
+ );
142
+ } catch {
143
+ }
144
+ }, []);
145
+ const onMediaPicked = react.useCallback((path, listener) => {
146
+ mediaPickListeners.current.set(path, listener);
147
+ return () => {
148
+ mediaPickListeners.current.delete(path);
149
+ };
150
+ }, []);
151
+ const value = react.useMemo(() => ({
152
+ enabled: urlState.enabled,
153
+ token: urlState.token,
154
+ changes,
155
+ isDirty: changes.size > 0,
156
+ isCommitting,
157
+ lastCommitError,
158
+ setChange,
159
+ clearChange,
160
+ commit,
161
+ openMediaPicker,
162
+ onMediaPicked
163
+ }), [urlState, changes, isCommitting, lastCommitError, setChange, clearChange, commit, openMediaPicker, onMediaPicked]);
164
+ return /* @__PURE__ */ jsxRuntime.jsxs(EditModeContext.Provider, { value, children: [
165
+ children,
166
+ urlState.enabled && /* @__PURE__ */ jsxRuntime.jsx(SaveBar, {})
167
+ ] });
168
+ }
169
+ function SaveBar() {
170
+ const { isDirty, isCommitting, lastCommitError, changes, commit } = useStrapiEditMode();
171
+ if (!isDirty && !lastCommitError && !isCommitting) return null;
172
+ return /* @__PURE__ */ jsxRuntime.jsxs(
173
+ "div",
174
+ {
175
+ style: {
176
+ position: "fixed",
177
+ bottom: 24,
178
+ right: 24,
179
+ zIndex: 999999,
180
+ fontFamily: "system-ui, -apple-system, sans-serif"
181
+ },
182
+ children: [
183
+ lastCommitError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
184
+ background: "#FA501E",
185
+ color: "#000",
186
+ padding: "8px 14px",
187
+ marginBottom: 8,
188
+ fontSize: 13,
189
+ fontWeight: 600,
190
+ maxWidth: 320
191
+ }, children: lastCommitError }),
192
+ /* @__PURE__ */ jsxRuntime.jsx(
193
+ "button",
194
+ {
195
+ type: "button",
196
+ onClick: commit,
197
+ disabled: isCommitting || !isDirty,
198
+ style: {
199
+ background: isCommitting ? "#666" : "#000",
200
+ color: "#EBC6DF",
201
+ border: "2px solid #000",
202
+ padding: "14px 22px",
203
+ fontSize: 13,
204
+ fontWeight: 700,
205
+ textTransform: "uppercase",
206
+ letterSpacing: "0.2em",
207
+ cursor: isCommitting ? "wait" : "pointer",
208
+ display: "flex",
209
+ alignItems: "center",
210
+ gap: 10,
211
+ boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
212
+ transition: "background 0.15s ease"
213
+ },
214
+ onMouseEnter: (e) => {
215
+ if (!isCommitting && isDirty) {
216
+ e.currentTarget.style.background = "#FA501E";
217
+ e.currentTarget.style.color = "#000";
218
+ }
219
+ },
220
+ onMouseLeave: (e) => {
221
+ if (!isCommitting && isDirty) {
222
+ e.currentTarget.style.background = "#000";
223
+ e.currentTarget.style.color = "#EBC6DF";
224
+ }
225
+ },
226
+ children: isCommitting ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Speichere\u2026" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
227
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
228
+ background: "#FA501E",
229
+ color: "#000",
230
+ width: 22,
231
+ height: 22,
232
+ borderRadius: "50%",
233
+ display: "inline-flex",
234
+ alignItems: "center",
235
+ justifyContent: "center",
236
+ fontSize: 11,
237
+ fontWeight: 700
238
+ }, children: changes.size }),
239
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Speichern" })
240
+ ] })
241
+ }
242
+ )
243
+ ]
244
+ }
96
245
  );
97
- if (!enabled) {
246
+ }
247
+ var EDITABLE_TYPES = /* @__PURE__ */ new Set([
248
+ "text",
249
+ "textarea",
250
+ "richText",
251
+ "email",
252
+ "number"
253
+ ]);
254
+ var HOVER_OUTLINE_COLOR = "#FA501E";
255
+ function StrapiField({ path, type, value, children, className }) {
256
+ const ctx = useStrapiEditMode();
257
+ const ref = react.useRef(null);
258
+ const initialText = react.useRef(void 0);
259
+ react.useEffect(() => {
260
+ if (!ctx.enabled) return;
261
+ if (!EDITABLE_TYPES.has(type)) return;
262
+ if (initialText.current === void 0 && ref.current) {
263
+ initialText.current = ref.current.textContent ?? "";
264
+ }
265
+ }, [ctx.enabled, type]);
266
+ if (!ctx.enabled) {
98
267
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
99
268
  }
100
269
  const arr = react.Children.toArray(children);
270
+ const isTextual = EDITABLE_TYPES.has(type);
101
271
  if (arr.length === 1 && react.isValidElement(arr[0])) {
102
272
  const el = arr[0];
103
- const existing = el.props.style ?? {};
273
+ const existingStyle = el.props.style ?? {};
104
274
  const existingClass = el.props.className ?? "";
105
- return react.cloneElement(el, {
275
+ const inlineStyle = {
276
+ outline: "1px dashed transparent",
277
+ outlineOffset: 2,
278
+ transition: "outline-color 0.15s ease",
279
+ cursor: isTextual ? "text" : "pointer",
280
+ ...existingStyle
281
+ };
282
+ const props = {
106
283
  "data-pp-edit": path,
107
284
  "data-pp-type": type,
108
- onClick,
285
+ ref: (node) => {
286
+ ref.current = node;
287
+ },
288
+ className: [existingClass, className, "pp-edit-target"].filter(Boolean).join(" "),
289
+ style: inlineStyle,
109
290
  onMouseEnter: (e) => {
110
- e.currentTarget.style.outlineColor = EDIT_HOVER_STYLE.outlineColor;
291
+ e.currentTarget.style.outlineColor = HOVER_OUTLINE_COLOR;
111
292
  },
112
293
  onMouseLeave: (e) => {
113
294
  e.currentTarget.style.outlineColor = "transparent";
114
- },
115
- style: { ...EDIT_ATTR_STYLE, ...existing },
116
- className: [existingClass, className, "pp-edit-target"].filter(Boolean).join(" ")
117
- });
295
+ }
296
+ };
297
+ if (isTextual) {
298
+ props.contentEditable = "plaintext-only";
299
+ props.suppressContentEditableWarning = true;
300
+ props.onFocus = (e) => {
301
+ e.currentTarget.style.outlineColor = HOVER_OUTLINE_COLOR;
302
+ e.currentTarget.style.outlineStyle = "solid";
303
+ };
304
+ props.onBlur = (e) => {
305
+ const target = e.currentTarget;
306
+ target.style.outlineColor = "transparent";
307
+ target.style.outlineStyle = "dashed";
308
+ const newText = target.textContent ?? "";
309
+ const original = typeof value === "string" ? value : initialText.current ?? "";
310
+ if (newText !== original) {
311
+ const newValue = type === "number" ? newText === "" ? null : Number(newText) : newText;
312
+ ctx.setChange(path, type, newValue);
313
+ }
314
+ };
315
+ props.onKeyDown = (e) => {
316
+ if (e.key === "Enter" && !e.shiftKey && type === "text") {
317
+ e.preventDefault();
318
+ e.currentTarget.blur();
319
+ }
320
+ if (e.key === "Escape") {
321
+ e.preventDefault();
322
+ e.currentTarget.blur();
323
+ }
324
+ };
325
+ }
326
+ return react.cloneElement(el, props);
118
327
  }
119
328
  return /* @__PURE__ */ jsxRuntime.jsx(
120
329
  "span",
121
330
  {
122
331
  "data-pp-edit": path,
123
332
  "data-pp-type": type,
124
- onClick,
125
- onMouseEnter: (e) => {
126
- e.currentTarget.style.outlineColor = EDIT_HOVER_STYLE.outlineColor;
127
- },
128
- onMouseLeave: (e) => {
129
- e.currentTarget.style.outlineColor = "transparent";
130
- },
131
- style: EDIT_ATTR_STYLE,
132
333
  className: ["pp-edit-target", className].filter(Boolean).join(" "),
334
+ style: { outline: "1px dashed transparent", outlineOffset: 2 },
133
335
  children
134
336
  }
135
337
  );
@@ -140,18 +342,68 @@ function StrapiText({ path, value, fieldType = "text", as = "span", className, c
140
342
  return /* @__PURE__ */ jsxRuntime.jsx(StrapiField, { path, type: fieldType, value, children: el });
141
343
  }
142
344
  function resolveUrl(value, baseUrl) {
143
- const u = value.url;
144
- if (u.startsWith("http")) return u;
145
- if (baseUrl) return `${baseUrl.replace(/\/$/, "")}${u.startsWith("/") ? "" : "/"}${u}`;
146
- return u;
345
+ if (value.url.startsWith("http")) return value.url;
346
+ if (baseUrl) return `${baseUrl.replace(/\/$/, "")}${value.url.startsWith("/") ? "" : "/"}${value.url}`;
347
+ return value.url;
147
348
  }
148
- function StrapiImage({ path, value, baseUrl, alt, className, style, loading = "lazy", fallback }) {
349
+ var HOVER_OUTLINE_COLOR2 = "#FA501E";
350
+ function StrapiImage({ path, value, baseUrl, alt, className, style, loading = "lazy", fallback, allowedTypes, multiple = false }) {
351
+ const ctx = useStrapiEditMode();
352
+ const containerRef = react.useRef(null);
353
+ const imgRef = react.useRef(null);
354
+ const handleClick = (e) => {
355
+ if (!ctx.enabled) return;
356
+ e.preventDefault();
357
+ e.stopPropagation();
358
+ const unsubscribe = ctx.onMediaPicked(path, (picked) => {
359
+ ctx.setChange(path, "media", picked);
360
+ unsubscribe();
361
+ if (imgRef.current && picked && typeof picked === "object" && !Array.isArray(picked) && "url" in picked) {
362
+ imgRef.current.src = resolveUrl(picked, baseUrl);
363
+ }
364
+ });
365
+ ctx.openMediaPicker(path, multiple, allowedTypes, value);
366
+ };
149
367
  if (!value) {
150
- return /* @__PURE__ */ jsxRuntime.jsx(StrapiField, { path, type: "media", value: null, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className, style, children: fallback ?? "" }) });
368
+ if (!ctx.enabled) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback ?? null });
369
+ return /* @__PURE__ */ jsxRuntime.jsx(
370
+ "span",
371
+ {
372
+ ref: containerRef,
373
+ "data-pp-edit": path,
374
+ "data-pp-type": "media",
375
+ onClick: handleClick,
376
+ onMouseEnter: (e) => {
377
+ e.currentTarget.style.outlineColor = HOVER_OUTLINE_COLOR2;
378
+ },
379
+ onMouseLeave: (e) => {
380
+ e.currentTarget.style.outlineColor = "transparent";
381
+ },
382
+ style: {
383
+ display: "inline-flex",
384
+ alignItems: "center",
385
+ justifyContent: "center",
386
+ background: "rgba(0,0,0,0.05)",
387
+ border: "1px dashed rgba(0,0,0,0.3)",
388
+ outline: "1px dashed transparent",
389
+ outlineOffset: 2,
390
+ cursor: "pointer",
391
+ padding: "20px 28px",
392
+ fontSize: 12,
393
+ fontWeight: 700,
394
+ textTransform: "uppercase",
395
+ letterSpacing: "0.15em",
396
+ color: "#000",
397
+ transition: "outline-color 0.15s ease"
398
+ },
399
+ children: "+ Bild hinzuf\xFCgen"
400
+ }
401
+ );
151
402
  }
152
- return /* @__PURE__ */ jsxRuntime.jsx(StrapiField, { path, type: "media", value, children: /* @__PURE__ */ jsxRuntime.jsx(
403
+ const imgEl = /* @__PURE__ */ jsxRuntime.jsx(
153
404
  "img",
154
405
  {
406
+ ref: imgRef,
155
407
  src: resolveUrl(value, baseUrl),
156
408
  alt: alt ?? value.alternativeText ?? value.name ?? "",
157
409
  width: value.width,
@@ -160,7 +412,64 @@ function StrapiImage({ path, value, baseUrl, alt, className, style, loading = "l
160
412
  className,
161
413
  style
162
414
  }
163
- ) });
415
+ );
416
+ if (!ctx.enabled) return imgEl;
417
+ return /* @__PURE__ */ jsxRuntime.jsxs(
418
+ "span",
419
+ {
420
+ ref: containerRef,
421
+ "data-pp-edit": path,
422
+ "data-pp-type": "media",
423
+ style: { position: "relative", display: "inline-block", outline: "1px dashed transparent", outlineOffset: 2, transition: "outline-color 0.15s ease" },
424
+ onMouseEnter: (e) => {
425
+ e.currentTarget.style.outlineColor = HOVER_OUTLINE_COLOR2;
426
+ const btn = e.currentTarget.querySelector("[data-pp-edit-btn]");
427
+ if (btn) btn.style.opacity = "1";
428
+ },
429
+ onMouseLeave: (e) => {
430
+ e.currentTarget.style.outlineColor = "transparent";
431
+ const btn = e.currentTarget.querySelector("[data-pp-edit-btn]");
432
+ if (btn) btn.style.opacity = "0";
433
+ },
434
+ children: [
435
+ imgEl,
436
+ /* @__PURE__ */ jsxRuntime.jsx(
437
+ "button",
438
+ {
439
+ type: "button",
440
+ "data-pp-edit-btn": true,
441
+ onClick: handleClick,
442
+ style: {
443
+ position: "absolute",
444
+ top: 8,
445
+ right: 8,
446
+ background: "#000",
447
+ color: "#EBC6DF",
448
+ border: "2px solid #000",
449
+ padding: "6px 10px",
450
+ fontSize: 11,
451
+ fontWeight: 700,
452
+ textTransform: "uppercase",
453
+ letterSpacing: "0.15em",
454
+ cursor: "pointer",
455
+ opacity: 0,
456
+ transition: "opacity 0.15s ease, background 0.15s ease",
457
+ fontFamily: "system-ui, -apple-system, sans-serif"
458
+ },
459
+ onMouseEnter: (e) => {
460
+ e.currentTarget.style.background = "#FA501E";
461
+ e.currentTarget.style.color = "#000";
462
+ },
463
+ onMouseLeave: (e) => {
464
+ e.currentTarget.style.background = "#000";
465
+ e.currentTarget.style.color = "#EBC6DF";
466
+ },
467
+ children: "\xC4ndern"
468
+ }
469
+ )
470
+ ]
471
+ }
472
+ );
164
473
  }
165
474
  function StrapiList({ path, value, renderItem, fallback }) {
166
475
  if (!Array.isArray(value) || value.length === 0) {
@@ -168,7 +477,157 @@ function StrapiList({ path, value, renderItem, fallback }) {
168
477
  }
169
478
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: value.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: renderItem(item, `${path}.${i}`, i) }, i)) });
170
479
  }
480
+ var HOVER_OUTLINE = "#FA501E";
481
+ function renderInline(node, key) {
482
+ if (node.type === "link") {
483
+ return /* @__PURE__ */ jsxRuntime.jsx("a", { href: node.url, style: { fontWeight: 700, textDecoration: "underline", textUnderlineOffset: 4 }, children: (node.children ?? []).map((c, i) => renderInline(c, i)) }, key);
484
+ }
485
+ let el = node.text;
486
+ if (node.code) el = /* @__PURE__ */ jsxRuntime.jsx("code", { style: { background: "rgba(0,0,0,0.08)", padding: "0 4px", fontFamily: "ui-monospace, monospace", fontSize: "0.9em" }, children: el });
487
+ if (node.bold) el = /* @__PURE__ */ jsxRuntime.jsx("strong", { children: el });
488
+ if (node.italic) el = /* @__PURE__ */ jsxRuntime.jsx("em", { children: el });
489
+ if (node.underline) el = /* @__PURE__ */ jsxRuntime.jsx("u", { children: el });
490
+ if (node.strikethrough) el = /* @__PURE__ */ jsxRuntime.jsx("s", { children: el });
491
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: el }, key);
492
+ }
493
+ function renderBlock(b, key) {
494
+ if (b.type === "paragraph") {
495
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { children: (b.children ?? []).map((c, i) => renderInline(c, i)) }, key);
496
+ }
497
+ if (b.type === "heading") {
498
+ const Tag = `h${Math.min(Math.max(b.level ?? 2, 1), 6)}`;
499
+ return /* @__PURE__ */ jsxRuntime.jsx(Tag, { children: (b.children ?? []).map((c, i) => renderInline(c, i)) }, key);
500
+ }
501
+ if (b.type === "quote") {
502
+ return /* @__PURE__ */ jsxRuntime.jsx("blockquote", { children: (b.children ?? []).map((c, i) => renderInline(c, i)) }, key);
503
+ }
504
+ if (b.type === "list") {
505
+ const ListTag = b.format === "ordered" ? "ol" : "ul";
506
+ return /* @__PURE__ */ jsxRuntime.jsx(ListTag, { children: (b.children ?? []).map((li, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: (li.children ?? []).map((c, j) => renderInline(c, j)) }, i)) }, key);
507
+ }
508
+ if (b.type === "code") {
509
+ return /* @__PURE__ */ jsxRuntime.jsx("pre", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: (b.children ?? []).map((c) => c.text).join("") }) }, key);
510
+ }
511
+ if (b.type === "image") {
512
+ return /* @__PURE__ */ jsxRuntime.jsx("img", { src: b.image.url, alt: b.image.alternativeText ?? "" }, key);
513
+ }
514
+ return null;
515
+ }
516
+ function parseInline(node) {
517
+ const out = [];
518
+ for (const child of Array.from(node.childNodes)) {
519
+ if (child.nodeType === Node.TEXT_NODE) {
520
+ const text = child.textContent ?? "";
521
+ if (text) out.push({ type: "text", text });
522
+ } else if (child.nodeType === Node.ELEMENT_NODE) {
523
+ const el = child;
524
+ const tag = el.tagName.toLowerCase();
525
+ if (tag === "br") {
526
+ out.push({ type: "text", text: "\n" });
527
+ continue;
528
+ }
529
+ if (tag === "a") {
530
+ const inner2 = parseInline(el).filter((n) => n.type === "text");
531
+ out.push({ type: "link", url: el.getAttribute("href") ?? "", children: inner2.length > 0 ? inner2 : [{ type: "text", text: el.textContent ?? "" }] });
532
+ continue;
533
+ }
534
+ const inner = parseInline(el);
535
+ const flagged = inner.map((n) => {
536
+ if (n.type !== "text") return n;
537
+ const t = { ...n };
538
+ if (tag === "strong" || tag === "b") t.bold = true;
539
+ if (tag === "em" || tag === "i") t.italic = true;
540
+ if (tag === "u") t.underline = true;
541
+ if (tag === "s" || tag === "strike" || tag === "del") t.strikethrough = true;
542
+ if (tag === "code") t.code = true;
543
+ return t;
544
+ });
545
+ out.push(...flagged);
546
+ }
547
+ }
548
+ return out;
549
+ }
550
+ function parseBlocksFromDOM(root) {
551
+ const blocks = [];
552
+ for (const child of Array.from(root.children)) {
553
+ const el = child;
554
+ const tag = el.tagName.toLowerCase();
555
+ if (tag === "p") {
556
+ blocks.push({ type: "paragraph", children: parseInline(el) });
557
+ } else if (/^h[1-6]$/.test(tag)) {
558
+ const level = parseInt(tag.slice(1), 10);
559
+ blocks.push({ type: "heading", level, children: parseInline(el) });
560
+ } else if (tag === "blockquote") {
561
+ blocks.push({ type: "quote", children: parseInline(el) });
562
+ } else if (tag === "ul" || tag === "ol") {
563
+ const items = [];
564
+ for (const li of Array.from(el.querySelectorAll(":scope > li"))) {
565
+ items.push({ type: "list-item", children: parseInline(li) });
566
+ }
567
+ blocks.push({ type: "list", format: tag === "ol" ? "ordered" : "unordered", children: items });
568
+ } else if (tag === "pre") {
569
+ blocks.push({ type: "code", children: [{ type: "text", text: el.textContent ?? "" }] });
570
+ } else if (tag === "img") {
571
+ blocks.push({ type: "image", image: { url: el.getAttribute("src") ?? "", alternativeText: el.getAttribute("alt") ?? "" } });
572
+ } else {
573
+ blocks.push({ type: "paragraph", children: parseInline(el) });
574
+ }
575
+ }
576
+ return blocks;
577
+ }
578
+ function StrapiBlocks({ path, value, className }) {
579
+ const ctx = useStrapiEditMode();
580
+ const containerRef = react.useRef(null);
581
+ const initialValue = react.useRef(null);
582
+ const blocks = Array.isArray(value) ? value : [];
583
+ react.useEffect(() => {
584
+ if (initialValue.current === null) initialValue.current = blocks;
585
+ }, [blocks]);
586
+ if (!ctx.enabled) {
587
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: blocks.map((b, i) => renderBlock(b, i)) });
588
+ }
589
+ return /* @__PURE__ */ jsxRuntime.jsx(
590
+ "div",
591
+ {
592
+ ref: containerRef,
593
+ className,
594
+ contentEditable: true,
595
+ suppressContentEditableWarning: true,
596
+ "data-pp-edit": path,
597
+ "data-pp-type": "blocks",
598
+ style: {
599
+ outline: "1px dashed transparent",
600
+ outlineOffset: 4,
601
+ transition: "outline-color 0.15s ease",
602
+ minHeight: "1em"
603
+ },
604
+ onMouseEnter: (e) => {
605
+ e.currentTarget.style.outlineColor = HOVER_OUTLINE;
606
+ },
607
+ onMouseLeave: (e) => {
608
+ e.currentTarget.style.outlineColor = "transparent";
609
+ },
610
+ onFocus: (e) => {
611
+ e.currentTarget.style.outlineColor = HOVER_OUTLINE;
612
+ e.currentTarget.style.outlineStyle = "solid";
613
+ },
614
+ onBlur: (e) => {
615
+ const root = e.currentTarget;
616
+ const parsed = parseBlocksFromDOM(root);
617
+ const before = JSON.stringify(initialValue.current);
618
+ const after = JSON.stringify(parsed);
619
+ if (before !== after) {
620
+ ctx.setChange(path, "blocks", parsed);
621
+ }
622
+ e.currentTarget.style.outlineColor = "transparent";
623
+ e.currentTarget.style.outlineStyle = "dashed";
624
+ },
625
+ children: blocks.map((b, i) => renderBlock(b, i))
626
+ }
627
+ );
628
+ }
171
629
 
630
+ exports.StrapiBlocks = StrapiBlocks;
172
631
  exports.StrapiEditModeProvider = StrapiEditModeProvider;
173
632
  exports.StrapiField = StrapiField;
174
633
  exports.StrapiImage = StrapiImage;