@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.
- package/README.md +219 -0
- package/dist/blocks/built-in.d.ts +6 -0
- package/dist/blocks/built-in.js +11 -0
- package/dist/chunk-3R3HAGQL.js +102 -0
- package/dist/chunk-62BAOSP6.js +100 -0
- package/dist/chunk-CJGZUEQO.js +270 -0
- package/dist/chunk-CLC3FEL2.js +313 -0
- package/dist/chunk-CYMYM7LP.js +25 -0
- package/dist/chunk-EERQYNER.js +123 -0
- package/dist/chunk-G6J2DCC5.js +77 -0
- package/dist/chunk-N3ETBM74.js +24 -0
- package/dist/chunk-PPVXNJWI.js +28 -0
- package/dist/chunk-QR225IXX.js +148 -0
- package/dist/chunk-VIJV6FLT.js +250 -0
- package/dist/components/ArtistEditor.d.ts +12 -0
- package/dist/components/ArtistEditor.js +11 -0
- package/dist/components/BlockEditor.d.ts +24 -0
- package/dist/components/BlockEditor.js +16 -0
- package/dist/components/BlockRenderer.d.ts +10 -0
- package/dist/components/BlockRenderer.js +12 -0
- package/dist/components/ImageUploadField.d.ts +11 -0
- package/dist/components/ImageUploadField.js +11 -0
- package/dist/context/BlockEditorProvider.d.ts +21 -0
- package/dist/context/BlockEditorProvider.js +10 -0
- package/dist/core/html-renderer.d.ts +13 -0
- package/dist/core/html-renderer.js +11 -0
- package/dist/core/image-resize.d.ts +17 -0
- package/dist/core/image-resize.js +11 -0
- package/dist/core/serializer.d.ts +9 -0
- package/dist/core/serializer.js +7 -0
- package/dist/hooks/useImageDropZone.d.ts +23 -0
- package/dist/hooks/useImageDropZone.js +10 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +57 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.js +0 -0
- package/package.json +43 -0
- package/styles/artist.css +332 -0
- package/styles/editor.css +394 -0
- 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
|
+
};
|