@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,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 {};
|