smartrte-react 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ClassicEditor.d.ts +6 -1
- package/dist/components/ClassicEditor.js +74 -55
- package/dist/components/MediaManager.d.ts +31 -0
- package/dist/components/MediaManager.js +167 -0
- package/dist/index.d.ts +1 -0
- package/dist/standalone/classic-editor-embed.js +1 -1
- package/dist/standalone/editor.js +67 -67
- package/package.json +14 -13
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MediaManagerAdapter } from "./MediaManager";
|
|
1
2
|
type ClassicEditorProps = {
|
|
2
3
|
value?: string;
|
|
3
4
|
onChange?: (html: string) => void;
|
|
@@ -5,6 +6,10 @@ type ClassicEditorProps = {
|
|
|
5
6
|
minHeight?: number | string;
|
|
6
7
|
maxHeight?: number | string;
|
|
7
8
|
readOnly?: boolean;
|
|
9
|
+
table?: boolean;
|
|
10
|
+
media?: boolean;
|
|
11
|
+
formula?: boolean;
|
|
12
|
+
mediaManager?: MediaManagerAdapter;
|
|
8
13
|
};
|
|
9
|
-
export declare function ClassicEditor({ value, onChange, placeholder, minHeight, maxHeight, readOnly, }: ClassicEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare function ClassicEditor({ value, onChange, placeholder, minHeight, maxHeight, readOnly, table, media, formula, mediaManager, }: ClassicEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
10
15
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
|
|
3
|
+
import { MediaManager } from "./MediaManager";
|
|
4
|
+
export function ClassicEditor({ value, onChange, placeholder = "Type here…", minHeight = 200, maxHeight = 500, readOnly = false, table = true, media = true, formula = true, mediaManager, }) {
|
|
4
5
|
const editableRef = useRef(null);
|
|
5
6
|
const lastEmittedRef = useRef("");
|
|
6
7
|
const isComposingRef = useRef(false);
|
|
@@ -16,6 +17,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
16
17
|
const [tableMenu, setTableMenu] = useState(null);
|
|
17
18
|
const selectionRef = useRef(null);
|
|
18
19
|
const selectingRef = useRef(null);
|
|
20
|
+
const [showMediaManager, setShowMediaManager] = useState(false);
|
|
19
21
|
useEffect(() => {
|
|
20
22
|
const el = editableRef.current;
|
|
21
23
|
if (!el)
|
|
@@ -62,6 +64,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
62
64
|
exec("createLink", url);
|
|
63
65
|
};
|
|
64
66
|
const insertImage = () => {
|
|
67
|
+
if (!media)
|
|
68
|
+
return;
|
|
65
69
|
fileInputRef.current?.click();
|
|
66
70
|
};
|
|
67
71
|
const scheduleImageOverlay = () => {
|
|
@@ -108,9 +112,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
108
112
|
range = document.createRange();
|
|
109
113
|
range.selectNodeContents(host);
|
|
110
114
|
range.collapse(false);
|
|
111
|
-
|
|
112
|
-
sel?.removeAllRanges();
|
|
113
|
-
sel?.addRange(range);
|
|
115
|
+
safeSelectRange(range);
|
|
114
116
|
}
|
|
115
117
|
const img = document.createElement("img");
|
|
116
118
|
img.src = src;
|
|
@@ -127,18 +129,31 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
127
129
|
const r = document.createRange();
|
|
128
130
|
r.setStartAfter(img);
|
|
129
131
|
r.collapse(true);
|
|
130
|
-
|
|
131
|
-
s?.removeAllRanges();
|
|
132
|
-
s?.addRange(r);
|
|
132
|
+
safeSelectRange(r);
|
|
133
133
|
setSelectedImage(img);
|
|
134
134
|
scheduleImageOverlay();
|
|
135
135
|
handleInput();
|
|
136
136
|
}
|
|
137
137
|
catch { }
|
|
138
138
|
};
|
|
139
|
+
const safeSelectRange = (range) => {
|
|
140
|
+
try {
|
|
141
|
+
if (!range)
|
|
142
|
+
return;
|
|
143
|
+
const start = range.startContainer;
|
|
144
|
+
if (!start || !start.isConnected)
|
|
145
|
+
return;
|
|
146
|
+
const sel = window.getSelection();
|
|
147
|
+
sel?.removeAllRanges();
|
|
148
|
+
sel?.addRange(range);
|
|
149
|
+
}
|
|
150
|
+
catch { }
|
|
151
|
+
};
|
|
139
152
|
const insertFormulaAtSelection = (tex) => {
|
|
140
153
|
if (!tex)
|
|
141
154
|
return;
|
|
155
|
+
if (!formula)
|
|
156
|
+
return;
|
|
142
157
|
try {
|
|
143
158
|
const host = editableRef.current;
|
|
144
159
|
if (!host)
|
|
@@ -150,9 +165,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
150
165
|
range = document.createRange();
|
|
151
166
|
range.selectNodeContents(host);
|
|
152
167
|
range.collapse(false);
|
|
153
|
-
|
|
154
|
-
sel?.removeAllRanges();
|
|
155
|
-
sel?.addRange(range);
|
|
168
|
+
safeSelectRange(range);
|
|
156
169
|
}
|
|
157
170
|
const span = document.createElement("span");
|
|
158
171
|
span.setAttribute("data-formula", tex);
|
|
@@ -176,9 +189,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
176
189
|
const r = document.createRange();
|
|
177
190
|
r.setStartAfter(span);
|
|
178
191
|
r.collapse(true);
|
|
179
|
-
|
|
180
|
-
s?.removeAllRanges();
|
|
181
|
-
s?.addRange(r);
|
|
192
|
+
safeSelectRange(r);
|
|
182
193
|
handleInput();
|
|
183
194
|
}
|
|
184
195
|
catch { }
|
|
@@ -224,6 +235,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
224
235
|
return s;
|
|
225
236
|
};
|
|
226
237
|
const handleLocalImageFiles = async (files) => {
|
|
238
|
+
if (!media)
|
|
239
|
+
return;
|
|
227
240
|
const list = Array.from(files).filter((f) => f.type.startsWith("image/"));
|
|
228
241
|
for (const f of list) {
|
|
229
242
|
await new Promise((resolve) => {
|
|
@@ -643,11 +656,11 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
643
656
|
position: "sticky",
|
|
644
657
|
top: 0,
|
|
645
658
|
zIndex: 1,
|
|
646
|
-
}, children: [_jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", multiple: true, style: { display: "none" }, onChange: (e) => {
|
|
659
|
+
}, children: [media && (_jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", multiple: true, style: { display: "none" }, onChange: (e) => {
|
|
647
660
|
if (e.currentTarget.files)
|
|
648
661
|
handleLocalImageFiles(e.currentTarget.files);
|
|
649
662
|
e.currentTarget.value = "";
|
|
650
|
-
} }), _jsxs("select", { defaultValue: "p", onChange: (e) => {
|
|
663
|
+
} })), _jsxs("select", { defaultValue: "p", onChange: (e) => {
|
|
651
664
|
const val = e.target.value;
|
|
652
665
|
if (val === "p")
|
|
653
666
|
applyFormatBlock("<p>");
|
|
@@ -657,39 +670,42 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
657
670
|
applyFormatBlock("<h2>");
|
|
658
671
|
else if (val === "h3")
|
|
659
672
|
applyFormatBlock("<h3>");
|
|
660
|
-
}, title: "Paragraph/Heading", children: [_jsx("option", { value: "p", children: "Paragraph" }), _jsx("option", { value: "h1", children: "Heading 1" }), _jsx("option", { value: "h2", children: "Heading 2" }), _jsx("option", { value: "h3", children: "Heading 3" })] }), _jsx("button", { onClick: () => exec("bold"), children: "B" }), _jsx("button", { onClick: () => exec("italic"), children: "I" }), _jsx("button", { onClick: () => exec("underline"), children: "U" }), _jsx("button", { onClick: () => exec("strikeThrough"), children: "S" }), _jsx("button", { onClick: () => exec("insertUnorderedList"), children: "\u2022 List" }), _jsx("button", { onClick: () => exec("insertOrderedList"), children: "1. List" }), _jsx("button", { onClick: () => exec("formatBlock", "<blockquote>"), children: "\u275D" }), _jsx("button", { onClick: () => exec("formatBlock", "<pre>"), children: "< />" }), _jsx("button", { title: "Formula", onClick: () => setShowFormulaDialog(true), children: "\u2211" }), _jsx("button", { onClick: insertLink, children: "Link" }), _jsx("button", { onClick: () => exec("unlink"), children: "Unlink" }), _jsx("button", { onClick: insertImage, children: "Image" }), _jsxs("div", { style: {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
673
|
+
}, title: "Paragraph/Heading", children: [_jsx("option", { value: "p", children: "Paragraph" }), _jsx("option", { value: "h1", children: "Heading 1" }), _jsx("option", { value: "h2", children: "Heading 2" }), _jsx("option", { value: "h3", children: "Heading 3" })] }), _jsx("button", { onClick: () => exec("bold"), children: "B" }), _jsx("button", { onClick: () => exec("italic"), children: "I" }), _jsx("button", { onClick: () => exec("underline"), children: "U" }), _jsx("button", { onClick: () => exec("strikeThrough"), children: "S" }), _jsx("button", { onClick: () => exec("insertUnorderedList"), children: "\u2022 List" }), _jsx("button", { onClick: () => exec("insertOrderedList"), children: "1. List" }), _jsx("button", { onClick: () => exec("formatBlock", "<blockquote>"), children: "\u275D" }), _jsx("button", { onClick: () => exec("formatBlock", "<pre>"), children: "< />" }), formula && (_jsx("button", { title: "Formula", onClick: () => setShowFormulaDialog(true), children: "\u2211" })), _jsx("button", { onClick: insertLink, children: "Link" }), _jsx("button", { onClick: () => exec("unlink"), children: "Unlink" }), media && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: insertImage, children: "Image" }), mediaManager && (_jsx("button", { onClick: () => setShowMediaManager(true), children: "Media manager" })), _jsxs("div", { style: {
|
|
674
|
+
display: "inline-flex",
|
|
675
|
+
gap: 4,
|
|
676
|
+
alignItems: "center",
|
|
677
|
+
marginLeft: 6,
|
|
678
|
+
}, children: [_jsx("span", { style: { fontSize: 12, opacity: 0.7 }, children: "Image align:" }), _jsx("button", { onClick: () => {
|
|
679
|
+
const img = selectedImage;
|
|
680
|
+
if (!img)
|
|
681
|
+
return;
|
|
682
|
+
img.style.display = "block";
|
|
683
|
+
img.style.margin = "0 auto";
|
|
684
|
+
img.style.float = "none";
|
|
685
|
+
scheduleImageOverlay();
|
|
686
|
+
handleInput();
|
|
687
|
+
}, title: "Center", children: "\u2299" }), _jsx("button", { onClick: () => {
|
|
688
|
+
const img = selectedImage;
|
|
689
|
+
if (!img)
|
|
690
|
+
return;
|
|
691
|
+
img.style.display = "inline";
|
|
692
|
+
img.style.float = "left";
|
|
693
|
+
img.style.margin = "0 8px 8px 0";
|
|
694
|
+
scheduleImageOverlay();
|
|
695
|
+
handleInput();
|
|
696
|
+
}, title: "Float left", children: "\u27F8" }), _jsx("button", { onClick: () => {
|
|
697
|
+
const img = selectedImage;
|
|
698
|
+
if (!img)
|
|
699
|
+
return;
|
|
700
|
+
img.style.display = "inline";
|
|
701
|
+
img.style.float = "right";
|
|
702
|
+
img.style.margin = "0 0 8px 8px";
|
|
703
|
+
scheduleImageOverlay();
|
|
704
|
+
handleInput();
|
|
705
|
+
}, title: "Float right", children: "\u27F9" })] })] })), table && (_jsx("button", { onClick: () => setShowTableDialog(true), children: "+ Table" })), _jsx("button", { onClick: () => exec("undo"), children: "Undo" }), _jsx("button", { onClick: () => exec("redo"), children: "Redo" })] }), media && mediaManager && (_jsx(MediaManager, { open: showMediaManager, onClose: () => setShowMediaManager(false), adapter: mediaManager, onSelect: (item) => {
|
|
706
|
+
if (item?.url)
|
|
707
|
+
insertImageAtSelection(item.url);
|
|
708
|
+
} })), table && showTableDialog && (_jsx("div", { style: {
|
|
693
709
|
position: "fixed",
|
|
694
710
|
inset: 0,
|
|
695
711
|
background: "rgba(0,0,0,0.35)",
|
|
@@ -732,7 +748,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
732
748
|
}, children: [_jsx("button", { onClick: () => setShowTableDialog(false), children: "Cancel" }), _jsx("button", { onClick: () => {
|
|
733
749
|
insertTable();
|
|
734
750
|
setShowTableDialog(false);
|
|
735
|
-
}, children: "Insert" })] })] }) })), showFormulaDialog && (_jsx("div", { style: {
|
|
751
|
+
}, children: "Insert" })] })] }) })), formula && showFormulaDialog && (_jsx("div", { style: {
|
|
736
752
|
position: "fixed",
|
|
737
753
|
inset: 0,
|
|
738
754
|
background: "rgba(0,0,0,0.35)",
|
|
@@ -838,7 +854,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
838
854
|
handleInput();
|
|
839
855
|
}, onPaste: (e) => {
|
|
840
856
|
const items = e.clipboardData?.files;
|
|
841
|
-
if (items && items.length) {
|
|
857
|
+
if (media && items && items.length) {
|
|
842
858
|
const hasImage = Array.from(items).some((f) => f.type.startsWith("image/"));
|
|
843
859
|
if (hasImage) {
|
|
844
860
|
e.preventDefault();
|
|
@@ -850,7 +866,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
850
866
|
e.preventDefault();
|
|
851
867
|
}
|
|
852
868
|
}, onDrop: (e) => {
|
|
853
|
-
if (e.dataTransfer?.files?.length) {
|
|
869
|
+
if (media && e.dataTransfer?.files?.length) {
|
|
854
870
|
e.preventDefault();
|
|
855
871
|
// Try to move caret to drop point
|
|
856
872
|
const x = e.clientX;
|
|
@@ -899,7 +915,9 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
899
915
|
el.innerHTML = "<p><br></p>";
|
|
900
916
|
}
|
|
901
917
|
}, onKeyDown: (e) => {
|
|
902
|
-
if (
|
|
918
|
+
if (formula &&
|
|
919
|
+
(e.metaKey || e.ctrlKey) &&
|
|
920
|
+
String(e.key).toLowerCase() === "m") {
|
|
903
921
|
e.preventDefault();
|
|
904
922
|
setShowFormulaDialog(true);
|
|
905
923
|
return;
|
|
@@ -919,7 +937,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
919
937
|
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
|
|
920
938
|
const sel = window.getSelection();
|
|
921
939
|
const cell = getClosestCell(sel?.anchorNode || null);
|
|
922
|
-
if (
|
|
940
|
+
if (table &&
|
|
941
|
+
cell &&
|
|
923
942
|
cell.parentElement &&
|
|
924
943
|
cell.parentElement.parentElement) {
|
|
925
944
|
const row = cell.parentElement;
|
|
@@ -1003,7 +1022,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1003
1022
|
else {
|
|
1004
1023
|
setTableMenu(null);
|
|
1005
1024
|
}
|
|
1006
|
-
}
|
|
1025
|
+
} }), selectedImage && imageOverlay && (_jsxs("div", { style: {
|
|
1007
1026
|
position: "fixed",
|
|
1008
1027
|
left: imageOverlay.left,
|
|
1009
1028
|
top: imageOverlay.top,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type MediaItem = {
|
|
2
|
+
id: string;
|
|
3
|
+
url: string;
|
|
4
|
+
width?: number;
|
|
5
|
+
height?: number;
|
|
6
|
+
sizeBytes?: number;
|
|
7
|
+
mimeType?: string;
|
|
8
|
+
hashHex?: string;
|
|
9
|
+
createdAt?: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
alt?: string;
|
|
12
|
+
tags?: string[];
|
|
13
|
+
};
|
|
14
|
+
export type MediaSearchQuery = {
|
|
15
|
+
q?: string;
|
|
16
|
+
tags?: string[];
|
|
17
|
+
mimePrefix?: string;
|
|
18
|
+
hashHex?: string;
|
|
19
|
+
page?: number;
|
|
20
|
+
pageSize?: number;
|
|
21
|
+
};
|
|
22
|
+
export type MediaManagerAdapter = {
|
|
23
|
+
upload: (files: File[]) => Promise<MediaItem[]>;
|
|
24
|
+
search: (query: MediaSearchQuery) => Promise<MediaItem[]>;
|
|
25
|
+
};
|
|
26
|
+
export declare function MediaManager(props: {
|
|
27
|
+
open: boolean;
|
|
28
|
+
onClose: () => void;
|
|
29
|
+
adapter: MediaManagerAdapter;
|
|
30
|
+
onSelect: (item: MediaItem) => void;
|
|
31
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
export function MediaManager(props) {
|
|
4
|
+
const { open, onClose, adapter, onSelect } = props;
|
|
5
|
+
const [activeTab, setActiveTab] = useState("upload");
|
|
6
|
+
const [uploading, setUploading] = useState(false);
|
|
7
|
+
const [error, setError] = useState(null);
|
|
8
|
+
const [query, setQuery] = useState("");
|
|
9
|
+
const [results, setResults] = useState([]);
|
|
10
|
+
const fileInputRef = useRef(null);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!open)
|
|
13
|
+
return;
|
|
14
|
+
setError(null);
|
|
15
|
+
if (activeTab === "library") {
|
|
16
|
+
performSearch();
|
|
17
|
+
}
|
|
18
|
+
}, [open, activeTab]);
|
|
19
|
+
const performSearch = async () => {
|
|
20
|
+
try {
|
|
21
|
+
const items = await adapter.search({ q: query, mimePrefix: "image/" });
|
|
22
|
+
setResults(items || []);
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
setError("Failed to search media.");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const computeSha256Hex = async (file) => {
|
|
29
|
+
const buf = await file.arrayBuffer();
|
|
30
|
+
const digest = await crypto.subtle.digest("SHA-256", buf);
|
|
31
|
+
const bytes = new Uint8Array(digest);
|
|
32
|
+
let hex = "";
|
|
33
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
34
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
35
|
+
}
|
|
36
|
+
return hex;
|
|
37
|
+
};
|
|
38
|
+
const handleUploadFiles = async (files) => {
|
|
39
|
+
if (!files || files.length === 0)
|
|
40
|
+
return;
|
|
41
|
+
const list = Array.from(files).filter((f) => f.type.startsWith("image/"));
|
|
42
|
+
if (list.length === 0)
|
|
43
|
+
return;
|
|
44
|
+
setUploading(true);
|
|
45
|
+
setError(null);
|
|
46
|
+
try {
|
|
47
|
+
// Duplicate detection by content hash (best-effort, server should also verify)
|
|
48
|
+
const duplicates = [];
|
|
49
|
+
const toUpload = [];
|
|
50
|
+
for (const f of list) {
|
|
51
|
+
try {
|
|
52
|
+
const hash = await computeSha256Hex(f);
|
|
53
|
+
const hits = await adapter.search({ hashHex: hash });
|
|
54
|
+
if (hits && hits.length) {
|
|
55
|
+
duplicates.push(hits[0]);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
toUpload.push(f);
|
|
59
|
+
}
|
|
60
|
+
catch { }
|
|
61
|
+
}
|
|
62
|
+
if (duplicates.length) {
|
|
63
|
+
// Prefer duplicates immediately
|
|
64
|
+
onSelect(duplicates[0]);
|
|
65
|
+
setUploading(false);
|
|
66
|
+
onClose();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (toUpload.length) {
|
|
70
|
+
const uploaded = await adapter.upload(toUpload);
|
|
71
|
+
if (uploaded && uploaded.length) {
|
|
72
|
+
onSelect(uploaded[0]);
|
|
73
|
+
onClose();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
setError("Upload failed. Try again.");
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
setUploading(false);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
if (!open)
|
|
85
|
+
return null;
|
|
86
|
+
return (_jsx("div", { style: {
|
|
87
|
+
position: "fixed",
|
|
88
|
+
inset: 0,
|
|
89
|
+
background: "rgba(0,0,0,0.35)",
|
|
90
|
+
display: "flex",
|
|
91
|
+
alignItems: "center",
|
|
92
|
+
justifyContent: "center",
|
|
93
|
+
zIndex: 80,
|
|
94
|
+
}, onClick: onClose, children: _jsxs("div", { style: {
|
|
95
|
+
background: "#fff",
|
|
96
|
+
width: 820,
|
|
97
|
+
maxWidth: "90vw",
|
|
98
|
+
maxHeight: "86vh",
|
|
99
|
+
borderRadius: 10,
|
|
100
|
+
boxShadow: "0 12px 32px rgba(0,0,0,0.22)",
|
|
101
|
+
display: "flex",
|
|
102
|
+
flexDirection: "column",
|
|
103
|
+
}, onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { style: {
|
|
104
|
+
display: "flex",
|
|
105
|
+
alignItems: "center",
|
|
106
|
+
justifyContent: "space-between",
|
|
107
|
+
padding: "10px 14px",
|
|
108
|
+
borderBottom: "1px solid #eee",
|
|
109
|
+
}, children: [_jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsx("button", { onClick: () => setActiveTab("upload"), style: {
|
|
110
|
+
padding: "6px 10px",
|
|
111
|
+
borderRadius: 6,
|
|
112
|
+
border: "1px solid #ddd",
|
|
113
|
+
background: activeTab === "upload" ? "#f2f2f2" : "#fff",
|
|
114
|
+
}, children: "Upload" }), _jsx("button", { onClick: () => setActiveTab("library"), style: {
|
|
115
|
+
padding: "6px 10px",
|
|
116
|
+
borderRadius: 6,
|
|
117
|
+
border: "1px solid #ddd",
|
|
118
|
+
background: activeTab === "library" ? "#f2f2f2" : "#fff",
|
|
119
|
+
}, children: "Library" })] }), _jsx("button", { onClick: onClose, children: "\u2715" })] }), error && (_jsx("div", { style: { color: "#b00020", padding: "8px 14px" }, children: error })), activeTab === "upload" ? (_jsxs("div", { style: { padding: 16 }, children: [_jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", multiple: true, style: { display: "none" }, onChange: (e) => handleUploadFiles(e.currentTarget.files) }), _jsx("div", { onClick: () => fileInputRef.current?.click(), onDragOver: (e) => {
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
}, onDrop: (e) => {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
handleUploadFiles(e.dataTransfer.files);
|
|
124
|
+
}, style: {
|
|
125
|
+
border: "2px dashed #bbb",
|
|
126
|
+
borderRadius: 10,
|
|
127
|
+
padding: 24,
|
|
128
|
+
textAlign: "center",
|
|
129
|
+
color: "#333",
|
|
130
|
+
background: "#fafafa",
|
|
131
|
+
cursor: uploading ? "default" : "pointer",
|
|
132
|
+
opacity: uploading ? 0.7 : 1,
|
|
133
|
+
}, children: uploading ? "Uploading…" : "Click or drag images to upload" })] })) : (_jsxs("div", { style: {
|
|
134
|
+
padding: 16,
|
|
135
|
+
display: "flex",
|
|
136
|
+
flexDirection: "column",
|
|
137
|
+
gap: 12,
|
|
138
|
+
}, children: [_jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsx("input", { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search images by name, tag, etc.", style: {
|
|
139
|
+
flex: 1,
|
|
140
|
+
padding: "6px 8px",
|
|
141
|
+
border: "1px solid #ddd",
|
|
142
|
+
borderRadius: 6,
|
|
143
|
+
} }), _jsx("button", { onClick: performSearch, children: "Search" })] }), _jsx("div", { style: {
|
|
144
|
+
display: "grid",
|
|
145
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))",
|
|
146
|
+
gap: 12,
|
|
147
|
+
overflowY: "auto",
|
|
148
|
+
paddingBottom: 16,
|
|
149
|
+
}, children: results.map((it) => (_jsxs("button", { onClick: () => {
|
|
150
|
+
onSelect(it);
|
|
151
|
+
onClose();
|
|
152
|
+
}, title: it.title || it.url, style: {
|
|
153
|
+
display: "block",
|
|
154
|
+
border: "1px solid #eee",
|
|
155
|
+
borderRadius: 8,
|
|
156
|
+
padding: 6,
|
|
157
|
+
background: "#fff",
|
|
158
|
+
textAlign: "center",
|
|
159
|
+
}, children: [_jsx("img", { src: it.url, alt: it.alt || "", style: {
|
|
160
|
+
maxWidth: "100%",
|
|
161
|
+
maxHeight: 100,
|
|
162
|
+
display: "block",
|
|
163
|
+
margin: "0 auto",
|
|
164
|
+
objectFit: "cover",
|
|
165
|
+
borderRadius: 6,
|
|
166
|
+
} }), _jsx("div", { style: { fontSize: 11, marginTop: 6, color: "#333" }, children: it.width && it.height ? `${it.width}×${it.height}` : "" })] }, it.id || it.url))) })] }))] }) }));
|
|
167
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ function ClassicEditorHost(props, ref) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
catch { }
|
|
40
|
-
}, placeholder: props.placeholder, minHeight: props.minHeight, maxHeight: props.maxHeight, readOnly: props.readOnly }) }));
|
|
40
|
+
}, placeholder: props.placeholder, minHeight: props.minHeight, maxHeight: props.maxHeight, readOnly: props.readOnly, table: props.table, media: props.media, formula: props.formula, mediaManager: props.mediaManager }) }));
|
|
41
41
|
}
|
|
42
42
|
const ClassicEditorHostWithRef = React.forwardRef(ClassicEditorHost);
|
|
43
43
|
function initClassicEditor(opts) {
|