@paulpaulstudio/strapi-render 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -134,15 +134,19 @@ interface StrapiImageProps {
134
134
  fallback?: React.ReactNode;
135
135
  allowedTypes?: string[];
136
136
  multiple?: boolean;
137
- /** Optional width override (default: value.width). Akzeptiert `<img>`-Attribute. */
138
137
  width?: number | string;
139
- /** Optional height override (default: value.height). */
140
138
  height?: number | string;
141
- /** Optional ARIA/Image-Attribute, falls vom Customer gebraucht. */
142
139
  draggable?: boolean;
143
140
  priority?: boolean;
144
141
  }
145
- declare function StrapiImage({ path, value, baseUrl, alt, className, style, loading, fallback, allowedTypes, multiple, width, height, draggable }: StrapiImageProps): react_jsx_runtime.JSX.Element;
142
+ /**
143
+ * Render-Mode: identisches HTML wie ein nativer <img>.
144
+ *
145
+ * Edit-Mode: derselbe <img>, aber mit Click-Handler + Hover-Outline. KEIN
146
+ * extra Wrapper-Element — Layouts mit `position:absolute; inset:0` o.ä.
147
+ * funktionieren exakt wie ohne SDK.
148
+ */
149
+ declare function StrapiImage({ path, value, baseUrl, alt, className, style, loading, fallback, allowedTypes, multiple, width, height, draggable, priority: _ignored, }: StrapiImageProps): react_jsx_runtime.JSX.Element;
146
150
 
147
151
  interface StrapiListProps<T> {
148
152
  /** Pfad zum Array-Feld, z.B. "features" */
@@ -171,14 +175,14 @@ interface StrapiListProps<T> {
171
175
  */
172
176
  declare function StrapiList<T>({ path, value, renderItem, fallback }: StrapiListProps<T>): react_jsx_runtime.JSX.Element;
173
177
 
174
- interface Props$1 {
178
+ interface Props$2 {
175
179
  path: string;
176
180
  value: unknown;
177
181
  className?: string;
178
182
  }
179
- declare function StrapiBlocks({ path, value, className }: Props$1): react_jsx_runtime.JSX.Element;
183
+ declare function StrapiBlocks({ path, value, className }: Props$2): react_jsx_runtime.JSX.Element;
180
184
 
181
- interface Props {
185
+ interface Props$1 {
182
186
  path: string;
183
187
  /** Markdown-Quelle aus Strapi. */
184
188
  value: string | null | undefined;
@@ -207,6 +211,29 @@ interface Props {
207
211
  * className="prose"
208
212
  * />
209
213
  */
210
- declare function StrapiMarkdownField({ path, value, render, className, minRows }: Props): react_jsx_runtime.JSX.Element;
214
+ declare function StrapiMarkdownField({ path, value, render, className, minRows }: Props$1): react_jsx_runtime.JSX.Element;
215
+
216
+ interface Props {
217
+ path: string;
218
+ /** Markdown-Quelle aus Strapi. */
219
+ value: string | null | undefined;
220
+ /** Render-Funktion für Read-Mode (z.B. ReactMarkdown). Wird auch im Edit-Mode
221
+ * initial gerendert — der User editiert dann direkt das ergebende HTML. */
222
+ render: (markdown: string) => ReactNode;
223
+ className?: string;
224
+ }
225
+ /**
226
+ * WYSIWYG-RichText.
227
+ *
228
+ * Read-Mode: ruft `render(value)` auf — das ist der bereits gerenderte
229
+ * Inhalt (z.B. ReactMarkdown → HTML). Keine Modifikation.
230
+ *
231
+ * Edit-Mode: derselbe gerenderte HTML-Inhalt wird in einem `contentEditable`-
232
+ * Container angezeigt. Der User editiert das HTML inline (Word-Style). Beim
233
+ * Blur wird das HTML via Turndown wieder in Markdown zurückgewandelt und an
234
+ * den Parent geschickt — damit die Strapi-Quelle Markdown bleibt + ohne
235
+ * Verluste round-trippt (Headings, Listen, Links, Bold/Italic bleiben).
236
+ */
237
+ declare function StrapiRichTextField({ path, value, render, className }: Props): react_jsx_runtime.JSX.Element;
211
238
 
212
- export { type MsgEditChange, type MsgEditCommitRequest, type MsgEditCommitResult, type MsgEditMediaPicked, type MsgEditOpenMediaPicker, type MsgEditReady, type MsgEditReload, StrapiBlocks, StrapiContent, type StrapiEditMessage, StrapiEditModeProvider, StrapiField, type StrapiFieldType, StrapiImage, StrapiList, StrapiMarkdownField, type StrapiMediaValue, StrapiText, useContentScope, useStrapiEditMode };
239
+ export { type MsgEditChange, type MsgEditCommitRequest, type MsgEditCommitResult, type MsgEditMediaPicked, type MsgEditOpenMediaPicker, type MsgEditReady, type MsgEditReload, StrapiBlocks, StrapiContent, type StrapiEditMessage, StrapiEditModeProvider, StrapiField, type StrapiFieldType, StrapiImage, StrapiList, StrapiMarkdownField, type StrapiMediaValue, StrapiRichTextField, StrapiText, useContentScope, useStrapiEditMode };
package/dist/index.d.ts CHANGED
@@ -134,15 +134,19 @@ interface StrapiImageProps {
134
134
  fallback?: React.ReactNode;
135
135
  allowedTypes?: string[];
136
136
  multiple?: boolean;
137
- /** Optional width override (default: value.width). Akzeptiert `<img>`-Attribute. */
138
137
  width?: number | string;
139
- /** Optional height override (default: value.height). */
140
138
  height?: number | string;
141
- /** Optional ARIA/Image-Attribute, falls vom Customer gebraucht. */
142
139
  draggable?: boolean;
143
140
  priority?: boolean;
144
141
  }
145
- declare function StrapiImage({ path, value, baseUrl, alt, className, style, loading, fallback, allowedTypes, multiple, width, height, draggable }: StrapiImageProps): react_jsx_runtime.JSX.Element;
142
+ /**
143
+ * Render-Mode: identisches HTML wie ein nativer <img>.
144
+ *
145
+ * Edit-Mode: derselbe <img>, aber mit Click-Handler + Hover-Outline. KEIN
146
+ * extra Wrapper-Element — Layouts mit `position:absolute; inset:0` o.ä.
147
+ * funktionieren exakt wie ohne SDK.
148
+ */
149
+ declare function StrapiImage({ path, value, baseUrl, alt, className, style, loading, fallback, allowedTypes, multiple, width, height, draggable, priority: _ignored, }: StrapiImageProps): react_jsx_runtime.JSX.Element;
146
150
 
147
151
  interface StrapiListProps<T> {
148
152
  /** Pfad zum Array-Feld, z.B. "features" */
@@ -171,14 +175,14 @@ interface StrapiListProps<T> {
171
175
  */
172
176
  declare function StrapiList<T>({ path, value, renderItem, fallback }: StrapiListProps<T>): react_jsx_runtime.JSX.Element;
173
177
 
174
- interface Props$1 {
178
+ interface Props$2 {
175
179
  path: string;
176
180
  value: unknown;
177
181
  className?: string;
178
182
  }
179
- declare function StrapiBlocks({ path, value, className }: Props$1): react_jsx_runtime.JSX.Element;
183
+ declare function StrapiBlocks({ path, value, className }: Props$2): react_jsx_runtime.JSX.Element;
180
184
 
181
- interface Props {
185
+ interface Props$1 {
182
186
  path: string;
183
187
  /** Markdown-Quelle aus Strapi. */
184
188
  value: string | null | undefined;
@@ -207,6 +211,29 @@ interface Props {
207
211
  * className="prose"
208
212
  * />
209
213
  */
210
- declare function StrapiMarkdownField({ path, value, render, className, minRows }: Props): react_jsx_runtime.JSX.Element;
214
+ declare function StrapiMarkdownField({ path, value, render, className, minRows }: Props$1): react_jsx_runtime.JSX.Element;
215
+
216
+ interface Props {
217
+ path: string;
218
+ /** Markdown-Quelle aus Strapi. */
219
+ value: string | null | undefined;
220
+ /** Render-Funktion für Read-Mode (z.B. ReactMarkdown). Wird auch im Edit-Mode
221
+ * initial gerendert — der User editiert dann direkt das ergebende HTML. */
222
+ render: (markdown: string) => ReactNode;
223
+ className?: string;
224
+ }
225
+ /**
226
+ * WYSIWYG-RichText.
227
+ *
228
+ * Read-Mode: ruft `render(value)` auf — das ist der bereits gerenderte
229
+ * Inhalt (z.B. ReactMarkdown → HTML). Keine Modifikation.
230
+ *
231
+ * Edit-Mode: derselbe gerenderte HTML-Inhalt wird in einem `contentEditable`-
232
+ * Container angezeigt. Der User editiert das HTML inline (Word-Style). Beim
233
+ * Blur wird das HTML via Turndown wieder in Markdown zurückgewandelt und an
234
+ * den Parent geschickt — damit die Strapi-Quelle Markdown bleibt + ohne
235
+ * Verluste round-trippt (Headings, Listen, Links, Bold/Italic bleiben).
236
+ */
237
+ declare function StrapiRichTextField({ path, value, render, className }: Props): react_jsx_runtime.JSX.Element;
211
238
 
212
- export { type MsgEditChange, type MsgEditCommitRequest, type MsgEditCommitResult, type MsgEditMediaPicked, type MsgEditOpenMediaPicker, type MsgEditReady, type MsgEditReload, StrapiBlocks, StrapiContent, type StrapiEditMessage, StrapiEditModeProvider, StrapiField, type StrapiFieldType, StrapiImage, StrapiList, StrapiMarkdownField, type StrapiMediaValue, StrapiText, useContentScope, useStrapiEditMode };
239
+ export { type MsgEditChange, type MsgEditCommitRequest, type MsgEditCommitResult, type MsgEditMediaPicked, type MsgEditOpenMediaPicker, type MsgEditReady, type MsgEditReload, StrapiBlocks, StrapiContent, type StrapiEditMessage, StrapiEditModeProvider, StrapiField, type StrapiFieldType, StrapiImage, StrapiList, StrapiMarkdownField, type StrapiMediaValue, StrapiRichTextField, StrapiText, useContentScope, useStrapiEditMode };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { createContext, useContext, useState, useRef, useEffect, useCallback, useMemo, Children, isValidElement, cloneElement, createElement, Fragment as Fragment$1 } from 'react';
3
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
+ import TurndownService from 'turndown';
4
5
 
5
6
  var EDIT_SESSION_KEY = "__pp_edit_session";
6
7
  var noop = () => {
@@ -233,10 +234,24 @@ function resolveUrl(value, baseUrl) {
233
234
  return value.url;
234
235
  }
235
236
  var HOVER_OUTLINE_COLOR2 = "#FA501E";
236
- function StrapiImage({ path, value, baseUrl, alt, className, style, loading = "lazy", fallback, allowedTypes, multiple = false, width, height, draggable }) {
237
+ function StrapiImage({
238
+ path,
239
+ value,
240
+ baseUrl,
241
+ alt,
242
+ className,
243
+ style,
244
+ loading = "lazy",
245
+ fallback,
246
+ allowedTypes,
247
+ multiple = false,
248
+ width,
249
+ height,
250
+ draggable,
251
+ priority: _ignored
252
+ }) {
237
253
  const ctx = useStrapiEditMode();
238
254
  const scope = useContentScope();
239
- const containerRef = useRef(null);
240
255
  const imgRef = useRef(null);
241
256
  const handleClick = (e) => {
242
257
  if (!ctx.enabled) return;
@@ -254,40 +269,38 @@ function StrapiImage({ path, value, baseUrl, alt, className, style, loading = "l
254
269
  if (!value) {
255
270
  if (!ctx.enabled) return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? null });
256
271
  return /* @__PURE__ */ jsx(
257
- "span",
272
+ "button",
258
273
  {
259
- ref: containerRef,
260
- "data-pp-edit": path,
261
- "data-pp-type": "media",
274
+ type: "button",
262
275
  onClick: handleClick,
263
- onMouseEnter: (e) => {
264
- e.currentTarget.style.outlineColor = HOVER_OUTLINE_COLOR2;
265
- },
266
- onMouseLeave: (e) => {
267
- e.currentTarget.style.outlineColor = "transparent";
268
- },
276
+ className,
269
277
  style: {
270
278
  display: "inline-flex",
271
279
  alignItems: "center",
272
280
  justifyContent: "center",
273
281
  background: "rgba(0,0,0,0.05)",
274
282
  border: "1px dashed rgba(0,0,0,0.3)",
275
- outline: "1px dashed transparent",
276
- outlineOffset: 2,
277
- cursor: "pointer",
278
283
  padding: "20px 28px",
279
284
  fontSize: 12,
280
285
  fontWeight: 700,
281
286
  textTransform: "uppercase",
282
287
  letterSpacing: "0.15em",
283
288
  color: "#000",
284
- transition: "outline-color 0.15s ease"
289
+ cursor: "pointer",
290
+ ...style
285
291
  },
286
292
  children: "+ Bild hinzuf\xFCgen"
287
293
  }
288
294
  );
289
295
  }
290
- const imgEl = /* @__PURE__ */ jsx(
296
+ const editStyle = ctx.enabled ? {
297
+ ...style,
298
+ outline: "1px dashed transparent",
299
+ outlineOffset: 2,
300
+ cursor: "pointer",
301
+ transition: "outline-color 0.15s ease"
302
+ } : style ?? {};
303
+ return /* @__PURE__ */ jsx(
291
304
  "img",
292
305
  {
293
306
  ref: imgRef,
@@ -297,74 +310,21 @@ function StrapiImage({ path, value, baseUrl, alt, className, style, loading = "l
297
310
  height: height ?? value.height,
298
311
  loading,
299
312
  className,
300
- style,
301
- draggable
302
- }
303
- );
304
- if (!ctx.enabled) return imgEl;
305
- return /* @__PURE__ */ jsxs(
306
- "span",
307
- {
308
- ref: containerRef,
309
- "data-pp-edit": path,
310
- "data-pp-type": "media",
311
- style: {
312
- position: "relative",
313
- display: "block",
314
- // Wrapper ist block-level damit das innere img mit className w-full / h-[60vh] etc.
315
- // die korrekte Größe ausfüllt. Im Read-Mode (kein Wrapper) hatte das img direkt
316
- // diese Größe — beim Wrap mit inline-block-span schrumpfte das.
317
- outline: "1px dashed transparent",
318
- outlineOffset: 2,
319
- transition: "outline-color 0.15s ease"
320
- },
321
- onMouseEnter: (e) => {
322
- e.currentTarget.style.outlineColor = HOVER_OUTLINE_COLOR2;
323
- const btn = e.currentTarget.querySelector("[data-pp-edit-btn]");
324
- if (btn) btn.style.opacity = "1";
325
- },
326
- onMouseLeave: (e) => {
327
- e.currentTarget.style.outlineColor = "transparent";
328
- const btn = e.currentTarget.querySelector("[data-pp-edit-btn]");
329
- if (btn) btn.style.opacity = "0";
330
- },
331
- children: [
332
- imgEl,
333
- /* @__PURE__ */ jsx(
334
- "button",
335
- {
336
- type: "button",
337
- "data-pp-edit-btn": true,
338
- onClick: handleClick,
339
- style: {
340
- position: "absolute",
341
- top: 8,
342
- right: 8,
343
- background: "#000",
344
- color: "#EBC6DF",
345
- border: "2px solid #000",
346
- padding: "6px 10px",
347
- fontSize: 11,
348
- fontWeight: 700,
349
- textTransform: "uppercase",
350
- letterSpacing: "0.15em",
351
- cursor: "pointer",
352
- opacity: 0,
353
- transition: "opacity 0.15s ease, background 0.15s ease",
354
- fontFamily: "system-ui, -apple-system, sans-serif"
355
- },
356
- onMouseEnter: (e) => {
357
- e.currentTarget.style.background = "#FA501E";
358
- e.currentTarget.style.color = "#000";
359
- },
360
- onMouseLeave: (e) => {
361
- e.currentTarget.style.background = "#000";
362
- e.currentTarget.style.color = "#EBC6DF";
363
- },
364
- children: "\xC4ndern"
365
- }
366
- )
367
- ]
313
+ style: editStyle,
314
+ draggable,
315
+ "data-pp-edit": ctx.enabled ? path : void 0,
316
+ "data-pp-type": ctx.enabled ? "media" : void 0,
317
+ onClick: ctx.enabled ? handleClick : void 0,
318
+ onMouseEnter: ctx.enabled ? (e) => {
319
+ const el = e.currentTarget;
320
+ el.style.outlineColor = HOVER_OUTLINE_COLOR2;
321
+ el.style.outlineStyle = "solid";
322
+ } : void 0,
323
+ onMouseLeave: ctx.enabled ? (e) => {
324
+ const el = e.currentTarget;
325
+ el.style.outlineColor = "transparent";
326
+ el.style.outlineStyle = "dashed";
327
+ } : void 0
368
328
  }
369
329
  );
370
330
  }
@@ -592,7 +552,96 @@ function StrapiMarkdownField({ path, value, render, className, minRows = 12 }) {
592
552
  }, children: "Markdown \u2014 **fett**, *kursiv*, ## \xDCberschrift, [link](url)" })
593
553
  ] });
594
554
  }
555
+ var HOVER_OUTLINE3 = "#FA501E";
556
+ function StrapiRichTextField({ path, value, render, className }) {
557
+ const ctx = useStrapiEditMode();
558
+ const scope = useContentScope();
559
+ const containerRef = useRef(null);
560
+ const turndownRef = useRef(null);
561
+ const initial = useRef(value ?? "");
562
+ const [, force] = useState({});
563
+ if (typeof window !== "undefined" && !turndownRef.current) {
564
+ const td = new TurndownService({
565
+ headingStyle: "atx",
566
+ hr: "---",
567
+ bulletListMarker: "-",
568
+ codeBlockStyle: "fenced",
569
+ emDelimiter: "*"
570
+ });
571
+ td.addRule("br", {
572
+ filter: "br",
573
+ replacement: () => "\n"
574
+ });
575
+ turndownRef.current = td;
576
+ }
577
+ useEffect(() => {
578
+ initial.current = value ?? "";
579
+ force({});
580
+ }, [value]);
581
+ if (!ctx.enabled) {
582
+ return /* @__PURE__ */ jsx("div", { className, children: render(value ?? "") });
583
+ }
584
+ return /* @__PURE__ */ jsx(
585
+ "div",
586
+ {
587
+ ref: containerRef,
588
+ className,
589
+ contentEditable: true,
590
+ suppressContentEditableWarning: true,
591
+ "data-pp-edit": path,
592
+ "data-pp-type": "richText",
593
+ style: {
594
+ outline: "1px dashed transparent",
595
+ outlineOffset: 4,
596
+ transition: "outline-color 0.15s ease",
597
+ cursor: "text"
598
+ },
599
+ onMouseEnter: (e) => {
600
+ e.currentTarget.style.outlineColor = HOVER_OUTLINE3;
601
+ },
602
+ onMouseLeave: (e) => {
603
+ if (document.activeElement !== e.currentTarget) {
604
+ e.currentTarget.style.outlineColor = "transparent";
605
+ }
606
+ },
607
+ onFocus: (e) => {
608
+ e.currentTarget.style.outlineColor = HOVER_OUTLINE3;
609
+ e.currentTarget.style.outlineStyle = "solid";
610
+ },
611
+ onBlur: (e) => {
612
+ const el = e.currentTarget;
613
+ el.style.outlineColor = "transparent";
614
+ el.style.outlineStyle = "dashed";
615
+ if (!turndownRef.current) return;
616
+ const html = el.innerHTML;
617
+ const md = turndownRef.current.turndown(html).trim();
618
+ if (md !== initial.current.trim()) {
619
+ postEditChange(scope, path, "richText", md);
620
+ }
621
+ },
622
+ onKeyDown: (e) => {
623
+ if (e.metaKey || e.ctrlKey) {
624
+ if (e.key === "b") {
625
+ e.preventDefault();
626
+ document.execCommand("bold");
627
+ } else if (e.key === "i") {
628
+ e.preventDefault();
629
+ document.execCommand("italic");
630
+ } else if (e.key === "u") {
631
+ e.preventDefault();
632
+ document.execCommand("underline");
633
+ } else if (e.key === "k") {
634
+ e.preventDefault();
635
+ const url = window.prompt("Link-URL:");
636
+ if (url) document.execCommand("createLink", false, url);
637
+ }
638
+ }
639
+ },
640
+ children: render(value ?? "")
641
+ }
642
+ );
643
+ }
595
644
 
596
- export { StrapiBlocks, StrapiContent, StrapiEditModeProvider, StrapiField, StrapiImage, StrapiList, StrapiMarkdownField, StrapiText, useContentScope, useStrapiEditMode };
645
+ export { StrapiBlocks, StrapiContent, StrapiEditModeProvider, StrapiField, StrapiImage, StrapiList, StrapiMarkdownField, StrapiRichTextField, StrapiText, useContentScope, useStrapiEditMode };
597
646
  //# sourceMappingURL=index.js.map
598
647
  //# sourceMappingURL=index.js.map