smartrte-react 0.1.6 → 0.1.8
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.js +629 -11
- package/dist/standalone/editor.js +65 -65
- package/package.json +1 -1
|
@@ -6,9 +6,11 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
6
6
|
const lastEmittedRef = useRef("");
|
|
7
7
|
const isComposingRef = useRef(false);
|
|
8
8
|
const fileInputRef = useRef(null);
|
|
9
|
+
const replaceTargetRef = useRef(null);
|
|
9
10
|
const [selectedImage, setSelectedImage] = useState(null);
|
|
10
11
|
const [imageOverlay, setImageOverlay] = useState(null);
|
|
11
12
|
const resizingRef = useRef(null);
|
|
13
|
+
const draggedImageRef = useRef(null);
|
|
12
14
|
const [showTableDialog, setShowTableDialog] = useState(false);
|
|
13
15
|
const [tableRows, setTableRows] = useState(3);
|
|
14
16
|
const [tableCols, setTableCols] = useState(3);
|
|
@@ -17,7 +19,12 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
17
19
|
const [tableMenu, setTableMenu] = useState(null);
|
|
18
20
|
const selectionRef = useRef(null);
|
|
19
21
|
const selectingRef = useRef(null);
|
|
22
|
+
const [imageMenu, setImageMenu] = useState(null);
|
|
20
23
|
const [showMediaManager, setShowMediaManager] = useState(false);
|
|
24
|
+
const [showColorPicker, setShowColorPicker] = useState(false);
|
|
25
|
+
const [colorPickerType, setColorPickerType] = useState('text');
|
|
26
|
+
const savedRangeRef = useRef(null);
|
|
27
|
+
const [currentFontSize, setCurrentFontSize] = useState("11");
|
|
21
28
|
useEffect(() => {
|
|
22
29
|
const el = editableRef.current;
|
|
23
30
|
if (!el)
|
|
@@ -39,6 +46,23 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
39
46
|
el.removeEventListener("contextmenu", onCtx, { capture: true });
|
|
40
47
|
};
|
|
41
48
|
}, [value]);
|
|
49
|
+
// Save selection whenever it changes
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const saveSelection = () => {
|
|
52
|
+
const sel = window.getSelection();
|
|
53
|
+
if (sel && sel.rangeCount > 0) {
|
|
54
|
+
const range = sel.getRangeAt(0);
|
|
55
|
+
const editor = editableRef.current;
|
|
56
|
+
if (editor && editor.contains(range.commonAncestorContainer)) {
|
|
57
|
+
savedRangeRef.current = range.cloneRange();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
document.addEventListener('selectionchange', saveSelection);
|
|
62
|
+
return () => {
|
|
63
|
+
document.removeEventListener('selectionchange', saveSelection);
|
|
64
|
+
};
|
|
65
|
+
}, []);
|
|
42
66
|
const exec = (command, valueArg) => {
|
|
43
67
|
try {
|
|
44
68
|
document.execCommand(command, false, valueArg);
|
|
@@ -63,6 +87,76 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
63
87
|
return;
|
|
64
88
|
exec("createLink", url);
|
|
65
89
|
};
|
|
90
|
+
const applyFontSize = (size) => {
|
|
91
|
+
try {
|
|
92
|
+
// Update current font size state
|
|
93
|
+
setCurrentFontSize(size);
|
|
94
|
+
const editor = editableRef.current;
|
|
95
|
+
if (!editor)
|
|
96
|
+
return;
|
|
97
|
+
editor.focus();
|
|
98
|
+
// Try to get current selection, or use saved range
|
|
99
|
+
let range = null;
|
|
100
|
+
const sel = window.getSelection();
|
|
101
|
+
if (sel && sel.rangeCount > 0) {
|
|
102
|
+
const currentRange = sel.getRangeAt(0);
|
|
103
|
+
// Use current range if it's within our editor
|
|
104
|
+
if (editor.contains(currentRange.commonAncestorContainer)) {
|
|
105
|
+
range = currentRange;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Fallback to saved range if current range is not available
|
|
109
|
+
if (!range && savedRangeRef.current) {
|
|
110
|
+
range = savedRangeRef.current.cloneRange();
|
|
111
|
+
}
|
|
112
|
+
// If no range at all, just update state for future typing
|
|
113
|
+
if (!range)
|
|
114
|
+
return;
|
|
115
|
+
// If range is collapsed (cursor position, no selection), insert an invisible span
|
|
116
|
+
if (range.collapsed) {
|
|
117
|
+
// Create a span with zero-width space that will capture future typing
|
|
118
|
+
const span = document.createElement('span');
|
|
119
|
+
span.style.fontSize = size + 'pt';
|
|
120
|
+
span.textContent = '\u200B'; // Zero-width space
|
|
121
|
+
range.insertNode(span);
|
|
122
|
+
// Position cursor inside the span
|
|
123
|
+
const newRange = document.createRange();
|
|
124
|
+
newRange.setStart(span.firstChild, 1);
|
|
125
|
+
newRange.collapse(true);
|
|
126
|
+
if (sel) {
|
|
127
|
+
sel.removeAllRanges();
|
|
128
|
+
sel.addRange(newRange);
|
|
129
|
+
}
|
|
130
|
+
handleInput();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// If there's selected text, wrap it
|
|
134
|
+
const span = document.createElement('span');
|
|
135
|
+
span.style.fontSize = size + 'pt';
|
|
136
|
+
// Extract the selected content and wrap it in the span
|
|
137
|
+
const fragment = range.extractContents();
|
|
138
|
+
span.appendChild(fragment);
|
|
139
|
+
// Insert the span at the current position
|
|
140
|
+
range.insertNode(span);
|
|
141
|
+
// Update selection to show what was changed
|
|
142
|
+
if (sel) {
|
|
143
|
+
range.selectNodeContents(span);
|
|
144
|
+
sel.removeAllRanges();
|
|
145
|
+
sel.addRange(range);
|
|
146
|
+
}
|
|
147
|
+
// Trigger change event
|
|
148
|
+
handleInput();
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error('Error applying font size:', error);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const applyTextColor = (color) => {
|
|
155
|
+
exec("foreColor", color);
|
|
156
|
+
};
|
|
157
|
+
const applyBackgroundColor = (color) => {
|
|
158
|
+
exec("hiliteColor", color);
|
|
159
|
+
};
|
|
66
160
|
const insertImage = () => {
|
|
67
161
|
if (!media)
|
|
68
162
|
return;
|
|
@@ -116,6 +210,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
116
210
|
}
|
|
117
211
|
const img = document.createElement("img");
|
|
118
212
|
img.src = src;
|
|
213
|
+
img.draggable = true;
|
|
119
214
|
img.style.maxWidth = "100%";
|
|
120
215
|
img.style.height = "auto";
|
|
121
216
|
img.style.display = "inline-block";
|
|
@@ -652,13 +747,36 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
652
747
|
gap: 8,
|
|
653
748
|
padding: 8,
|
|
654
749
|
borderBottom: "1px solid #eee",
|
|
655
|
-
background: "#
|
|
750
|
+
background: "#fff",
|
|
751
|
+
color: "#111",
|
|
656
752
|
position: "sticky",
|
|
657
753
|
top: 0,
|
|
658
754
|
zIndex: 1,
|
|
659
755
|
}, children: [media && (_jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", multiple: true, style: { display: "none" }, onChange: (e) => {
|
|
660
|
-
|
|
661
|
-
|
|
756
|
+
const list = e.currentTarget.files;
|
|
757
|
+
if (list && list.length) {
|
|
758
|
+
if (replaceTargetRef.current) {
|
|
759
|
+
const img = replaceTargetRef.current;
|
|
760
|
+
replaceTargetRef.current = null;
|
|
761
|
+
const f = list[0];
|
|
762
|
+
if (f && f.type.startsWith("image/")) {
|
|
763
|
+
const reader = new FileReader();
|
|
764
|
+
reader.onload = () => {
|
|
765
|
+
const dataUrl = String(reader.result || "");
|
|
766
|
+
if (dataUrl) {
|
|
767
|
+
img.src = dataUrl;
|
|
768
|
+
setSelectedImage(img);
|
|
769
|
+
scheduleImageOverlay();
|
|
770
|
+
handleInput();
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
reader.readAsDataURL(f);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
handleLocalImageFiles(list);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
662
780
|
e.currentTarget.value = "";
|
|
663
781
|
} })), _jsxs("select", { defaultValue: "p", onChange: (e) => {
|
|
664
782
|
const val = e.target.value;
|
|
@@ -670,7 +788,165 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
670
788
|
applyFormatBlock("<h2>");
|
|
671
789
|
else if (val === "h3")
|
|
672
790
|
applyFormatBlock("<h3>");
|
|
673
|
-
}, title: "Paragraph/Heading",
|
|
791
|
+
}, title: "Paragraph/Heading", style: {
|
|
792
|
+
height: 32,
|
|
793
|
+
padding: "0 8px",
|
|
794
|
+
border: "1px solid #e5e7eb",
|
|
795
|
+
borderRadius: 6,
|
|
796
|
+
background: "#fff",
|
|
797
|
+
color: "#111",
|
|
798
|
+
}, 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", { title: "Bold", onClick: () => exec("bold"), style: {
|
|
799
|
+
height: 32,
|
|
800
|
+
minWidth: 32,
|
|
801
|
+
padding: "0 8px",
|
|
802
|
+
border: "1px solid #e5e7eb",
|
|
803
|
+
borderRadius: 6,
|
|
804
|
+
background: "#fff",
|
|
805
|
+
color: "#111",
|
|
806
|
+
}, children: _jsx("span", { style: { fontWeight: 700 }, children: "B" }) }), _jsx("button", { title: "Italic", onClick: () => exec("italic"), style: {
|
|
807
|
+
height: 32,
|
|
808
|
+
minWidth: 32,
|
|
809
|
+
padding: "0 8px",
|
|
810
|
+
border: "1px solid #e5e7eb",
|
|
811
|
+
borderRadius: 6,
|
|
812
|
+
background: "#fff",
|
|
813
|
+
fontStyle: "italic",
|
|
814
|
+
color: "#111",
|
|
815
|
+
}, children: "I" }), _jsx("button", { title: "Underline", onClick: () => exec("underline"), style: {
|
|
816
|
+
height: 32,
|
|
817
|
+
minWidth: 32,
|
|
818
|
+
padding: "0 8px",
|
|
819
|
+
border: "1px solid #e5e7eb",
|
|
820
|
+
borderRadius: 6,
|
|
821
|
+
background: "#fff",
|
|
822
|
+
textDecoration: "underline",
|
|
823
|
+
color: "#111",
|
|
824
|
+
}, children: "U" }), _jsx("button", { title: "Strikethrough", onClick: () => exec("strikeThrough"), style: {
|
|
825
|
+
height: 32,
|
|
826
|
+
minWidth: 32,
|
|
827
|
+
padding: "0 8px",
|
|
828
|
+
border: "1px solid #e5e7eb",
|
|
829
|
+
borderRadius: 6,
|
|
830
|
+
background: "#fff",
|
|
831
|
+
textDecoration: "line-through",
|
|
832
|
+
color: "#111",
|
|
833
|
+
}, children: "S" }), _jsxs("select", { value: currentFontSize, onMouseDown: () => {
|
|
834
|
+
// Save selection before dropdown interaction
|
|
835
|
+
const sel = window.getSelection();
|
|
836
|
+
if (sel && sel.rangeCount > 0) {
|
|
837
|
+
const range = sel.getRangeAt(0);
|
|
838
|
+
const editor = editableRef.current;
|
|
839
|
+
if (editor && editor.contains(range.commonAncestorContainer) && !range.collapsed) {
|
|
840
|
+
savedRangeRef.current = range.cloneRange();
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}, onChange: (e) => applyFontSize(e.target.value), title: "Font Size", style: {
|
|
844
|
+
height: 32,
|
|
845
|
+
padding: "0 8px",
|
|
846
|
+
border: "1px solid #e5e7eb",
|
|
847
|
+
borderRadius: 6,
|
|
848
|
+
background: "#fff",
|
|
849
|
+
color: "#111",
|
|
850
|
+
}, children: [_jsx("option", { value: "8", children: "8" }), _jsx("option", { value: "9", children: "9" }), _jsx("option", { value: "10", children: "10" }), _jsx("option", { value: "11", children: "11" }), _jsx("option", { value: "12", children: "12" }), _jsx("option", { value: "14", children: "14" }), _jsx("option", { value: "18", children: "18" }), _jsx("option", { value: "24", children: "24" }), _jsx("option", { value: "30", children: "30" }), _jsx("option", { value: "36", children: "36" }), _jsx("option", { value: "48", children: "48" }), _jsx("option", { value: "60", children: "60" }), _jsx("option", { value: "72", children: "72" }), _jsx("option", { value: "96", children: "96" })] }), _jsxs("button", { title: "Text Color", onClick: () => {
|
|
851
|
+
setColorPickerType('text');
|
|
852
|
+
setShowColorPicker(true);
|
|
853
|
+
}, style: {
|
|
854
|
+
height: 32,
|
|
855
|
+
minWidth: 32,
|
|
856
|
+
padding: "0 8px",
|
|
857
|
+
border: "1px solid #e5e7eb",
|
|
858
|
+
borderRadius: 6,
|
|
859
|
+
background: "#fff",
|
|
860
|
+
color: "#111",
|
|
861
|
+
position: "relative",
|
|
862
|
+
}, children: [_jsx("span", { style: { fontWeight: 700 }, children: "A" }), _jsx("div", { style: {
|
|
863
|
+
position: "absolute",
|
|
864
|
+
bottom: 4,
|
|
865
|
+
left: "50%",
|
|
866
|
+
transform: "translateX(-50%)",
|
|
867
|
+
width: 16,
|
|
868
|
+
height: 3,
|
|
869
|
+
background: "#000",
|
|
870
|
+
borderRadius: 1,
|
|
871
|
+
} })] }), _jsx("button", { title: "Background Color", onClick: () => {
|
|
872
|
+
setColorPickerType('background');
|
|
873
|
+
setShowColorPicker(true);
|
|
874
|
+
}, style: {
|
|
875
|
+
height: 32,
|
|
876
|
+
minWidth: 32,
|
|
877
|
+
padding: "0 8px",
|
|
878
|
+
border: "1px solid #e5e7eb",
|
|
879
|
+
borderRadius: 6,
|
|
880
|
+
background: "#fff",
|
|
881
|
+
color: "#111",
|
|
882
|
+
}, children: _jsx("span", { style: { fontWeight: 700, background: "#ffeb3b", padding: "2px 4px" }, children: "A" }) }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), style: {
|
|
883
|
+
height: 32,
|
|
884
|
+
padding: "0 10px",
|
|
885
|
+
border: "1px solid #e5e7eb",
|
|
886
|
+
borderRadius: 6,
|
|
887
|
+
background: "#fff",
|
|
888
|
+
color: "#111",
|
|
889
|
+
}, children: "\u2022 List" }), _jsx("button", { title: "Numbered list", onClick: () => exec("insertOrderedList"), style: {
|
|
890
|
+
height: 32,
|
|
891
|
+
padding: "0 10px",
|
|
892
|
+
border: "1px solid #e5e7eb",
|
|
893
|
+
borderRadius: 6,
|
|
894
|
+
background: "#fff",
|
|
895
|
+
color: "#111",
|
|
896
|
+
}, children: "1. List" }), _jsx("button", { title: "Blockquote", onClick: () => exec("formatBlock", "<blockquote>"), style: {
|
|
897
|
+
height: 32,
|
|
898
|
+
minWidth: 32,
|
|
899
|
+
padding: "0 8px",
|
|
900
|
+
border: "1px solid #e5e7eb",
|
|
901
|
+
borderRadius: 6,
|
|
902
|
+
background: "#fff",
|
|
903
|
+
color: "#111",
|
|
904
|
+
}, children: "\u275D" }), _jsx("button", { title: "Code block", onClick: () => exec("formatBlock", "<pre>"), style: {
|
|
905
|
+
height: 32,
|
|
906
|
+
minWidth: 36,
|
|
907
|
+
padding: "0 8px",
|
|
908
|
+
border: "1px solid #e5e7eb",
|
|
909
|
+
borderRadius: 6,
|
|
910
|
+
background: "#fff",
|
|
911
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo",
|
|
912
|
+
color: "#111",
|
|
913
|
+
}, children: "< />" }), formula && (_jsx("button", { title: "Insert formula", onClick: () => setShowFormulaDialog(true), style: {
|
|
914
|
+
height: 32,
|
|
915
|
+
minWidth: 32,
|
|
916
|
+
padding: "0 8px",
|
|
917
|
+
border: "1px solid #e5e7eb",
|
|
918
|
+
borderRadius: 6,
|
|
919
|
+
background: "#fff",
|
|
920
|
+
color: "#111",
|
|
921
|
+
}, children: "\u2211" })), _jsx("button", { title: "Insert link", onClick: insertLink, style: {
|
|
922
|
+
height: 32,
|
|
923
|
+
padding: "0 10px",
|
|
924
|
+
border: "1px solid #e5e7eb",
|
|
925
|
+
borderRadius: 6,
|
|
926
|
+
background: "#fff",
|
|
927
|
+
color: "#111",
|
|
928
|
+
}, children: "Link" }), _jsx("button", { title: "Remove link", onClick: () => exec("unlink"), style: {
|
|
929
|
+
height: 32,
|
|
930
|
+
padding: "0 10px",
|
|
931
|
+
border: "1px solid #e5e7eb",
|
|
932
|
+
borderRadius: 6,
|
|
933
|
+
background: "#fff",
|
|
934
|
+
color: "#111",
|
|
935
|
+
}, children: "Unlink" }), media && (_jsxs(_Fragment, { children: [_jsx("button", { title: "Insert image", onClick: insertImage, style: {
|
|
936
|
+
height: 32,
|
|
937
|
+
padding: "0 10px",
|
|
938
|
+
border: "1px solid #e5e7eb",
|
|
939
|
+
borderRadius: 6,
|
|
940
|
+
background: "#fff",
|
|
941
|
+
color: "#111",
|
|
942
|
+
}, children: "\uD83D\uDDBC\uFE0F Image" }), mediaManager && (_jsx("button", { title: "Open media manager", onClick: () => setShowMediaManager(true), style: {
|
|
943
|
+
height: 32,
|
|
944
|
+
padding: "0 10px",
|
|
945
|
+
border: "1px solid #e5e7eb",
|
|
946
|
+
borderRadius: 6,
|
|
947
|
+
background: "#fff",
|
|
948
|
+
color: "#111",
|
|
949
|
+
}, children: "\uD83D\uDCC1 Media" })), _jsxs("div", { style: {
|
|
674
950
|
display: "inline-flex",
|
|
675
951
|
gap: 4,
|
|
676
952
|
alignItems: "center",
|
|
@@ -684,7 +960,15 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
684
960
|
img.style.float = "none";
|
|
685
961
|
scheduleImageOverlay();
|
|
686
962
|
handleInput();
|
|
687
|
-
}, title: "Center",
|
|
963
|
+
}, title: "Center", style: {
|
|
964
|
+
height: 28,
|
|
965
|
+
minWidth: 28,
|
|
966
|
+
padding: "0 6px",
|
|
967
|
+
border: "1px solid #e5e7eb",
|
|
968
|
+
borderRadius: 6,
|
|
969
|
+
background: "#fff",
|
|
970
|
+
color: "#111",
|
|
971
|
+
}, children: "\u2299" }), _jsx("button", { onClick: () => {
|
|
688
972
|
const img = selectedImage;
|
|
689
973
|
if (!img)
|
|
690
974
|
return;
|
|
@@ -693,7 +977,15 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
693
977
|
img.style.margin = "0 8px 8px 0";
|
|
694
978
|
scheduleImageOverlay();
|
|
695
979
|
handleInput();
|
|
696
|
-
}, title: "Float left",
|
|
980
|
+
}, title: "Float left", style: {
|
|
981
|
+
height: 28,
|
|
982
|
+
minWidth: 28,
|
|
983
|
+
padding: "0 6px",
|
|
984
|
+
border: "1px solid #e5e7eb",
|
|
985
|
+
borderRadius: 6,
|
|
986
|
+
background: "#fff",
|
|
987
|
+
color: "#111",
|
|
988
|
+
}, children: "\u27F8" }), _jsx("button", { onClick: () => {
|
|
697
989
|
const img = selectedImage;
|
|
698
990
|
if (!img)
|
|
699
991
|
return;
|
|
@@ -702,7 +994,36 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
702
994
|
img.style.margin = "0 0 8px 8px";
|
|
703
995
|
scheduleImageOverlay();
|
|
704
996
|
handleInput();
|
|
705
|
-
}, title: "Float right",
|
|
997
|
+
}, title: "Float right", style: {
|
|
998
|
+
height: 28,
|
|
999
|
+
minWidth: 28,
|
|
1000
|
+
padding: "0 6px",
|
|
1001
|
+
border: "1px solid #e5e7eb",
|
|
1002
|
+
borderRadius: 6,
|
|
1003
|
+
background: "#fff",
|
|
1004
|
+
color: "#111",
|
|
1005
|
+
}, children: "\u27F9" })] })] })), table && (_jsx("button", { title: "Insert table", onClick: () => setShowTableDialog(true), style: {
|
|
1006
|
+
height: 32,
|
|
1007
|
+
padding: "0 10px",
|
|
1008
|
+
border: "1px solid #e5e7eb",
|
|
1009
|
+
borderRadius: 6,
|
|
1010
|
+
background: "#fff",
|
|
1011
|
+
color: "#111",
|
|
1012
|
+
}, children: "\u2795 Table" })), _jsx("button", { title: "Undo", onClick: () => exec("undo"), style: {
|
|
1013
|
+
height: 32,
|
|
1014
|
+
padding: "0 10px",
|
|
1015
|
+
border: "1px solid #e5e7eb",
|
|
1016
|
+
borderRadius: 6,
|
|
1017
|
+
background: "#fff",
|
|
1018
|
+
color: "#111",
|
|
1019
|
+
}, children: "\u238C Undo" }), _jsx("button", { title: "Redo", onClick: () => exec("redo"), style: {
|
|
1020
|
+
height: 32,
|
|
1021
|
+
padding: "0 10px",
|
|
1022
|
+
border: "1px solid #e5e7eb",
|
|
1023
|
+
borderRadius: 6,
|
|
1024
|
+
background: "#fff",
|
|
1025
|
+
color: "#111",
|
|
1026
|
+
}, children: "\u293E Redo" })] }), media && mediaManager && (_jsx(MediaManager, { open: showMediaManager, onClose: () => setShowMediaManager(false), adapter: mediaManager, onSelect: (item) => {
|
|
706
1027
|
if (item?.url)
|
|
707
1028
|
insertImageAtSelection(item.url);
|
|
708
1029
|
} })), table && showTableDialog && (_jsx("div", { style: {
|
|
@@ -748,7 +1069,72 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
748
1069
|
}, children: [_jsx("button", { onClick: () => setShowTableDialog(false), children: "Cancel" }), _jsx("button", { onClick: () => {
|
|
749
1070
|
insertTable();
|
|
750
1071
|
setShowTableDialog(false);
|
|
751
|
-
}, children: "Insert" })] })] }) })),
|
|
1072
|
+
}, children: "Insert" })] })] }) })), showColorPicker && (_jsx("div", { style: {
|
|
1073
|
+
position: "fixed",
|
|
1074
|
+
inset: 0,
|
|
1075
|
+
background: "rgba(0,0,0,0.35)",
|
|
1076
|
+
display: "flex",
|
|
1077
|
+
alignItems: "center",
|
|
1078
|
+
justifyContent: "center",
|
|
1079
|
+
zIndex: 50,
|
|
1080
|
+
}, onClick: () => setShowColorPicker(false), children: _jsxs("div", { style: {
|
|
1081
|
+
background: "#fff",
|
|
1082
|
+
padding: 16,
|
|
1083
|
+
borderRadius: 8,
|
|
1084
|
+
minWidth: 320,
|
|
1085
|
+
color: "#000",
|
|
1086
|
+
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 12 }, children: colorPickerType === 'text' ? 'Select Text Color' : 'Select Background Color' }), _jsx("div", { style: {
|
|
1087
|
+
display: "grid",
|
|
1088
|
+
gridTemplateColumns: "repeat(8, 1fr)",
|
|
1089
|
+
gap: 8,
|
|
1090
|
+
marginBottom: 12,
|
|
1091
|
+
}, children: [
|
|
1092
|
+
'#000000', '#434343', '#666666', '#999999',
|
|
1093
|
+
'#b7b7b7', '#cccccc', '#d9d9d9', '#efefef',
|
|
1094
|
+
'#f3f3f3', '#ffffff', '#980000', '#ff0000',
|
|
1095
|
+
'#ff9900', '#ffff00', '#00ff00', '#00ffff',
|
|
1096
|
+
'#4a86e8', '#0000ff', '#9900ff', '#ff00ff',
|
|
1097
|
+
'#e6b8af', '#f4cccc', '#fce5cd', '#fff2cc',
|
|
1098
|
+
'#d9ead3', '#d0e0e3', '#c9daf8', '#cfe2f3',
|
|
1099
|
+
'#d9d2e9', '#ead1dc', '#dd7e6b', '#ea9999',
|
|
1100
|
+
'#f9cb9c', '#ffe599', '#b6d7a8', '#a2c4c9',
|
|
1101
|
+
'#a4c2f4', '#9fc5e8', '#b4a7d6', '#d5a6bd',
|
|
1102
|
+
].map((color) => (_jsx("button", { onClick: () => {
|
|
1103
|
+
if (colorPickerType === 'text') {
|
|
1104
|
+
applyTextColor(color);
|
|
1105
|
+
}
|
|
1106
|
+
else {
|
|
1107
|
+
applyBackgroundColor(color);
|
|
1108
|
+
}
|
|
1109
|
+
setShowColorPicker(false);
|
|
1110
|
+
}, style: {
|
|
1111
|
+
width: 32,
|
|
1112
|
+
height: 32,
|
|
1113
|
+
border: color === '#ffffff' ? '1px solid #ddd' : 'none',
|
|
1114
|
+
borderRadius: 4,
|
|
1115
|
+
background: color,
|
|
1116
|
+
cursor: 'pointer',
|
|
1117
|
+
}, title: color }, color))) }), _jsxs("div", { style: { marginBottom: 12 }, children: [_jsx("label", { style: { display: 'block', marginBottom: 6, fontSize: 12 }, children: "Custom color:" }), _jsx("input", { type: "color", onChange: (e) => {
|
|
1118
|
+
if (colorPickerType === 'text') {
|
|
1119
|
+
applyTextColor(e.target.value);
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
applyBackgroundColor(e.target.value);
|
|
1123
|
+
}
|
|
1124
|
+
}, style: {
|
|
1125
|
+
width: '100%',
|
|
1126
|
+
height: 40,
|
|
1127
|
+
border: '1px solid #ddd',
|
|
1128
|
+
borderRadius: 6,
|
|
1129
|
+
cursor: 'pointer',
|
|
1130
|
+
} })] }), _jsx("div", { style: { display: 'flex', justifyContent: 'end' }, children: _jsx("button", { onClick: () => setShowColorPicker(false), style: {
|
|
1131
|
+
padding: '6px 16px',
|
|
1132
|
+
border: '1px solid #e5e7eb',
|
|
1133
|
+
borderRadius: 6,
|
|
1134
|
+
background: '#fff',
|
|
1135
|
+
color: '#111',
|
|
1136
|
+
cursor: 'pointer',
|
|
1137
|
+
}, children: "Close" }) })] }) })), formula && showFormulaDialog && (_jsx("div", { style: {
|
|
752
1138
|
position: "fixed",
|
|
753
1139
|
inset: 0,
|
|
754
1140
|
background: "rgba(0,0,0,0.35)",
|
|
@@ -862,10 +1248,65 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
862
1248
|
}
|
|
863
1249
|
}
|
|
864
1250
|
}, onDragOver: (e) => {
|
|
865
|
-
|
|
1251
|
+
// Allow dragging images within editor and file drops
|
|
1252
|
+
if (draggedImageRef.current ||
|
|
1253
|
+
e.dataTransfer?.types?.includes("Files")) {
|
|
866
1254
|
e.preventDefault();
|
|
867
1255
|
}
|
|
868
1256
|
}, onDrop: (e) => {
|
|
1257
|
+
// Move existing dragged image inside editor
|
|
1258
|
+
if (draggedImageRef.current) {
|
|
1259
|
+
e.preventDefault();
|
|
1260
|
+
const x = e.clientX;
|
|
1261
|
+
const y = e.clientY;
|
|
1262
|
+
let range = null;
|
|
1263
|
+
// @ts-ignore
|
|
1264
|
+
if (document.caretRangeFromPoint) {
|
|
1265
|
+
// @ts-ignore
|
|
1266
|
+
range = document.caretRangeFromPoint(x, y);
|
|
1267
|
+
}
|
|
1268
|
+
else if (document.caretPositionFromPoint) {
|
|
1269
|
+
const pos = document.caretPositionFromPoint(x, y);
|
|
1270
|
+
if (pos) {
|
|
1271
|
+
range = document.createRange();
|
|
1272
|
+
range.setStart(pos.offsetNode, pos.offset);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
const img = draggedImageRef.current;
|
|
1276
|
+
draggedImageRef.current = null;
|
|
1277
|
+
if (range &&
|
|
1278
|
+
img &&
|
|
1279
|
+
editableRef.current?.contains(range.commonAncestorContainer)) {
|
|
1280
|
+
// Avoid inserting inside the image itself
|
|
1281
|
+
if (range.startContainer === img || range.endContainer === img)
|
|
1282
|
+
return;
|
|
1283
|
+
// If dropping inside a link, insert right after the link element
|
|
1284
|
+
let container = range.commonAncestorContainer;
|
|
1285
|
+
let linkAncestor = null;
|
|
1286
|
+
let el = container;
|
|
1287
|
+
while (el && el !== editableRef.current) {
|
|
1288
|
+
if (el.tagName === "A") {
|
|
1289
|
+
linkAncestor = el;
|
|
1290
|
+
break;
|
|
1291
|
+
}
|
|
1292
|
+
el = el.parentElement;
|
|
1293
|
+
}
|
|
1294
|
+
if (linkAncestor) {
|
|
1295
|
+
linkAncestor.parentElement?.insertBefore(img, linkAncestor.nextSibling);
|
|
1296
|
+
}
|
|
1297
|
+
else {
|
|
1298
|
+
range.insertNode(img);
|
|
1299
|
+
}
|
|
1300
|
+
const r = document.createRange();
|
|
1301
|
+
r.setStartAfter(img);
|
|
1302
|
+
r.collapse(true);
|
|
1303
|
+
safeSelectRange(r);
|
|
1304
|
+
setSelectedImage(img);
|
|
1305
|
+
scheduleImageOverlay();
|
|
1306
|
+
handleInput();
|
|
1307
|
+
}
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
869
1310
|
if (media && e.dataTransfer?.files?.length) {
|
|
870
1311
|
e.preventDefault();
|
|
871
1312
|
// Try to move caret to drop point
|
|
@@ -901,6 +1342,30 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
901
1342
|
setSelectedImage(null);
|
|
902
1343
|
setImageOverlay(null);
|
|
903
1344
|
}
|
|
1345
|
+
}, onDragStart: (e) => {
|
|
1346
|
+
const t = e.target;
|
|
1347
|
+
if (t && t.tagName === "IMG") {
|
|
1348
|
+
draggedImageRef.current = t;
|
|
1349
|
+
try {
|
|
1350
|
+
e.dataTransfer?.setData("text/plain", "moving-image");
|
|
1351
|
+
e.dataTransfer.effectAllowed = "move";
|
|
1352
|
+
// Provide a subtle drag image
|
|
1353
|
+
const dt = e.dataTransfer;
|
|
1354
|
+
if (dt && typeof dt.setDragImage === "function") {
|
|
1355
|
+
const ghost = new Image();
|
|
1356
|
+
ghost.src = t.src;
|
|
1357
|
+
ghost.width = Math.min(120, t.width);
|
|
1358
|
+
ghost.height = Math.min(120, t.height);
|
|
1359
|
+
dt.setDragImage(ghost, 10, 10);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
catch { }
|
|
1363
|
+
}
|
|
1364
|
+
else {
|
|
1365
|
+
draggedImageRef.current = null;
|
|
1366
|
+
}
|
|
1367
|
+
}, onDragEnd: () => {
|
|
1368
|
+
draggedImageRef.current = null;
|
|
904
1369
|
}, style: {
|
|
905
1370
|
padding: 12,
|
|
906
1371
|
minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight,
|
|
@@ -1008,6 +1473,19 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1008
1473
|
window.addEventListener("mousemove", onMove);
|
|
1009
1474
|
window.addEventListener("mouseup", onUp);
|
|
1010
1475
|
}, onContextMenu: (e) => {
|
|
1476
|
+
const target = e.target;
|
|
1477
|
+
if (target && target.tagName === "IMG") {
|
|
1478
|
+
e.preventDefault();
|
|
1479
|
+
const vw = window.innerWidth;
|
|
1480
|
+
const vh = window.innerHeight;
|
|
1481
|
+
const menuW = 220;
|
|
1482
|
+
const menuH = 200;
|
|
1483
|
+
const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
|
|
1484
|
+
const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
|
|
1485
|
+
setImageMenu({ x, y, img: target });
|
|
1486
|
+
setTableMenu(null);
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1011
1489
|
const cell = getClosestCell(e.target);
|
|
1012
1490
|
if (cell) {
|
|
1013
1491
|
e.preventDefault();
|
|
@@ -1021,6 +1499,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1021
1499
|
}
|
|
1022
1500
|
else {
|
|
1023
1501
|
setTableMenu(null);
|
|
1502
|
+
setImageMenu(null);
|
|
1024
1503
|
}
|
|
1025
1504
|
} }), selectedImage && imageOverlay && (_jsxs("div", { style: {
|
|
1026
1505
|
position: "fixed",
|
|
@@ -1029,7 +1508,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1029
1508
|
width: imageOverlay.width,
|
|
1030
1509
|
height: imageOverlay.height,
|
|
1031
1510
|
pointerEvents: "none",
|
|
1032
|
-
zIndex:
|
|
1511
|
+
zIndex: 49,
|
|
1033
1512
|
}, children: [_jsx("div", { style: {
|
|
1034
1513
|
position: "absolute",
|
|
1035
1514
|
inset: 0,
|
|
@@ -1145,6 +1624,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1145
1624
|
width: 200,
|
|
1146
1625
|
maxHeight: 260,
|
|
1147
1626
|
overflowY: "auto",
|
|
1627
|
+
color: "#111",
|
|
1148
1628
|
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, fontSize: 11, margin: "2px 6px 6px" }, children: "Table" }), _jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsxs("button", { style: {
|
|
1149
1629
|
display: "flex",
|
|
1150
1630
|
alignItems: "center",
|
|
@@ -1276,5 +1756,143 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1276
1756
|
}, onClick: () => {
|
|
1277
1757
|
deleteTable(tableMenu.cell);
|
|
1278
1758
|
setTableMenu(null);
|
|
1279
|
-
}, children: [_jsx("span", { children: "\uD83D\uDDD1" }), _jsx("span", { children: "Delete table" })] })] })] }) }))
|
|
1759
|
+
}, children: [_jsx("span", { children: "\uD83D\uDDD1" }), _jsx("span", { children: "Delete table" })] })] })] }) })), imageMenu && (_jsx("div", { style: { position: "fixed", inset: 0, zIndex: 60 }, onClick: () => setImageMenu(null), onContextMenu: (e) => {
|
|
1760
|
+
e.preventDefault();
|
|
1761
|
+
const vw = window.innerWidth;
|
|
1762
|
+
const vh = window.innerHeight;
|
|
1763
|
+
const menuW = 220;
|
|
1764
|
+
const menuH = 220;
|
|
1765
|
+
const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
|
|
1766
|
+
const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
|
|
1767
|
+
setImageMenu({ x, y, img: imageMenu.img });
|
|
1768
|
+
}, children: _jsxs("div", { style: {
|
|
1769
|
+
position: "fixed",
|
|
1770
|
+
left: imageMenu.x,
|
|
1771
|
+
top: imageMenu.y,
|
|
1772
|
+
background: "#fff",
|
|
1773
|
+
border: "1px solid #ddd",
|
|
1774
|
+
borderRadius: 8,
|
|
1775
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.18)",
|
|
1776
|
+
padding: 8,
|
|
1777
|
+
width: 220,
|
|
1778
|
+
color: "#111",
|
|
1779
|
+
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, fontSize: 11, margin: "2px 6px 6px" }, children: "Image" }), _jsxs("div", { style: { display: "grid", gap: 6 }, children: [_jsxs("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [_jsx("span", { style: { width: 48, fontSize: 12 }, children: "Link" }), _jsx("input", { defaultValue: imageMenu.img.parentElement?.tagName === "A"
|
|
1780
|
+
? imageMenu.img.parentElement.href
|
|
1781
|
+
: "", placeholder: "https://", onChange: (e) => {
|
|
1782
|
+
const url = e.target.value.trim();
|
|
1783
|
+
const curParent = imageMenu.img.parentElement;
|
|
1784
|
+
if (url) {
|
|
1785
|
+
if (curParent && curParent.tagName === "A") {
|
|
1786
|
+
curParent.href = url;
|
|
1787
|
+
}
|
|
1788
|
+
else {
|
|
1789
|
+
const a = document.createElement("a");
|
|
1790
|
+
a.href = url;
|
|
1791
|
+
curParent?.insertBefore(a, imageMenu.img);
|
|
1792
|
+
a.appendChild(imageMenu.img);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
else if (curParent && curParent.tagName === "A") {
|
|
1796
|
+
// unwrap
|
|
1797
|
+
curParent.parentElement?.insertBefore(imageMenu.img, curParent);
|
|
1798
|
+
curParent.remove();
|
|
1799
|
+
}
|
|
1800
|
+
handleInput();
|
|
1801
|
+
}, style: {
|
|
1802
|
+
flex: 1,
|
|
1803
|
+
padding: "4px 6px",
|
|
1804
|
+
border: "1px solid #eee",
|
|
1805
|
+
borderRadius: 4,
|
|
1806
|
+
color: "#111",
|
|
1807
|
+
background: "#fff",
|
|
1808
|
+
} })] }), _jsxs("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [_jsx("span", { style: { width: 48, fontSize: 12 }, children: "Target" }), _jsxs("select", { defaultValue: imageMenu.img.parentElement?.tagName === "A"
|
|
1809
|
+
? imageMenu.img.parentElement
|
|
1810
|
+
.target || "_self"
|
|
1811
|
+
: "_self", onChange: (e) => {
|
|
1812
|
+
const curParent = imageMenu.img.parentElement;
|
|
1813
|
+
if (curParent && curParent.tagName === "A") {
|
|
1814
|
+
curParent.target = e.target.value;
|
|
1815
|
+
handleInput();
|
|
1816
|
+
}
|
|
1817
|
+
}, style: {
|
|
1818
|
+
flex: 1,
|
|
1819
|
+
height: 28,
|
|
1820
|
+
padding: "0 6px",
|
|
1821
|
+
border: "1px solid #eee",
|
|
1822
|
+
borderRadius: 4,
|
|
1823
|
+
background: "#fff",
|
|
1824
|
+
color: "#111",
|
|
1825
|
+
}, children: [_jsx("option", { value: "_self", children: "Same tab" }), _jsx("option", { value: "_blank", children: "New tab" })] })] }), _jsxs("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [_jsx("span", { style: { width: 48, fontSize: 12 }, children: "Alt" }), _jsx("input", { defaultValue: imageMenu.img.alt || "", onChange: (e) => {
|
|
1826
|
+
imageMenu.img.alt = e.target.value;
|
|
1827
|
+
handleInput();
|
|
1828
|
+
}, style: {
|
|
1829
|
+
flex: 1,
|
|
1830
|
+
padding: "4px 6px",
|
|
1831
|
+
border: "1px solid #eee",
|
|
1832
|
+
borderRadius: 4,
|
|
1833
|
+
color: "#111",
|
|
1834
|
+
background: "#fff",
|
|
1835
|
+
} })] }), _jsxs("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [_jsx("span", { style: { width: 48, fontSize: 12 }, children: "Width" }), _jsx("input", { type: "number", min: 40, max: 2000, defaultValue: Math.round(imageMenu.img.getBoundingClientRect().width), onChange: (e) => {
|
|
1836
|
+
const v = Math.max(40, Math.min(2000, Number(e.target.value) || 0));
|
|
1837
|
+
imageMenu.img.style.width = v + "px";
|
|
1838
|
+
imageMenu.img.style.height = "auto";
|
|
1839
|
+
scheduleImageOverlay();
|
|
1840
|
+
}, style: {
|
|
1841
|
+
width: 90,
|
|
1842
|
+
padding: "4px 6px",
|
|
1843
|
+
border: "1px solid #eee",
|
|
1844
|
+
borderRadius: 4,
|
|
1845
|
+
color: "#111",
|
|
1846
|
+
background: "#fff",
|
|
1847
|
+
} }), _jsx("span", { style: { fontSize: 12 }, children: "px" })] }), _jsxs("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [_jsx("span", { style: { width: 48, fontSize: 12 }, children: "Radius" }), _jsx("input", { type: "number", min: 0, max: 200, defaultValue: parseInt((imageMenu.img.style.borderRadius || "0").toString()) || 0, onChange: (e) => {
|
|
1848
|
+
const v = Math.max(0, Math.min(200, Number(e.target.value) || 0));
|
|
1849
|
+
imageMenu.img.style.borderRadius = v + "px";
|
|
1850
|
+
handleInput();
|
|
1851
|
+
}, style: {
|
|
1852
|
+
width: 90,
|
|
1853
|
+
padding: "4px 6px",
|
|
1854
|
+
border: "1px solid #eee",
|
|
1855
|
+
borderRadius: 4,
|
|
1856
|
+
color: "#111",
|
|
1857
|
+
background: "#fff",
|
|
1858
|
+
} }), _jsx("span", { style: { fontSize: 12 }, children: "px" })] }), _jsxs("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [_jsx("span", { style: { width: 48, fontSize: 12 }, children: "Align" }), _jsx("button", { onClick: () => {
|
|
1859
|
+
const img = imageMenu.img;
|
|
1860
|
+
img.style.display = "block";
|
|
1861
|
+
img.style.margin = "0 auto";
|
|
1862
|
+
img.style.float = "none";
|
|
1863
|
+
scheduleImageOverlay();
|
|
1864
|
+
handleInput();
|
|
1865
|
+
}, children: "\u29BF" }), _jsx("button", { onClick: () => {
|
|
1866
|
+
const img = imageMenu.img;
|
|
1867
|
+
img.style.display = "inline";
|
|
1868
|
+
img.style.float = "left";
|
|
1869
|
+
img.style.margin = "0 8px 8px 0";
|
|
1870
|
+
scheduleImageOverlay();
|
|
1871
|
+
handleInput();
|
|
1872
|
+
}, children: "\u27F8" }), _jsx("button", { onClick: () => {
|
|
1873
|
+
const img = imageMenu.img;
|
|
1874
|
+
img.style.display = "inline";
|
|
1875
|
+
img.style.float = "right";
|
|
1876
|
+
img.style.margin = "0 0 8px 8px";
|
|
1877
|
+
scheduleImageOverlay();
|
|
1878
|
+
handleInput();
|
|
1879
|
+
}, children: "\u27F9" })] }), _jsxs("div", { style: { display: "flex", gap: 6 }, children: [_jsx("button", { onClick: () => {
|
|
1880
|
+
replaceTargetRef.current = imageMenu.img;
|
|
1881
|
+
fileInputRef.current?.click();
|
|
1882
|
+
}, children: "Replace\u2026" }), _jsx("button", { onClick: () => {
|
|
1883
|
+
const img = imageMenu.img;
|
|
1884
|
+
img.style.width = "";
|
|
1885
|
+
img.style.height = "auto";
|
|
1886
|
+
img.style.borderRadius = "";
|
|
1887
|
+
img.style.margin = "";
|
|
1888
|
+
img.style.float = "none";
|
|
1889
|
+
scheduleImageOverlay();
|
|
1890
|
+
handleInput();
|
|
1891
|
+
}, children: "Reset" })] }), _jsxs("div", { style: { display: "flex", gap: 6 }, children: [_jsx("button", { style: { color: "#b00020" }, onClick: () => {
|
|
1892
|
+
imageMenu.img.remove();
|
|
1893
|
+
setImageMenu(null);
|
|
1894
|
+
setSelectedImage(null);
|
|
1895
|
+
setImageOverlay(null);
|
|
1896
|
+
handleInput();
|
|
1897
|
+
}, children: "Delete" }), _jsx("button", { onClick: () => setImageMenu(null), children: "Close" })] })] })] }) }))] }));
|
|
1280
1898
|
}
|