@withwiz/block-editor 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.
Files changed (40) hide show
  1. package/README.md +219 -0
  2. package/dist/blocks/built-in.d.ts +6 -0
  3. package/dist/blocks/built-in.js +11 -0
  4. package/dist/chunk-3R3HAGQL.js +102 -0
  5. package/dist/chunk-62BAOSP6.js +100 -0
  6. package/dist/chunk-CJGZUEQO.js +270 -0
  7. package/dist/chunk-CLC3FEL2.js +313 -0
  8. package/dist/chunk-CYMYM7LP.js +25 -0
  9. package/dist/chunk-EERQYNER.js +123 -0
  10. package/dist/chunk-G6J2DCC5.js +77 -0
  11. package/dist/chunk-N3ETBM74.js +24 -0
  12. package/dist/chunk-PPVXNJWI.js +28 -0
  13. package/dist/chunk-QR225IXX.js +148 -0
  14. package/dist/chunk-VIJV6FLT.js +250 -0
  15. package/dist/components/ArtistEditor.d.ts +12 -0
  16. package/dist/components/ArtistEditor.js +11 -0
  17. package/dist/components/BlockEditor.d.ts +24 -0
  18. package/dist/components/BlockEditor.js +16 -0
  19. package/dist/components/BlockRenderer.d.ts +10 -0
  20. package/dist/components/BlockRenderer.js +12 -0
  21. package/dist/components/ImageUploadField.d.ts +11 -0
  22. package/dist/components/ImageUploadField.js +11 -0
  23. package/dist/context/BlockEditorProvider.d.ts +21 -0
  24. package/dist/context/BlockEditorProvider.js +10 -0
  25. package/dist/core/html-renderer.d.ts +13 -0
  26. package/dist/core/html-renderer.js +11 -0
  27. package/dist/core/image-resize.d.ts +17 -0
  28. package/dist/core/image-resize.js +11 -0
  29. package/dist/core/serializer.d.ts +9 -0
  30. package/dist/core/serializer.js +7 -0
  31. package/dist/hooks/useImageDropZone.d.ts +23 -0
  32. package/dist/hooks/useImageDropZone.js +10 -0
  33. package/dist/index.d.ts +11 -0
  34. package/dist/index.js +57 -0
  35. package/dist/types.d.ts +67 -0
  36. package/dist/types.js +0 -0
  37. package/package.json +43 -0
  38. package/styles/artist.css +332 -0
  39. package/styles/editor.css +394 -0
  40. package/styles/preview.css +203 -0
@@ -0,0 +1,123 @@
1
+ // src/blocks/built-in.ts
2
+ function empty(type, id) {
3
+ const b = { type, id };
4
+ switch (type) {
5
+ case "lead":
6
+ case "paragraph":
7
+ b.text = "";
8
+ break;
9
+ case "subheading":
10
+ b.text = "";
11
+ break;
12
+ case "subheading-label":
13
+ b.en = "";
14
+ b.text = "";
15
+ break;
16
+ case "divider":
17
+ break;
18
+ case "spacer":
19
+ b.size = "medium";
20
+ break;
21
+ case "img-full":
22
+ b.src = "";
23
+ b.cap = "";
24
+ break;
25
+ case "img-inline":
26
+ b.src = "";
27
+ b.cap = "";
28
+ b.size = "full";
29
+ break;
30
+ case "img-pair":
31
+ b.src1 = "";
32
+ b.src2 = "";
33
+ b.cap = "";
34
+ break;
35
+ case "gallery":
36
+ b.src1 = "";
37
+ b.src2 = "";
38
+ b.src3 = "";
39
+ b.cap = "";
40
+ break;
41
+ case "img-text":
42
+ b.src = "";
43
+ b.name = "";
44
+ b.role = "";
45
+ b.bio = "";
46
+ break;
47
+ case "quote":
48
+ case "quote-large":
49
+ b.text = "";
50
+ b.attr = "";
51
+ break;
52
+ case "stats":
53
+ b.items = [{ num: "", label: "" }, { num: "", label: "" }, { num: "", label: "" }];
54
+ break;
55
+ case "infobox":
56
+ b.label = "";
57
+ b.items = [{ k: "", v: "" }];
58
+ break;
59
+ case "callout":
60
+ b.title = "";
61
+ b.text = "";
62
+ break;
63
+ case "numcards":
64
+ b.items = [{ title: "", desc: "" }];
65
+ break;
66
+ case "qa":
67
+ b.q = "";
68
+ b.a = "";
69
+ break;
70
+ case "press-list":
71
+ b.items = [{ src: "", date: "", title: "", ex: "", link: "" }];
72
+ break;
73
+ case "timeline":
74
+ b.items = [{ date: "", title: "", desc: "" }];
75
+ break;
76
+ case "video":
77
+ b.url = "";
78
+ b.cap = "";
79
+ break;
80
+ case "cta":
81
+ b.text = "";
82
+ b.label = "";
83
+ b.url = "";
84
+ break;
85
+ }
86
+ return b;
87
+ }
88
+ function createEmptyBlock(type, id) {
89
+ return empty(type, id);
90
+ }
91
+ var BUILT_IN_BLOCKS = [
92
+ { type: "lead", label: "\uB9AC\uB4DC \uB2E8\uB77D", icon: "\u2761", desc: "\uAE30\uC0AC \uB3C4\uC785\uBD80. \uD575\uC2EC \uB0B4\uC6A9\uC744 \uC694\uC57D\uD558\uB294 \uCCAB \uBB38\uB2E8", createEmpty: (id) => empty("lead", id) },
93
+ { type: "paragraph", label: "\uC77C\uBC18 \uB2E8\uB77D", icon: "\xB6", desc: "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8", createEmpty: (id) => empty("paragraph", id) },
94
+ { type: "subheading", label: "\uC18C\uC81C\uBAA9", icon: "H", desc: "\uBCF8\uBB38 \uC911\uAC04\uC5D0 \uB123\uB294 \uAD6C\uBD84 \uC81C\uBAA9", createEmpty: (id) => empty("subheading", id) },
95
+ { type: "subheading-label", label: "\uC18C\uC81C\uBAA9+\uBD80\uC81C", icon: "H\u2091", desc: "\uC791\uC740 \uBD80\uC81C\uAC00 \uC704\uC5D0 \uBD99\uB294 \uC18C\uC81C\uBAA9", createEmpty: (id) => empty("subheading-label", id) },
96
+ { type: "divider", label: "\uAD6C\uBD84\uC120", icon: "\u2014", desc: "\uB0B4\uC6A9 \uC0AC\uC774 \uC2DC\uAC01\uC801 \uAD6C\uBD84", createEmpty: (id) => empty("divider", id) },
97
+ { type: "spacer", label: "\uC5EC\uBC31", icon: "\u2195", desc: "\uB0B4\uC6A9 \uC0AC\uC774\uC5D0 \uBE48 \uACF5\uAC04 \uCD94\uAC00. \uD06C\uAE30 \uC870\uC808 \uAC00\uB2A5", createEmpty: (id) => empty("spacer", id) },
98
+ { type: "img-full", label: "\uC774\uBBF8\uC9C0(\uC804\uD3ED)", icon: "\u25A3", desc: "\uD654\uBA74 \uB108\uBE44 \uAC00\uB4DD \uCC44\uC6B0\uB294 \uD070 \uC0AC\uC9C4", createEmpty: (id) => empty("img-full", id) },
99
+ { type: "img-inline", label: "\uC774\uBBF8\uC9C0(\uBCF8\uBB38\uD3ED)", icon: "\u25A2", desc: "\uBCF8\uBB38 \uB108\uBE44\uC5D0 \uB9DE\uB294 \uC0AC\uC9C4. \uD06C\uAE30 \uC870\uC808 \uAC00\uB2A5", createEmpty: (id) => empty("img-inline", id) },
100
+ { type: "img-pair", label: "\uC774\uBBF8\uC9C0 2\uC7A5", icon: "\u25A5", desc: "\uB098\uB780\uD788 \uBC30\uCE58\uB418\uB294 \uC0AC\uC9C4 \uB450 \uC7A5", createEmpty: (id) => empty("img-pair", id) },
101
+ { type: "gallery", label: "\uAC24\uB7EC\uB9AC 3\uC7A5", icon: "\u229E", desc: "\uC815\uC0AC\uAC01\uD615 \uC0AC\uC9C4 \uC138 \uC7A5 \uB098\uB780\uD788", createEmpty: (id) => empty("gallery", id) },
102
+ { type: "img-text", label: "\uC778\uBB3C \uC18C\uAC1C", icon: "\u263A", desc: "\uC778\uBB3C \uC0AC\uC9C4 + \uC774\uB984, \uC9C1\uD568, \uC18C\uAC1C\uAE00", createEmpty: (id) => empty("img-text", id) },
103
+ { type: "quote", label: "\uC778\uC6A9\uBB38", icon: '"', desc: "\uC778\uD130\uBDF0, \uB300\uC0AC \uB4F1 \uAC15\uC870\uD560 \uB9D0", createEmpty: (id) => empty("quote", id) },
104
+ { type: "quote-large", label: "\uC778\uC6A9\uBB38(\uB300\uD615)", icon: "\u275D", desc: "\uD398\uC774\uC9C0 \uC911\uC559\uC5D0 \uD06C\uAC8C \uD45C\uC2DC\uB418\uB294 \uC778\uC6A9", createEmpty: (id) => empty("quote-large", id) },
105
+ { type: "stats", label: "\uC22B\uC790 \uD558\uC774\uB77C\uC774\uD2B8", icon: "#", desc: "\uAC15\uC870\uD560 \uC22B\uC790 (\uC608: \uACF5\uC5F0 3\uD68C, \uAD00\uAC1D 1,200\uBA85)", createEmpty: (id) => empty("stats", id) },
106
+ { type: "infobox", label: "\uC815\uBCF4 \uBC15\uC2A4", icon: "\u2630", desc: "\uD56D\uBAA9\uBCC4 \uC815\uBCF4", createEmpty: (id) => empty("infobox", id) },
107
+ { type: "callout", label: "\uAC15\uC870 \uBC15\uC2A4", icon: "!", desc: "\uB3C5\uC790\uC5D0\uAC8C \uC548\uB0B4\uD560 \uC911\uC694 \uC0AC\uD56D", createEmpty: (id) => empty("callout", id) },
108
+ { type: "numcards", label: "\uB118\uBC84 \uCE74\uB4DC", icon: "\u2460", desc: "01, 02, 03 \uC21C\uC11C\uB85C \uC815\uB9AC\uD558\uB294 \uC548\uB0B4 \uCE74\uB4DC", createEmpty: (id) => empty("numcards", id) },
109
+ { type: "qa", label: "Q&A", icon: "Q", desc: "\uC9C8\uBB38\uACFC \uB2F5\uBCC0 \uD615\uC2DD\uC758 \uC778\uD130\uBDF0", createEmpty: (id) => empty("qa", id) },
110
+ { type: "press-list", label: "\uBCF4\uB3C4 \uBAA9\uB85D", icon: "\u270E", desc: "\uC5B8\uB860 \uBCF4\uB3C4 \uAE30\uC0AC \uBAA9\uB85D (\uB9E4\uCCB4\uBA85, \uC81C\uBAA9, \uB9C1\uD06C)", createEmpty: (id) => empty("press-list", id) },
111
+ { type: "timeline", label: "\uD0C0\uC784\uB77C\uC778", icon: "\u2193", desc: "\uC2DC\uAC04 \uC21C\uC11C\uB300\uB85C \uC815\uB9AC\uD558\uB294 \uC77C\uC815/\uACFC\uC815", createEmpty: (id) => empty("timeline", id) },
112
+ { type: "video", label: "\uC601\uC0C1", icon: "\u25B6", desc: "YouTube \uC601\uC0C1 \uC0BD\uC785", createEmpty: (id) => empty("video", id) },
113
+ { type: "cta", label: "CTA \uBC84\uD2BC", icon: "\u2192", desc: "\uD589\uB3D9 \uC720\uB3C4 \uBC84\uD2BC", createEmpty: (id) => empty("cta", id) }
114
+ ];
115
+ function getBlockDef(type) {
116
+ return BUILT_IN_BLOCKS.find((d) => d.type === type);
117
+ }
118
+
119
+ export {
120
+ createEmptyBlock,
121
+ BUILT_IN_BLOCKS,
122
+ getBlockDef
123
+ };
@@ -0,0 +1,77 @@
1
+ import {
2
+ useImageDropZone
3
+ } from "./chunk-QR225IXX.js";
4
+ import {
5
+ __spreadProps,
6
+ __spreadValues
7
+ } from "./chunk-N3ETBM74.js";
8
+
9
+ // src/components/ImageUploadField.tsx
10
+ import { useRef } from "react";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ function ImageUploadField({
13
+ blockId,
14
+ field,
15
+ src,
16
+ className,
17
+ onUploadComplete,
18
+ onKeyTracked,
19
+ onClear
20
+ }) {
21
+ const inputRef = useRef(null);
22
+ const drop = useImageDropZone({
23
+ onUpload: (result) => onUploadComplete(blockId, field, result.url),
24
+ onKeyTracked
25
+ });
26
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
27
+ /* @__PURE__ */ jsx(
28
+ "input",
29
+ {
30
+ ref: inputRef,
31
+ type: "file",
32
+ accept: "image/*",
33
+ style: { display: "none" },
34
+ onChange: async (e) => {
35
+ await drop.handleFileInput(e.target.files);
36
+ e.target.value = "";
37
+ }
38
+ }
39
+ ),
40
+ /* @__PURE__ */ jsxs(
41
+ "div",
42
+ __spreadProps(__spreadValues({
43
+ className: `be-img-upload ${src ? "has-image" : ""} ${className || ""}${drop.isDragOver ? " is-drag-over" : ""}${drop.isUploading ? " is-uploading" : ""}${drop.isResizing ? " is-resizing" : ""}`,
44
+ onClick: () => {
45
+ var _a;
46
+ return !src && !drop.isUploading && !drop.isResizing && ((_a = inputRef.current) == null ? void 0 : _a.click());
47
+ }
48
+ }, drop.dragHandlers), {
49
+ children: [
50
+ drop.isResizing ? /* @__PURE__ */ jsx("div", { className: "be-upload-spinner", children: "\uC774\uBBF8\uC9C0 \uCD5C\uC801\uD654 \uC911..." }) : drop.isUploading ? /* @__PURE__ */ jsx("div", { className: "be-upload-spinner", children: "\uC5C5\uB85C\uB4DC \uC911..." }) : drop.isDragOver ? /* @__PURE__ */ jsx("div", { className: "be-drag-hint", children: "\uC5EC\uAE30\uC5D0 \uB193\uC73C\uC138\uC694" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
51
+ /* @__PURE__ */ jsx("span", { className: "be-img-upload-text", children: "+ \uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB4DC" }),
52
+ /* @__PURE__ */ jsx("span", { className: "be-img-upload-hint", children: "JPG, PNG, WebP, GIF / 10MB \uCD08\uACFC \uC2DC \uC790\uB3D9 \uCD5C\uC801\uD654" })
53
+ ] }),
54
+ src && /* @__PURE__ */ jsxs(Fragment, { children: [
55
+ /* @__PURE__ */ jsx("img", { src, alt: "" }),
56
+ /* @__PURE__ */ jsx(
57
+ "button",
58
+ {
59
+ className: "be-img-remove-btn",
60
+ onClick: (e) => {
61
+ e.stopPropagation();
62
+ onClear(blockId, field);
63
+ },
64
+ children: "\xD7"
65
+ }
66
+ )
67
+ ] })
68
+ ]
69
+ })
70
+ ),
71
+ drop.error && /* @__PURE__ */ jsx("div", { className: "be-error", children: drop.error })
72
+ ] });
73
+ }
74
+
75
+ export {
76
+ ImageUploadField
77
+ };
@@ -0,0 +1,24 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+
21
+ export {
22
+ __spreadValues,
23
+ __spreadProps
24
+ };
@@ -0,0 +1,28 @@
1
+ // src/core/serializer.ts
2
+ function createSerializer(marker) {
3
+ const prefix = `<!-- ${marker}`;
4
+ function serialize(data) {
5
+ const json = JSON.stringify(data);
6
+ const encoded = typeof btoa === "function" ? btoa(encodeURIComponent(json)) : "";
7
+ return `
8
+ ${prefix}${encoded} -->`;
9
+ }
10
+ function deserialize(html) {
11
+ const idx = html.indexOf(prefix);
12
+ if (idx === -1) return null;
13
+ try {
14
+ const start = idx + prefix.length;
15
+ const end = html.indexOf(" -->", start);
16
+ const encoded = html.substring(start, end);
17
+ const json = decodeURIComponent(atob(encoded));
18
+ return JSON.parse(json);
19
+ } catch (e) {
20
+ return null;
21
+ }
22
+ }
23
+ return { serialize, deserialize };
24
+ }
25
+
26
+ export {
27
+ createSerializer
28
+ };
@@ -0,0 +1,148 @@
1
+ import {
2
+ ALLOWED_IMAGE_TYPES,
3
+ resizeImageIfNeeded,
4
+ validateImageFile
5
+ } from "./chunk-3R3HAGQL.js";
6
+ import {
7
+ useBlockEditorContext
8
+ } from "./chunk-CYMYM7LP.js";
9
+
10
+ // src/hooks/useImageDropZone.ts
11
+ import { useState, useCallback, useRef } from "react";
12
+ function useImageDropZone(options) {
13
+ const {
14
+ multiple = false,
15
+ maxFiles,
16
+ onUpload,
17
+ onKeyTracked,
18
+ disabled = false
19
+ } = options;
20
+ const { uploadImage, onError, autoResize, maxSizeMB } = useBlockEditorContext();
21
+ const maxSize = maxSizeMB * 1024 * 1024;
22
+ const [isDragOver, setIsDragOver] = useState(false);
23
+ const [isUploading, setIsUploading] = useState(false);
24
+ const [isResizing, setIsResizing] = useState(false);
25
+ const [error, setError] = useState(null);
26
+ const dragCounterRef = useRef(0);
27
+ const processFiles = useCallback(
28
+ async (files) => {
29
+ if (disabled || files.length === 0) return;
30
+ let toProcess = multiple ? files : [files[0]];
31
+ if (maxFiles && toProcess.length > maxFiles) {
32
+ toProcess = toProcess.slice(0, maxFiles);
33
+ setError(`\uCD5C\uB300 ${maxFiles}\uAC1C\uAE4C\uC9C0 \uC5C5\uB85C\uB4DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
34
+ }
35
+ for (const file of toProcess) {
36
+ const validationError = validateImageFile(file);
37
+ if (validationError) {
38
+ setError(validationError);
39
+ return;
40
+ }
41
+ }
42
+ setError(null);
43
+ let resizedFiles;
44
+ const needsResize = autoResize && toProcess.some((f) => f.size > maxSize);
45
+ if (needsResize) {
46
+ setIsResizing(true);
47
+ try {
48
+ resizedFiles = [];
49
+ for (const file of toProcess) {
50
+ const result = await resizeImageIfNeeded(file);
51
+ if (result.file.size > maxSize) {
52
+ setError(
53
+ `\uC774\uBBF8\uC9C0 \uD06C\uAE30\uB97C \uC904\uC600\uC9C0\uB9CC \uC5EC\uC804\uD788 ${maxSizeMB}MB\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4. (${(result.file.size / 1024 / 1024).toFixed(1)}MB) \uB354 \uC791\uC740 \uC774\uBBF8\uC9C0\uB97C \uC0AC\uC6A9\uD574 \uC8FC\uC138\uC694.`
54
+ );
55
+ setIsResizing(false);
56
+ return;
57
+ }
58
+ resizedFiles.push(result.file);
59
+ }
60
+ } catch (err) {
61
+ const msg = err instanceof Error ? err.message : "\uC774\uBBF8\uC9C0 \uCD5C\uC801\uD654 \uC911 \uC624\uB958 \uBC1C\uC0DD";
62
+ setError(msg);
63
+ setIsResizing(false);
64
+ return;
65
+ }
66
+ setIsResizing(false);
67
+ } else {
68
+ resizedFiles = toProcess;
69
+ }
70
+ setIsUploading(true);
71
+ try {
72
+ for (const file of resizedFiles) {
73
+ const result = await uploadImage(file);
74
+ onUpload(result);
75
+ if (result.key) onKeyTracked == null ? void 0 : onKeyTracked(result.key);
76
+ }
77
+ } catch (err) {
78
+ const msg = err instanceof Error ? err.message : "\uC5C5\uB85C\uB4DC \uC911 \uC624\uB958 \uBC1C\uC0DD";
79
+ setError(msg);
80
+ onError(msg);
81
+ } finally {
82
+ setIsUploading(false);
83
+ }
84
+ },
85
+ [multiple, maxFiles, onUpload, onKeyTracked, disabled, uploadImage, onError, autoResize, maxSize, maxSizeMB]
86
+ );
87
+ const onDragEnter = useCallback((e) => {
88
+ var _a;
89
+ e.preventDefault();
90
+ e.stopPropagation();
91
+ dragCounterRef.current++;
92
+ if ((_a = e.dataTransfer) == null ? void 0 : _a.types.includes("Files")) {
93
+ setIsDragOver(true);
94
+ }
95
+ }, []);
96
+ const onDragOver = useCallback((e) => {
97
+ e.preventDefault();
98
+ e.stopPropagation();
99
+ }, []);
100
+ const onDragLeave = useCallback((e) => {
101
+ e.preventDefault();
102
+ e.stopPropagation();
103
+ dragCounterRef.current--;
104
+ if (dragCounterRef.current === 0) {
105
+ setIsDragOver(false);
106
+ }
107
+ }, []);
108
+ const onDrop = useCallback(
109
+ (e) => {
110
+ var _a;
111
+ e.preventDefault();
112
+ e.stopPropagation();
113
+ dragCounterRef.current = 0;
114
+ setIsDragOver(false);
115
+ if (disabled) return;
116
+ const files = Array.from(((_a = e.dataTransfer) == null ? void 0 : _a.files) || []).filter(
117
+ (f) => ALLOWED_IMAGE_TYPES.includes(f.type)
118
+ );
119
+ if (files.length === 0) {
120
+ setError("\uC774\uBBF8\uC9C0 \uD30C\uC77C\uB9CC \uC5C5\uB85C\uB4DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
121
+ return;
122
+ }
123
+ processFiles(files);
124
+ },
125
+ [processFiles, disabled]
126
+ );
127
+ const handleFileInput = useCallback(
128
+ async (fileList) => {
129
+ const files = Array.from(fileList || []);
130
+ if (files.length > 0) {
131
+ await processFiles(files);
132
+ }
133
+ },
134
+ [processFiles]
135
+ );
136
+ return {
137
+ isDragOver,
138
+ isUploading,
139
+ isResizing,
140
+ error,
141
+ dragHandlers: { onDragEnter, onDragOver, onDragLeave, onDrop },
142
+ handleFileInput
143
+ };
144
+ }
145
+
146
+ export {
147
+ useImageDropZone
148
+ };
@@ -0,0 +1,250 @@
1
+ import {
2
+ h,
3
+ nl2br
4
+ } from "./chunk-62BAOSP6.js";
5
+ import {
6
+ createSerializer
7
+ } from "./chunk-PPVXNJWI.js";
8
+ import {
9
+ useBlockEditorContext
10
+ } from "./chunk-CYMYM7LP.js";
11
+ import {
12
+ __spreadProps,
13
+ __spreadValues
14
+ } from "./chunk-N3ETBM74.js";
15
+
16
+ // src/components/ArtistEditor.tsx
17
+ import { useState, useCallback, useRef, useEffect } from "react";
18
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
19
+ var DEFAULT_MARKER = "abe-blocks:";
20
+ function generateHtml(data) {
21
+ const hasText = !!data.text.trim();
22
+ const hasMain = !!data.mainImage;
23
+ const galleryImages = data.gallery.filter(Boolean);
24
+ const hasGallery = galleryImages.length > 0;
25
+ if (!hasText && !hasMain && !hasGallery) {
26
+ return '<div class="abe-pv-body"><div class="abe-pv-empty">\uC57D\uB825\uC744 \uC785\uB825\uD558\uBA74 \uBBF8\uB9AC\uBCF4\uAE30\uAC00 \uD45C\uC2DC\uB429\uB2C8\uB2E4</div></div>';
27
+ }
28
+ let html = '<div class="abe-pv-body">';
29
+ if (hasText || hasMain) {
30
+ if (hasText && hasMain) {
31
+ html += '<div class="abe-pv-intro">';
32
+ html += `<div class="abe-pv-bio">${nl2br(data.text)}</div>`;
33
+ html += `<div class="abe-pv-main-img"><img src="${data.mainImage}" alt=""></div>`;
34
+ html += "</div>";
35
+ } else if (hasText) {
36
+ html += `<div class="abe-pv-bio" style="margin-bottom:20px">${nl2br(data.text)}</div>`;
37
+ } else {
38
+ html += `<div class="abe-pv-main-img" style="max-width:200px;margin-bottom:20px"><img src="${data.mainImage}" alt=""></div>`;
39
+ }
40
+ }
41
+ if (hasGallery) {
42
+ const n = galleryImages.length;
43
+ html += '<div class="abe-pv-gallery">';
44
+ html += '<div class="abe-pv-gallery-label">Gallery</div>';
45
+ html += `<div class="abe-pv-gallery-grid layout-${n}">`;
46
+ galleryImages.forEach((src, i) => {
47
+ html += `<img src="${h(src)}" alt="" class="abe-gi abe-gi-${i}">`;
48
+ });
49
+ html += "</div></div>";
50
+ }
51
+ html += "</div>";
52
+ return html;
53
+ }
54
+ function ArtistEditor({
55
+ content,
56
+ onChange,
57
+ onImageUploaded,
58
+ placeholder,
59
+ marker = DEFAULT_MARKER,
60
+ maxGallery = 5
61
+ }) {
62
+ const { uploadImage, onError } = useBlockEditorContext();
63
+ const ser = useRef(createSerializer(marker));
64
+ const [data, setData] = useState(() => {
65
+ const existing = ser.current.deserialize(content);
66
+ if (existing) return existing;
67
+ const plainText = content ? content.replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "").trim() : "";
68
+ return { text: plainText, mainImage: "", gallery: [] };
69
+ });
70
+ const isInitialMount = useRef(true);
71
+ const onChangeRef = useRef(onChange);
72
+ onChangeRef.current = onChange;
73
+ useEffect(() => {
74
+ if (isInitialMount.current) {
75
+ isInitialMount.current = false;
76
+ return;
77
+ }
78
+ const html = generateHtml(data);
79
+ const serialized = ser.current.serialize(data);
80
+ onChangeRef.current(html + serialized);
81
+ }, [data]);
82
+ const updateText = useCallback((text) => {
83
+ setData((prev) => __spreadProps(__spreadValues({}, prev), { text }));
84
+ }, []);
85
+ const updateMainImage = useCallback((mainImage) => {
86
+ setData((prev) => __spreadProps(__spreadValues({}, prev), { mainImage }));
87
+ }, []);
88
+ const updateGalleryImage = useCallback((index, src) => {
89
+ setData((prev) => {
90
+ const gallery = [...prev.gallery];
91
+ gallery[index] = src;
92
+ return __spreadProps(__spreadValues({}, prev), { gallery });
93
+ });
94
+ }, []);
95
+ const removeGalleryImage = useCallback((index) => {
96
+ setData((prev) => {
97
+ const gallery = prev.gallery.filter((_, i) => i !== index);
98
+ return __spreadProps(__spreadValues({}, prev), { gallery });
99
+ });
100
+ }, []);
101
+ const handleImageUpload = useCallback(
102
+ (target) => {
103
+ const input = document.createElement("input");
104
+ input.type = "file";
105
+ input.accept = "image/*";
106
+ input.onchange = async () => {
107
+ var _a;
108
+ const file = (_a = input.files) == null ? void 0 : _a[0];
109
+ if (!file) return;
110
+ try {
111
+ const result = await uploadImage(file);
112
+ if (result.url) {
113
+ if (target === "main") {
114
+ updateMainImage(result.url);
115
+ } else {
116
+ updateGalleryImage(target, result.url);
117
+ }
118
+ if (result.key) onImageUploaded == null ? void 0 : onImageUploaded(result.key);
119
+ }
120
+ } catch (e) {
121
+ onError("\uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB4DC \uC911 \uC624\uB958 \uBC1C\uC0DD");
122
+ }
123
+ };
124
+ input.click();
125
+ },
126
+ [uploadImage, updateMainImage, updateGalleryImage, onImageUploaded, onError]
127
+ );
128
+ const handleGalleryMultiUpload = useCallback(() => {
129
+ const currentCount = data.gallery.filter(Boolean).length;
130
+ const remaining = maxGallery - currentCount;
131
+ if (remaining <= 0) {
132
+ onError(`\uAC24\uB7EC\uB9AC \uC774\uBBF8\uC9C0\uB294 \uCD5C\uB300 ${maxGallery}\uC7A5\uAE4C\uC9C0 \uB4F1\uB85D\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
133
+ return;
134
+ }
135
+ const input = document.createElement("input");
136
+ input.type = "file";
137
+ input.accept = "image/*";
138
+ input.multiple = true;
139
+ input.onchange = async () => {
140
+ const files = Array.from(input.files || []).slice(0, remaining);
141
+ if (files.length === 0) return;
142
+ for (const file of files) {
143
+ try {
144
+ const result = await uploadImage(file);
145
+ if (result.url) {
146
+ setData((prev) => {
147
+ if (prev.gallery.filter(Boolean).length >= maxGallery) return prev;
148
+ return __spreadProps(__spreadValues({}, prev), { gallery: [...prev.gallery, result.url] });
149
+ });
150
+ if (result.key) onImageUploaded == null ? void 0 : onImageUploaded(result.key);
151
+ }
152
+ } catch (e) {
153
+ onError("\uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB4DC \uC911 \uC624\uB958 \uBC1C\uC0DD");
154
+ }
155
+ }
156
+ };
157
+ input.click();
158
+ }, [data.gallery, maxGallery, uploadImage, onImageUploaded, onError]);
159
+ const previewHtml = generateHtml(data);
160
+ const galleryImages = data.gallery.filter(Boolean);
161
+ const galleryRemaining = maxGallery - galleryImages.length;
162
+ return /* @__PURE__ */ jsxs("div", { className: "abe-wrapper", children: [
163
+ /* @__PURE__ */ jsxs("div", { className: "abe-editor", children: [
164
+ /* @__PURE__ */ jsxs("div", { className: "abe-section", children: [
165
+ /* @__PURE__ */ jsx("div", { className: "abe-section-label", children: "\uC57D\uB825 \uD14D\uC2A4\uD2B8" }),
166
+ /* @__PURE__ */ jsx(
167
+ "textarea",
168
+ {
169
+ className: "abe-textarea",
170
+ value: data.text,
171
+ onChange: (e) => updateText(e.target.value),
172
+ placeholder: placeholder || "\uC57D\uB825\uC744 \uC785\uB825\uD558\uC138\uC694..."
173
+ }
174
+ )
175
+ ] }),
176
+ /* @__PURE__ */ jsxs("div", { className: "abe-section", children: [
177
+ /* @__PURE__ */ jsx("div", { className: "abe-section-label", children: "\uB300\uD45C \uC774\uBBF8\uC9C0" }),
178
+ /* @__PURE__ */ jsxs(
179
+ "div",
180
+ {
181
+ className: `abe-main-img-upload ${data.mainImage ? "has-image" : ""}`,
182
+ onClick: () => !data.mainImage && handleImageUpload("main"),
183
+ children: [
184
+ /* @__PURE__ */ jsxs("span", { className: "abe-upload-text", children: [
185
+ "+ \uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB4DC",
186
+ /* @__PURE__ */ jsx("br", {}),
187
+ "3:4 \uBE44\uC728 \uAD8C\uC7A5"
188
+ ] }),
189
+ data.mainImage && /* @__PURE__ */ jsxs(Fragment, { children: [
190
+ /* @__PURE__ */ jsx("img", { src: data.mainImage, alt: "" }),
191
+ /* @__PURE__ */ jsx(
192
+ "button",
193
+ {
194
+ className: "abe-img-remove-btn",
195
+ onClick: (e) => {
196
+ e.stopPropagation();
197
+ updateMainImage("");
198
+ },
199
+ children: "\xD7"
200
+ }
201
+ )
202
+ ] })
203
+ ]
204
+ }
205
+ )
206
+ ] }),
207
+ /* @__PURE__ */ jsxs("div", { className: "abe-section", children: [
208
+ /* @__PURE__ */ jsxs("div", { className: "abe-section-label", children: [
209
+ "\uCD94\uAC00 \uAC24\uB7EC\uB9AC (",
210
+ galleryImages.length,
211
+ "/",
212
+ maxGallery,
213
+ ")"
214
+ ] }),
215
+ galleryImages.length > 0 && /* @__PURE__ */ jsx("div", { className: "abe-gallery-grid", children: galleryImages.map((src, i) => /* @__PURE__ */ jsxs("div", { className: "abe-gallery-item has-image", children: [
216
+ /* @__PURE__ */ jsx("img", { src, alt: "" }),
217
+ /* @__PURE__ */ jsx(
218
+ "button",
219
+ {
220
+ className: "abe-img-remove-btn",
221
+ onClick: () => removeGalleryImage(i),
222
+ children: "\xD7"
223
+ }
224
+ )
225
+ ] }, i)) }),
226
+ galleryRemaining > 0 && /* @__PURE__ */ jsxs(
227
+ "button",
228
+ {
229
+ type: "button",
230
+ className: "abe-gallery-add-btn",
231
+ onClick: handleGalleryMultiUpload,
232
+ children: [
233
+ "+ \uC774\uBBF8\uC9C0 \uCD94\uAC00 (\uCD5C\uB300 ",
234
+ galleryRemaining,
235
+ "\uC7A5 \uC120\uD0DD \uAC00\uB2A5)"
236
+ ]
237
+ }
238
+ )
239
+ ] })
240
+ ] }),
241
+ /* @__PURE__ */ jsxs("div", { className: "abe-preview", children: [
242
+ /* @__PURE__ */ jsx("div", { className: "abe-pv-label", children: "\uC2E4\uC2DC\uAC04 \uBBF8\uB9AC\uBCF4\uAE30" }),
243
+ /* @__PURE__ */ jsx("div", { className: "abe-pv-article", dangerouslySetInnerHTML: { __html: previewHtml } })
244
+ ] })
245
+ ] });
246
+ }
247
+
248
+ export {
249
+ ArtistEditor
250
+ };
@@ -0,0 +1,12 @@
1
+ interface ArtistEditorProps {
2
+ content: string;
3
+ onChange: (html: string) => void;
4
+ onImageUploaded?: (key: string) => void;
5
+ placeholder?: string;
6
+ /** Serialization marker (default: "abe-blocks:") */
7
+ marker?: string;
8
+ /** Max gallery images (default: 5) */
9
+ maxGallery?: number;
10
+ }
11
+ export declare function ArtistEditor({ content, onChange, onImageUploaded, placeholder, marker, maxGallery, }: ArtistEditorProps): import("react/jsx-runtime").JSX.Element;
12
+ export {};
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import {
3
+ ArtistEditor
4
+ } from "../chunk-VIJV6FLT.js";
5
+ import "../chunk-62BAOSP6.js";
6
+ import "../chunk-PPVXNJWI.js";
7
+ import "../chunk-CYMYM7LP.js";
8
+ import "../chunk-N3ETBM74.js";
9
+ export {
10
+ ArtistEditor
11
+ };