@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,270 @@
1
+ import {
2
+ ImageUploadField
3
+ } from "./chunk-G6J2DCC5.js";
4
+
5
+ // src/components/BlockRenderer.tsx
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ function BlockRenderer({ block, updateBlock, addSubItem, removeSubItem, onImageUploaded }) {
8
+ var _a, _b, _c, _d, _e;
9
+ const { id, type } = block;
10
+ const inp = (field, placeholder, style) => /* @__PURE__ */ jsx(
11
+ "input",
12
+ {
13
+ className: "be-input",
14
+ value: block[field] || "",
15
+ onChange: (e) => updateBlock(id, field, e.target.value),
16
+ placeholder,
17
+ style
18
+ }
19
+ );
20
+ const ta = (field, placeholder, style) => /* @__PURE__ */ jsx(
21
+ "textarea",
22
+ {
23
+ className: "be-textarea",
24
+ value: block[field] || "",
25
+ onChange: (e) => updateBlock(id, field, e.target.value),
26
+ placeholder,
27
+ style
28
+ }
29
+ );
30
+ const iInp = (index, field, placeholder, style) => {
31
+ var _a2, _b2;
32
+ return /* @__PURE__ */ jsx(
33
+ "input",
34
+ {
35
+ className: "be-input",
36
+ value: ((_b2 = (_a2 = block.items) == null ? void 0 : _a2[index]) == null ? void 0 : _b2[field]) || "",
37
+ onChange: (e) => updateBlock(id, `items.${index}.${field}`, e.target.value),
38
+ placeholder,
39
+ style
40
+ }
41
+ );
42
+ };
43
+ const iTa = (index, field, placeholder, style) => {
44
+ var _a2, _b2;
45
+ return /* @__PURE__ */ jsx(
46
+ "textarea",
47
+ {
48
+ className: "be-textarea",
49
+ value: ((_b2 = (_a2 = block.items) == null ? void 0 : _a2[index]) == null ? void 0 : _b2[field]) || "",
50
+ onChange: (e) => updateBlock(id, `items.${index}.${field}`, e.target.value),
51
+ placeholder,
52
+ style
53
+ }
54
+ );
55
+ };
56
+ const imgUp = (field, className) => {
57
+ const src = block[field] || "";
58
+ return /* @__PURE__ */ jsx(
59
+ ImageUploadField,
60
+ {
61
+ blockId: id,
62
+ field,
63
+ src,
64
+ className,
65
+ onUploadComplete: (bid, f, url) => updateBlock(bid, f, url),
66
+ onKeyTracked: onImageUploaded,
67
+ onClear: (bid, f) => updateBlock(bid, f, "")
68
+ }
69
+ );
70
+ };
71
+ switch (type) {
72
+ case "lead":
73
+ return ta("text", "\uAE30\uC0AC\uC758 \uCCAB \uBB38\uB2E8\uC785\uB2C8\uB2E4. \uD575\uC2EC \uB0B4\uC6A9\uC744 1~2\uBB38\uC7A5\uC73C\uB85C \uC694\uC57D\uD574 \uC8FC\uC138\uC694.", { fontStyle: block.text ? "normal" : "italic" });
74
+ case "paragraph":
75
+ return ta("text", "\uBCF8\uBB38 \uB0B4\uC6A9\uC744 \uC785\uB825\uD558\uC138\uC694.");
76
+ case "subheading":
77
+ return inp("text", "\uBCF8\uBB38\uC744 \uB098\uB204\uB294 \uC911\uAC04 \uC81C\uBAA9");
78
+ case "subheading-label":
79
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
80
+ inp("en", "\uC704\uC5D0 \uC791\uAC8C \uD45C\uC2DC\uB420 \uBD80\uC81C (\uC608: Behind the Scene)", { fontSize: 11, marginBottom: 4 }),
81
+ inp("text", "\uC18C\uC81C\uBAA9")
82
+ ] });
83
+ case "divider":
84
+ return /* @__PURE__ */ jsx("div", { style: { height: 1, background: "var(--be-border, #ddd)", margin: "4px 0" } });
85
+ case "spacer":
86
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
87
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: "var(--be-text-dim, #999)" }, children: "\uC5EC\uBC31 \uD06C\uAE30" }),
88
+ /* @__PURE__ */ jsxs(
89
+ "select",
90
+ {
91
+ className: "be-input",
92
+ value: block.size || "medium",
93
+ onChange: (e) => updateBlock(id, "size", e.target.value),
94
+ style: { width: "auto", padding: "4px 8px", fontSize: 11 },
95
+ children: [
96
+ /* @__PURE__ */ jsx("option", { value: "small", children: "\uC791\uAC8C (16px)" }),
97
+ /* @__PURE__ */ jsx("option", { value: "medium", children: "\uBCF4\uD1B5 (32px)" }),
98
+ /* @__PURE__ */ jsx("option", { value: "large", children: "\uD06C\uAC8C (56px)" })
99
+ ]
100
+ }
101
+ )
102
+ ] });
103
+ case "img-full":
104
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
105
+ imgUp("src"),
106
+ inp("cap", "\uCEA1\uC158 (\uC120\uD0DD)", { fontSize: 11 })
107
+ ] });
108
+ case "img-inline":
109
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
110
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, marginBottom: 6, alignItems: "center" }, children: [
111
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 10, color: "var(--be-text-dim, #999)" }, children: "\uC0AC\uC774\uC988" }),
112
+ /* @__PURE__ */ jsxs(
113
+ "select",
114
+ {
115
+ className: "be-input",
116
+ value: block.size || "full",
117
+ onChange: (e) => updateBlock(id, "size", e.target.value),
118
+ style: { width: "auto", padding: "4px 8px", fontSize: 10 },
119
+ children: [
120
+ /* @__PURE__ */ jsx("option", { value: "full", children: "\uC804\uCCB4 (100%)" }),
121
+ /* @__PURE__ */ jsx("option", { value: "medium", children: "\uC911\uAC04 (70%)" }),
122
+ /* @__PURE__ */ jsx("option", { value: "small", children: "\uC791\uAC8C (50%)" })
123
+ ]
124
+ }
125
+ )
126
+ ] }),
127
+ imgUp("src"),
128
+ inp("cap", "\uCEA1\uC158 (\uC120\uD0DD)", { fontSize: 11 })
129
+ ] });
130
+ case "img-pair":
131
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
132
+ /* @__PURE__ */ jsxs("div", { className: "be-img-pair-grid", children: [
133
+ imgUp("src1", "small"),
134
+ imgUp("src2", "small")
135
+ ] }),
136
+ inp("cap", "\uCEA1\uC158 (\uC120\uD0DD)", { fontSize: 11 })
137
+ ] });
138
+ case "gallery":
139
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
140
+ /* @__PURE__ */ jsxs("div", { className: "be-img-gallery-grid", children: [
141
+ imgUp("src1", "square"),
142
+ imgUp("src2", "square"),
143
+ imgUp("src3", "square")
144
+ ] }),
145
+ inp("cap", "\uCEA1\uC158 (\uC120\uD0DD)", { fontSize: 11 })
146
+ ] });
147
+ case "img-text":
148
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
149
+ imgUp("src", "portrait"),
150
+ inp("name", "\uC774\uB984", { fontSize: 11, marginBottom: 3 }),
151
+ inp("role", "\uC9C1\uD568", { fontSize: 11, marginBottom: 3 }),
152
+ ta("bio", "\uC18C\uAC1C", { minHeight: 40 })
153
+ ] });
154
+ case "quote":
155
+ return /* @__PURE__ */ jsxs("div", { className: "be-quote-area", children: [
156
+ ta("text", "\uAC15\uC870\uD558\uACE0 \uC2F6\uC740 \uB9D0, \uC778\uD130\uBDF0 \uB0B4\uC6A9 \uB4F1", { minHeight: 40 }),
157
+ inp("attr", "\uCD9C\uCC98 (\uC608: \u2014 \uC9C0\uC6B0\uC601 \uC608\uC220\uAC10\uB3C5)", { fontSize: 11, marginTop: 4 })
158
+ ] });
159
+ case "quote-large":
160
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
161
+ ta("text", "\uD398\uC774\uC9C0 \uC911\uC559\uC5D0 \uD06C\uAC8C \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uAC00\uC7A5 \uC778\uC0C1\uC801\uC778 \uD55C\uB9C8\uB514\uB97C \uB123\uC5B4\uC8FC\uC138\uC694.", { minHeight: 40, textAlign: "center" }),
162
+ inp("attr", "\uCD9C\uCC98", { fontSize: 11, marginTop: 4, textAlign: "center" })
163
+ ] });
164
+ case "stats":
165
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
166
+ (_a = block.items) == null ? void 0 : _a.map((_, i) => {
167
+ var _a2, _b2, _c2, _d2;
168
+ return /* @__PURE__ */ jsxs("div", { className: "be-stats-row", children: [
169
+ /* @__PURE__ */ jsx("input", { className: "be-kv-key", value: ((_b2 = (_a2 = block.items) == null ? void 0 : _a2[i]) == null ? void 0 : _b2.num) || "", onChange: (e) => updateBlock(id, `items.${i}.num`, e.target.value), placeholder: "\uC22B\uC790" }),
170
+ /* @__PURE__ */ jsx("input", { className: "be-kv-value", value: ((_d2 = (_c2 = block.items) == null ? void 0 : _c2[i]) == null ? void 0 : _d2.label) || "", onChange: (e) => updateBlock(id, `items.${i}.label`, e.target.value), placeholder: "\uB77C\uBCA8" }),
171
+ /* @__PURE__ */ jsx("button", { className: "be-kv-delete", onClick: () => removeSubItem(id, i), children: "\xD7" })
172
+ ] }, i);
173
+ }),
174
+ /* @__PURE__ */ jsx("button", { className: "be-add-row-btn", onClick: () => addSubItem(id, "stats"), children: "+ \uD56D\uBAA9 \uCD94\uAC00" })
175
+ ] });
176
+ case "infobox":
177
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
178
+ inp("label", "\uBC15\uC2A4 \uB77C\uBCA8", { fontSize: 10, letterSpacing: 1, marginBottom: 6 }),
179
+ (_b = block.items) == null ? void 0 : _b.map((_, i) => {
180
+ var _a2, _b2, _c2, _d2;
181
+ return /* @__PURE__ */ jsxs("div", { className: "be-kv-row", children: [
182
+ /* @__PURE__ */ jsx("input", { className: "be-kv-key", value: ((_b2 = (_a2 = block.items) == null ? void 0 : _a2[i]) == null ? void 0 : _b2.k) || "", onChange: (e) => updateBlock(id, `items.${i}.k`, e.target.value), placeholder: "\uD56D\uBAA9\uBA85" }),
183
+ /* @__PURE__ */ jsx("input", { className: "be-kv-value", value: ((_d2 = (_c2 = block.items) == null ? void 0 : _c2[i]) == null ? void 0 : _d2.v) || "", onChange: (e) => updateBlock(id, `items.${i}.v`, e.target.value), placeholder: "\uB0B4\uC6A9" }),
184
+ /* @__PURE__ */ jsx("button", { className: "be-kv-delete", onClick: () => removeSubItem(id, i), children: "\xD7" })
185
+ ] }, i);
186
+ }),
187
+ /* @__PURE__ */ jsx("button", { className: "be-add-row-btn", onClick: () => addSubItem(id, "infobox"), children: "+ \uD56D\uBAA9 \uCD94\uAC00" })
188
+ ] });
189
+ case "callout":
190
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
191
+ inp("title", "\uC81C\uBAA9 (\uC120\uD0DD)", { fontSize: 11, marginBottom: 4 }),
192
+ ta("text", "\uAC15\uC870 \uB0B4\uC6A9...")
193
+ ] });
194
+ case "numcards":
195
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
196
+ (_c = block.items) == null ? void 0 : _c.map((_, i) => /* @__PURE__ */ jsxs("div", { className: "be-subitem-card", children: [
197
+ /* @__PURE__ */ jsxs("div", { className: "be-subitem-header", children: [
198
+ /* @__PURE__ */ jsxs("span", { className: "be-subitem-label", children: [
199
+ "\uCE74\uB4DC ",
200
+ String(i + 1).padStart(2, "0")
201
+ ] }),
202
+ /* @__PURE__ */ jsx("button", { className: "be-kv-delete", onClick: () => removeSubItem(id, i), children: "\xD7" })
203
+ ] }),
204
+ iInp(i, "title", "\uC81C\uBAA9", { fontSize: 11, margin: "4px 0 3px" }),
205
+ iTa(i, "desc", "\uC124\uBA85", { minHeight: 36 })
206
+ ] }, i)),
207
+ /* @__PURE__ */ jsx("button", { className: "be-add-row-btn", onClick: () => addSubItem(id, "numcards"), children: "+ \uCE74\uB4DC \uCD94\uAC00" })
208
+ ] });
209
+ case "qa":
210
+ return /* @__PURE__ */ jsxs("div", { className: "be-quote-area", children: [
211
+ inp("q", "\uC9C8\uBB38", { fontSize: 12, marginBottom: 4 }),
212
+ ta("a", "\uB2F5\uBCC0")
213
+ ] });
214
+ case "press-list":
215
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
216
+ (_d = block.items) == null ? void 0 : _d.map((_, i) => /* @__PURE__ */ jsxs("div", { className: "be-subitem-card", children: [
217
+ /* @__PURE__ */ jsxs("div", { className: "be-subitem-header", children: [
218
+ /* @__PURE__ */ jsxs("span", { className: "be-subitem-label", children: [
219
+ "\uBCF4\uB3C4 ",
220
+ i + 1
221
+ ] }),
222
+ /* @__PURE__ */ jsx("button", { className: "be-kv-delete", onClick: () => removeSubItem(id, i), children: "\xD7" })
223
+ ] }),
224
+ /* @__PURE__ */ jsxs("div", { className: "be-subitem-2col", children: [
225
+ iInp(i, "src", "\uB9E4\uCCB4\uBA85", { fontSize: 10 }),
226
+ iInp(i, "date", "\uBCF4\uB3C4\uC77C", { fontSize: 10 })
227
+ ] }),
228
+ iInp(i, "title", "\uAE30\uC0AC \uC81C\uBAA9", { fontSize: 11, marginBottom: 3 }),
229
+ iInp(i, "ex", "\uC694\uC57D", { fontSize: 10, marginBottom: 3 }),
230
+ iInp(i, "link", "\uC6D0\uBB38 URL", { fontSize: 10 })
231
+ ] }, i)),
232
+ /* @__PURE__ */ jsx("button", { className: "be-add-row-btn", onClick: () => addSubItem(id, "press-list"), children: "+ \uBCF4\uB3C4 \uCD94\uAC00" })
233
+ ] });
234
+ case "timeline":
235
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
236
+ (_e = block.items) == null ? void 0 : _e.map((_, i) => /* @__PURE__ */ jsxs("div", { className: "be-timeline-card", children: [
237
+ /* @__PURE__ */ jsxs("div", { className: "be-subitem-header", children: [
238
+ /* @__PURE__ */ jsxs("span", { className: "be-subitem-label", children: [
239
+ "Step ",
240
+ i + 1
241
+ ] }),
242
+ /* @__PURE__ */ jsx("button", { className: "be-kv-delete", onClick: () => removeSubItem(id, i), children: "\xD7" })
243
+ ] }),
244
+ iInp(i, "date", "\uB0A0\uC9DC/\uC2DC\uAE30", { fontSize: 10, margin: "4px 0 3px" }),
245
+ iInp(i, "title", "\uC81C\uBAA9", { fontSize: 11, marginBottom: 3 }),
246
+ iInp(i, "desc", "\uC124\uBA85 (\uC120\uD0DD)", { fontSize: 10 })
247
+ ] }, i)),
248
+ /* @__PURE__ */ jsx("button", { className: "be-add-row-btn", onClick: () => addSubItem(id, "timeline"), children: "+ \uB2E8\uACC4 \uCD94\uAC00" })
249
+ ] });
250
+ case "video":
251
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
252
+ inp("url", "YouTube URL (\uC608: https://www.youtube.com/embed/...)", { fontSize: 11, marginBottom: 4 }),
253
+ inp("cap", "\uCEA1\uC158 (\uC120\uD0DD)", { fontSize: 11 })
254
+ ] });
255
+ case "cta":
256
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
257
+ ta("text", "\uD589\uB3D9\uC744 \uC720\uB3C4\uD558\uB294 \uBA54\uC2DC\uC9C0", { minHeight: 36 }),
258
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, marginTop: 4 }, children: [
259
+ inp("label", "\uBC84\uD2BC \uD14D\uC2A4\uD2B8", { fontSize: 11, flex: 1 }),
260
+ inp("url", "\uBC84\uD2BC \uB9C1\uD06C URL", { fontSize: 11, flex: 2 })
261
+ ] })
262
+ ] });
263
+ default:
264
+ return null;
265
+ }
266
+ }
267
+
268
+ export {
269
+ BlockRenderer
270
+ };
@@ -0,0 +1,313 @@
1
+ import {
2
+ createEmptyBlock
3
+ } from "./chunk-EERQYNER.js";
4
+ import {
5
+ createHtmlRenderer
6
+ } from "./chunk-62BAOSP6.js";
7
+ import {
8
+ createSerializer
9
+ } from "./chunk-PPVXNJWI.js";
10
+ import {
11
+ BlockRenderer
12
+ } from "./chunk-CJGZUEQO.js";
13
+ import {
14
+ __spreadProps,
15
+ __spreadValues
16
+ } from "./chunk-N3ETBM74.js";
17
+
18
+ // src/components/BlockEditor.tsx
19
+ import { useState, useCallback, useRef, useEffect } from "react";
20
+ import { jsx, jsxs } from "react/jsx-runtime";
21
+ function isSerialized(props) {
22
+ return "content" in props;
23
+ }
24
+ function BlockEditor(props) {
25
+ var _a;
26
+ const { config, category = "", onImageUploaded, onModeChange } = props;
27
+ const { enableDragDrop = true, enableCategoryFilter = false } = config;
28
+ const serializer = useRef(createSerializer(config.marker));
29
+ const renderer = useRef(createHtmlRenderer(
30
+ config.cssPrefix,
31
+ (_a = config.catClasses) == null ? void 0 : _a[category]
32
+ ));
33
+ useEffect(() => {
34
+ var _a2;
35
+ renderer.current = createHtmlRenderer(config.cssPrefix, (_a2 = config.catClasses) == null ? void 0 : _a2[category]);
36
+ }, [category, config.cssPrefix, config.catClasses]);
37
+ const [blocks, setBlocksState] = useState(() => {
38
+ var _a2;
39
+ if (isSerialized(props)) {
40
+ const existing = serializer.current.deserialize(props.content);
41
+ if (existing && existing.length > 0) return existing;
42
+ const tpl = (_a2 = config.templates) == null ? void 0 : _a2[category];
43
+ if (tpl) return tpl.map((t, i) => __spreadProps(__spreadValues({}, t), { id: i }));
44
+ return [];
45
+ }
46
+ return props.blocks;
47
+ });
48
+ const nextId = useRef(blocks.length > 0 ? Math.max(...blocks.map((b) => b.id)) + 1 : 0);
49
+ const editorRef = useRef(null);
50
+ const isInitialMount = useRef(true);
51
+ const prevCategoryRef = useRef(category);
52
+ const onChangeRef = useRef(null);
53
+ if (isSerialized(props)) {
54
+ onChangeRef.current = props.onChange;
55
+ }
56
+ const onBlocksChangeRef = useRef(null);
57
+ if (!isSerialized(props)) {
58
+ onBlocksChangeRef.current = props.onBlocksChange;
59
+ }
60
+ const blocksRef = useRef(blocks);
61
+ blocksRef.current = blocks;
62
+ const setBlocks = useCallback((updater) => {
63
+ setBlocksState((prev) => {
64
+ const next = typeof updater === "function" ? updater(prev) : updater;
65
+ return next;
66
+ });
67
+ }, []);
68
+ useEffect(() => {
69
+ if (!isSerialized(props)) {
70
+ setBlocksState(props.blocks);
71
+ }
72
+ }, [!isSerialized(props) && props.blocks]);
73
+ const [draggedId, setDraggedId] = useState(null);
74
+ const [dragOverId, setDragOverId] = useState(null);
75
+ const dragOverDir = useRef("below");
76
+ useEffect(() => {
77
+ if (isInitialMount.current) {
78
+ isInitialMount.current = false;
79
+ return;
80
+ }
81
+ if (onChangeRef.current) {
82
+ const html = renderer.current.renderBlocksWrapped(blocks);
83
+ const data = serializer.current.serialize(blocks);
84
+ onChangeRef.current(html + data);
85
+ }
86
+ if (onBlocksChangeRef.current) {
87
+ onBlocksChangeRef.current(blocks);
88
+ }
89
+ }, [blocks, category]);
90
+ useEffect(() => {
91
+ var _a2;
92
+ if (prevCategoryRef.current === category) return;
93
+ prevCategoryRef.current = category;
94
+ const currentBlocks = blocksRef.current;
95
+ const hasContent = currentBlocks.some((b) => {
96
+ var _a3;
97
+ if (b.text || b.en || b.src || b.src1 || b.src2 || b.src3) return true;
98
+ if (b.name || b.bio || b.q || b.a || b.url) return true;
99
+ if ((_a3 = b.items) == null ? void 0 : _a3.some((it) => Object.entries(it).some(([key, val]) => key !== "k" && val))) return true;
100
+ return false;
101
+ });
102
+ if (!hasContent && ((_a2 = config.templates) == null ? void 0 : _a2[category])) {
103
+ const id = nextId.current;
104
+ const tpl = config.templates[category].map((t, i) => __spreadProps(__spreadValues({}, t), { id: id + i }));
105
+ nextId.current = id + tpl.length;
106
+ setBlocks(tpl);
107
+ }
108
+ }, [category, config.templates, setBlocks]);
109
+ const hasBlockContent = useCallback(() => {
110
+ return blocksRef.current.some((b) => {
111
+ var _a2;
112
+ if (b.text || b.en || b.src || b.src1 || b.src2 || b.src3) return true;
113
+ if (b.name || b.bio || b.q || b.a || b.url) return true;
114
+ if ((_a2 = b.items) == null ? void 0 : _a2.some((it) => Object.values(it).some((val) => val))) return true;
115
+ return false;
116
+ });
117
+ }, []);
118
+ const loadTemplate = useCallback(() => {
119
+ var _a2;
120
+ if (!((_a2 = config.templates) == null ? void 0 : _a2[category])) return;
121
+ if (hasBlockContent() && !confirm("\uD604\uC7AC \uC791\uC131 \uC911\uC778 \uB0B4\uC6A9\uC774 \uBAA8\uB450 \uCD08\uAE30\uD654\uB429\uB2C8\uB2E4. \uACC4\uC18D\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?")) return;
122
+ const id = nextId.current;
123
+ const tpl = config.templates[category].map((t, i) => __spreadProps(__spreadValues({}, t), { id: id + i }));
124
+ nextId.current = id + tpl.length;
125
+ setBlocks(tpl);
126
+ onModeChange == null ? void 0 : onModeChange("template");
127
+ }, [category, config.templates, hasBlockContent, onModeChange, setBlocks]);
128
+ const loadSample = useCallback(() => {
129
+ var _a2;
130
+ if (!((_a2 = config.samples) == null ? void 0 : _a2[category])) return;
131
+ if (hasBlockContent() && !confirm("\uD604\uC7AC \uC791\uC131 \uC911\uC778 \uB0B4\uC6A9\uC774 \uBAA8\uB450 \uCD08\uAE30\uD654\uB429\uB2C8\uB2E4. \uACC4\uC18D\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?")) return;
132
+ const id = nextId.current;
133
+ const smp = config.samples[category].map((t, i) => __spreadProps(__spreadValues({}, t), { id: id + i }));
134
+ nextId.current = id + smp.length;
135
+ setBlocks(smp);
136
+ onModeChange == null ? void 0 : onModeChange("sample");
137
+ }, [category, config.samples, hasBlockContent, onModeChange, setBlocks]);
138
+ const addBlock = useCallback((type) => {
139
+ const def = config.blocks.find((d) => d.type === type);
140
+ const id = nextId.current++;
141
+ const newBlock = def ? def.createEmpty(id) : createEmptyBlock(type, id);
142
+ setBlocks((prev) => [...prev, newBlock]);
143
+ setTimeout(() => {
144
+ var _a2;
145
+ (_a2 = editorRef.current) == null ? void 0 : _a2.scrollTo({ top: editorRef.current.scrollHeight, behavior: "smooth" });
146
+ }, 50);
147
+ }, [config.blocks, setBlocks]);
148
+ const removeBlock = useCallback((id) => {
149
+ setBlocks((prev) => prev.filter((b) => b.id !== id));
150
+ }, [setBlocks]);
151
+ const moveBlock = useCallback((id, dir) => {
152
+ setBlocks((prev) => {
153
+ const i = prev.findIndex((b) => b.id === id);
154
+ const ni = i + dir;
155
+ if (ni < 0 || ni >= prev.length) return prev;
156
+ const next = [...prev];
157
+ [next[i], next[ni]] = [next[ni], next[i]];
158
+ return next;
159
+ });
160
+ }, [setBlocks]);
161
+ const updateBlock = useCallback((id, field, value) => {
162
+ setBlocks(
163
+ (prev) => prev.map((b) => {
164
+ if (b.id !== id) return b;
165
+ const parts = field.split(".");
166
+ if (parts.length === 3 && parts[0] === "items") {
167
+ const idx = parseInt(parts[1]);
168
+ const key = parts[2];
169
+ const items = [...b.items || []];
170
+ items[idx] = __spreadProps(__spreadValues({}, items[idx]), { [key]: value });
171
+ return __spreadProps(__spreadValues({}, b), { items });
172
+ }
173
+ return __spreadProps(__spreadValues({}, b), { [field]: value });
174
+ })
175
+ );
176
+ }, [setBlocks]);
177
+ const addSubItem = useCallback((id, type) => {
178
+ setBlocks(
179
+ (prev) => prev.map((b) => {
180
+ if (b.id !== id) return b;
181
+ const items = [...b.items || []];
182
+ switch (type) {
183
+ case "stats":
184
+ items.push({ num: "", label: "" });
185
+ break;
186
+ case "infobox":
187
+ items.push({ k: "", v: "" });
188
+ break;
189
+ case "numcards":
190
+ items.push({ title: "", desc: "" });
191
+ break;
192
+ case "press-list":
193
+ items.push({ src: "", date: "", title: "", ex: "", link: "" });
194
+ break;
195
+ case "timeline":
196
+ items.push({ date: "", title: "", desc: "" });
197
+ break;
198
+ }
199
+ return __spreadProps(__spreadValues({}, b), { items });
200
+ })
201
+ );
202
+ }, [setBlocks]);
203
+ const removeSubItem = useCallback((id, idx) => {
204
+ setBlocks(
205
+ (prev) => prev.map((b) => {
206
+ if (b.id !== id) return b;
207
+ const items = [...b.items || []];
208
+ items.splice(idx, 1);
209
+ return __spreadProps(__spreadValues({}, b), { items });
210
+ })
211
+ );
212
+ }, [setBlocks]);
213
+ const handleDragStart = useCallback((e, id) => {
214
+ setDraggedId(id);
215
+ e.dataTransfer.effectAllowed = "move";
216
+ e.dataTransfer.setData("text/plain", String(id));
217
+ const el = e.target.closest(".be-block");
218
+ if (el) requestAnimationFrame(() => {
219
+ el.style.opacity = "0.4";
220
+ });
221
+ }, []);
222
+ const handleDragEnd = useCallback((e) => {
223
+ const el = e.target.closest(".be-block");
224
+ if (el) el.style.opacity = "";
225
+ setDraggedId(null);
226
+ setDragOverId(null);
227
+ }, []);
228
+ const handleDragOver = useCallback((e, id) => {
229
+ e.preventDefault();
230
+ e.dataTransfer.dropEffect = "move";
231
+ if (id === draggedId) return;
232
+ const rect = e.currentTarget.getBoundingClientRect();
233
+ const midY = rect.top + rect.height / 2;
234
+ dragOverDir.current = e.clientY < midY ? "above" : "below";
235
+ setDragOverId(id);
236
+ }, [draggedId]);
237
+ const handleDrop = useCallback((e, targetId) => {
238
+ e.preventDefault();
239
+ if (draggedId === null || draggedId === targetId) return;
240
+ setBlocks((prev) => {
241
+ const fromIdx = prev.findIndex((b) => b.id === draggedId);
242
+ const toIdx = prev.findIndex((b) => b.id === targetId);
243
+ if (fromIdx === -1 || toIdx === -1) return prev;
244
+ const next = [...prev];
245
+ const [moved] = next.splice(fromIdx, 1);
246
+ const insertIdx = dragOverDir.current === "above" ? toIdx > fromIdx ? toIdx - 1 : toIdx : toIdx > fromIdx ? toIdx : toIdx + 1;
247
+ next.splice(insertIdx, 0, moved);
248
+ return next;
249
+ });
250
+ setDraggedId(null);
251
+ setDragOverId(null);
252
+ }, [draggedId, setBlocks]);
253
+ const availableBlocks = enableCategoryFilter && category ? config.blocks.filter((def) => !def.cats || def.cats.includes(category)) : config.blocks;
254
+ const blockLabel = (type) => {
255
+ var _a2;
256
+ return ((_a2 = config.blocks.find((d) => d.type === type)) == null ? void 0 : _a2.label) || type;
257
+ };
258
+ const hasTemplates = !!(config.templates && config.templates[category]);
259
+ const hasSamples = !!(config.samples && config.samples[category]);
260
+ return /* @__PURE__ */ jsxs("div", { className: "be-blocks-section", ref: editorRef, children: [
261
+ (hasTemplates || hasSamples) && /* @__PURE__ */ jsxs("div", { className: "be-mode-bar", children: [
262
+ /* @__PURE__ */ jsx("span", { className: "be-mode-label", children: "\uCD08\uAE30\uD654" }),
263
+ hasTemplates && /* @__PURE__ */ jsx("button", { className: "be-mode-btn", onClick: loadTemplate, children: "\uBE48 \uD15C\uD50C\uB9BF" }),
264
+ hasSamples && /* @__PURE__ */ jsx("button", { className: "be-mode-btn", onClick: loadSample, children: "\uC608\uC2DC \uCF58\uD150\uCE20" }),
265
+ enableCategoryFilter && /* @__PURE__ */ jsx("span", { className: "be-mode-hint", children: "\uCE74\uD14C\uACE0\uB9AC\uBCC4 \uBE14\uB85D \uAD6C\uC131\uC774 \uC790\uB3D9 \uC801\uC6A9\uB429\uB2C8\uB2E4" })
266
+ ] }),
267
+ /* @__PURE__ */ jsx("div", { className: "be-blocks", children: blocks.map((block) => /* @__PURE__ */ jsxs(
268
+ "div",
269
+ {
270
+ className: `be-block${draggedId === block.id ? " be-dragging" : ""}${dragOverId === block.id && draggedId !== block.id ? ` be-drag-over be-drag-${dragOverDir.current}` : ""}`,
271
+ draggable: enableDragDrop,
272
+ onDragStart: enableDragDrop ? (e) => handleDragStart(e, block.id) : void 0,
273
+ onDragEnd: enableDragDrop ? handleDragEnd : void 0,
274
+ onDragOver: enableDragDrop ? (e) => handleDragOver(e, block.id) : void 0,
275
+ onDrop: enableDragDrop ? (e) => handleDrop(e, block.id) : void 0,
276
+ children: [
277
+ /* @__PURE__ */ jsxs("div", { className: "be-block-header", children: [
278
+ /* @__PURE__ */ jsxs("div", { className: "be-block-type", children: [
279
+ enableDragDrop && /* @__PURE__ */ jsx("span", { className: "be-block-drag", children: "\u283F" }),
280
+ blockLabel(block.type)
281
+ ] }),
282
+ /* @__PURE__ */ jsxs("div", { className: "be-block-actions", children: [
283
+ /* @__PURE__ */ jsx("button", { className: "be-block-btn", onClick: () => moveBlock(block.id, -1), children: "\u2191" }),
284
+ /* @__PURE__ */ jsx("button", { className: "be-block-btn", onClick: () => moveBlock(block.id, 1), children: "\u2193" }),
285
+ /* @__PURE__ */ jsx("button", { className: "be-block-btn be-block-btn-del", onClick: () => removeBlock(block.id), children: "\xD7" })
286
+ ] })
287
+ ] }),
288
+ /* @__PURE__ */ jsx("div", { className: "be-block-body", children: /* @__PURE__ */ jsx(
289
+ BlockRenderer,
290
+ {
291
+ block,
292
+ updateBlock,
293
+ addSubItem,
294
+ removeSubItem,
295
+ onImageUploaded
296
+ }
297
+ ) })
298
+ ]
299
+ },
300
+ block.id
301
+ )) }),
302
+ blocks.length === 0 && /* @__PURE__ */ jsx("div", { className: "be-empty", children: "\uC544\uB798 \uBC84\uD2BC\uC73C\uB85C \uCF58\uD150\uCE20 \uBE14\uB85D\uC744 \uCD94\uAC00\uD558\uC138\uC694" }),
303
+ /* @__PURE__ */ jsx("div", { className: "be-add-bar", children: availableBlocks.map((def) => /* @__PURE__ */ jsxs("button", { className: "be-add-btn", title: def.desc, onClick: () => addBlock(def.type), children: [
304
+ def.icon,
305
+ " ",
306
+ def.label
307
+ ] }, def.type)) })
308
+ ] });
309
+ }
310
+
311
+ export {
312
+ BlockEditor
313
+ };
@@ -0,0 +1,25 @@
1
+ // src/context/BlockEditorProvider.tsx
2
+ import { createContext, useContext } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var BlockEditorContext = createContext(null);
5
+ function BlockEditorProvider({
6
+ uploadImage,
7
+ onError = console.error,
8
+ autoResize = true,
9
+ maxSizeMB = 10,
10
+ children
11
+ }) {
12
+ return /* @__PURE__ */ jsx(BlockEditorContext.Provider, { value: { uploadImage, onError, autoResize, maxSizeMB }, children });
13
+ }
14
+ function useBlockEditorContext() {
15
+ const ctx = useContext(BlockEditorContext);
16
+ if (!ctx) {
17
+ throw new Error("BlockEditorProvider is required. Wrap your editor with <BlockEditorProvider>.");
18
+ }
19
+ return ctx;
20
+ }
21
+
22
+ export {
23
+ BlockEditorProvider,
24
+ useBlockEditorContext
25
+ };