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