smartrte-react 0.2.1 → 0.2.2
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 +19 -1
- package/dist/components/ClassicEditor.js +785 -94
- package/dist/components/MediaManager.d.ts +7 -0
- package/dist/components/MediaManager.js +65 -14
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +17 -0
- package/package.json +13 -11
|
@@ -26,6 +26,24 @@ type ClassicEditorProps = {
|
|
|
26
26
|
* Example: "Arial, sans-serif"
|
|
27
27
|
*/
|
|
28
28
|
defaultFont?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Preserve font-family styles from pasted/imported content.
|
|
31
|
+
* Defaults to false so host applications can keep a single app font while
|
|
32
|
+
* still preserving bold, italic, headings, lists, tables, and colors.
|
|
33
|
+
*/
|
|
34
|
+
preserveFontFamily?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Preserve foreground/background colors from pasted/imported content.
|
|
37
|
+
* Defaults to false so dark-mode editors remain readable when content is
|
|
38
|
+
* copied from sources such as Google Docs with hardcoded black-on-white styles.
|
|
39
|
+
*/
|
|
40
|
+
preserveColors?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Preserve visual styling from imported DOCX files except font-family.
|
|
43
|
+
* This keeps Word-authored colors, spacing, borders, and table fills while
|
|
44
|
+
* still allowing the host app to control the editor font.
|
|
45
|
+
*/
|
|
46
|
+
preserveDocxStyles?: boolean;
|
|
29
47
|
/**
|
|
30
48
|
* Theme mode for the editor.
|
|
31
49
|
* - "light" (default): Uses the built-in light theme.
|
|
@@ -39,5 +57,5 @@ type ClassicEditorProps = {
|
|
|
39
57
|
*/
|
|
40
58
|
className?: string;
|
|
41
59
|
};
|
|
42
|
-
export declare function ClassicEditor({ value, onChange, placeholder, minHeight, maxHeight, readOnly, table, media, formula, mediaManager, fonts, defaultFont, theme, className, }: ClassicEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
60
|
+
export declare function ClassicEditor({ value, onChange, placeholder, minHeight, maxHeight, readOnly, table, media, formula, mediaManager, fonts, defaultFont, preserveFontFamily, preserveColors, preserveDocxStyles, theme, className, }: ClassicEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
43
61
|
export {};
|
|
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
3
3
|
import { MediaManager } from "./MediaManager";
|
|
4
4
|
import * as pdfjsLib from 'pdfjs-dist';
|
|
5
5
|
import mammoth from 'mammoth';
|
|
6
|
+
import JSZip from 'jszip';
|
|
6
7
|
import { ensureStyleSheet } from '../theme';
|
|
7
8
|
// Initialize PDF.js worker
|
|
8
9
|
if (typeof window !== 'undefined') {
|
|
@@ -16,7 +17,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
16
17
|
{ name: "Times New Roman", value: "'Times New Roman', Times, serif" },
|
|
17
18
|
{ name: "Verdana", value: "Verdana, Geneva, sans-serif" },
|
|
18
19
|
{ name: "Courier New", value: "'Courier New', Courier, monospace" },
|
|
19
|
-
], defaultFont, theme = "light", className, }) {
|
|
20
|
+
], defaultFont, preserveFontFamily = false, preserveColors = false, preserveDocxStyles = true, theme = "light", className, }) {
|
|
20
21
|
ensureStyleSheet();
|
|
21
22
|
const editableRef = useRef(null);
|
|
22
23
|
const lastEmittedRef = useRef("");
|
|
@@ -24,6 +25,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
24
25
|
const fileInputRef = useRef(null);
|
|
25
26
|
const pdfInputRef = useRef(null);
|
|
26
27
|
const docxInputRef = useRef(null);
|
|
28
|
+
const htmlInputRef = useRef(null);
|
|
29
|
+
const mdInputRef = useRef(null);
|
|
27
30
|
const [loadingPdf, setLoadingPdf] = useState(false);
|
|
28
31
|
const [loadingDocx, setLoadingDocx] = useState(false);
|
|
29
32
|
// State for import confirmation
|
|
@@ -45,6 +48,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
45
48
|
const [imageMenu, setImageMenu] = useState(null);
|
|
46
49
|
const [showMediaManager, setShowMediaManager] = useState(false);
|
|
47
50
|
const [showColorPicker, setShowColorPicker] = useState(false);
|
|
51
|
+
const [showSpecialChars, setShowSpecialChars] = useState(false);
|
|
48
52
|
const [colorPickerType, setColorPickerType] = useState('text');
|
|
49
53
|
const savedRangeRef = useRef(null);
|
|
50
54
|
const [currentFontSize, setCurrentFontSize] = useState("");
|
|
@@ -93,27 +97,91 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
93
97
|
const exec = (command, valueArg) => {
|
|
94
98
|
try {
|
|
95
99
|
document.execCommand(command, false, valueArg);
|
|
96
|
-
|
|
97
|
-
const el = editableRef.current;
|
|
98
|
-
if (el && onChange) {
|
|
99
|
-
const html = el.innerHTML;
|
|
100
|
-
if (html !== lastEmittedRef.current) {
|
|
101
|
-
lastEmittedRef.current = html;
|
|
102
|
-
onChange(html);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
100
|
+
emitChange();
|
|
105
101
|
}
|
|
106
102
|
catch { }
|
|
107
103
|
};
|
|
108
104
|
const applyFormatBlock = (blockName) => {
|
|
109
105
|
exec("formatBlock", blockName);
|
|
110
106
|
};
|
|
107
|
+
const emitChange = () => {
|
|
108
|
+
const el = editableRef.current;
|
|
109
|
+
if (!el || !onChange)
|
|
110
|
+
return;
|
|
111
|
+
const html = el.innerHTML;
|
|
112
|
+
if (html !== lastEmittedRef.current) {
|
|
113
|
+
lastEmittedRef.current = html;
|
|
114
|
+
onChange(html);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
111
117
|
const insertLink = () => {
|
|
112
118
|
const url = window.prompt("Enter URL", "https://");
|
|
113
119
|
if (!url)
|
|
114
120
|
return;
|
|
115
121
|
exec("createLink", url);
|
|
116
122
|
};
|
|
123
|
+
const getSelectionRangeInEditor = () => {
|
|
124
|
+
const editor = editableRef.current;
|
|
125
|
+
if (!editor)
|
|
126
|
+
return null;
|
|
127
|
+
editor.focus();
|
|
128
|
+
const sel = window.getSelection();
|
|
129
|
+
if (sel && sel.rangeCount > 0) {
|
|
130
|
+
const range = sel.getRangeAt(0);
|
|
131
|
+
if (editor.contains(range.commonAncestorContainer))
|
|
132
|
+
return range;
|
|
133
|
+
}
|
|
134
|
+
if (savedRangeRef.current && editor.contains(savedRangeRef.current.commonAncestorContainer)) {
|
|
135
|
+
return savedRangeRef.current.cloneRange();
|
|
136
|
+
}
|
|
137
|
+
const range = document.createRange();
|
|
138
|
+
range.selectNodeContents(editor);
|
|
139
|
+
range.collapse(false);
|
|
140
|
+
return range;
|
|
141
|
+
};
|
|
142
|
+
const insertTextAtSelection = (text) => {
|
|
143
|
+
if (!text)
|
|
144
|
+
return;
|
|
145
|
+
try {
|
|
146
|
+
const range = getSelectionRangeInEditor();
|
|
147
|
+
if (!range)
|
|
148
|
+
return;
|
|
149
|
+
range.deleteContents();
|
|
150
|
+
const node = document.createTextNode(text);
|
|
151
|
+
range.insertNode(node);
|
|
152
|
+
const nextRange = document.createRange();
|
|
153
|
+
nextRange.setStartAfter(node);
|
|
154
|
+
nextRange.collapse(true);
|
|
155
|
+
safeSelectRange(nextRange);
|
|
156
|
+
handleInput();
|
|
157
|
+
}
|
|
158
|
+
catch { }
|
|
159
|
+
};
|
|
160
|
+
const toggleBlockquote = () => {
|
|
161
|
+
try {
|
|
162
|
+
const range = getSelectionRangeInEditor();
|
|
163
|
+
if (!range)
|
|
164
|
+
return;
|
|
165
|
+
let node = range.commonAncestorContainer;
|
|
166
|
+
if (node.nodeType === Node.TEXT_NODE)
|
|
167
|
+
node = node.parentElement;
|
|
168
|
+
const element = node;
|
|
169
|
+
const quote = element?.closest?.('blockquote');
|
|
170
|
+
if (quote && editableRef.current?.contains(quote)) {
|
|
171
|
+
const replacement = document.createElement('p');
|
|
172
|
+
replacement.innerHTML = quote.innerHTML || '<br>';
|
|
173
|
+
quote.parentElement?.replaceChild(replacement, quote);
|
|
174
|
+
const nextRange = document.createRange();
|
|
175
|
+
nextRange.selectNodeContents(replacement);
|
|
176
|
+
nextRange.collapse(false);
|
|
177
|
+
safeSelectRange(nextRange);
|
|
178
|
+
handleInput();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
exec("formatBlock", "<blockquote>");
|
|
182
|
+
}
|
|
183
|
+
catch { }
|
|
184
|
+
};
|
|
117
185
|
const applyFontSize = (size) => {
|
|
118
186
|
try {
|
|
119
187
|
// Update current font size state
|
|
@@ -395,11 +463,12 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
395
463
|
window.removeEventListener('touchend', onTouchEnd);
|
|
396
464
|
};
|
|
397
465
|
}, [table]);
|
|
398
|
-
const insertImageAtSelection = (
|
|
466
|
+
const insertImageAtSelection = (srcOrItem) => {
|
|
399
467
|
try {
|
|
400
468
|
const host = editableRef.current;
|
|
401
469
|
if (!host)
|
|
402
470
|
return;
|
|
471
|
+
const src = typeof srcOrItem === "string" ? srcOrItem : srcOrItem.url;
|
|
403
472
|
host.focus();
|
|
404
473
|
let sel = window.getSelection();
|
|
405
474
|
let range = sel && sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
|
|
@@ -415,7 +484,21 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
415
484
|
img.style.maxWidth = "100%";
|
|
416
485
|
img.style.height = "auto";
|
|
417
486
|
img.style.display = "inline-block";
|
|
418
|
-
img.alt = "image";
|
|
487
|
+
img.alt = typeof srcOrItem === "string" ? "image" : (srcOrItem.alt || srcOrItem.title || "image");
|
|
488
|
+
if (typeof srcOrItem !== "string") {
|
|
489
|
+
if (srcOrItem.title)
|
|
490
|
+
img.title = srcOrItem.title;
|
|
491
|
+
if (srcOrItem.license?.author)
|
|
492
|
+
img.dataset.licenseAuthor = srcOrItem.license.author;
|
|
493
|
+
if (srcOrItem.license?.licenseType)
|
|
494
|
+
img.dataset.licenseType = srcOrItem.license.licenseType;
|
|
495
|
+
if (srcOrItem.license?.licenseText)
|
|
496
|
+
img.dataset.licenseText = srcOrItem.license.licenseText;
|
|
497
|
+
if (srcOrItem.license?.sourceUrl)
|
|
498
|
+
img.dataset.licenseUrl = srcOrItem.license.sourceUrl;
|
|
499
|
+
if (srcOrItem.license?.workName)
|
|
500
|
+
img.dataset.workName = srcOrItem.license.workName;
|
|
501
|
+
}
|
|
419
502
|
if (range) {
|
|
420
503
|
range.insertNode(img);
|
|
421
504
|
}
|
|
@@ -806,30 +889,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
806
889
|
closeTable();
|
|
807
890
|
fullHtml += html;
|
|
808
891
|
}
|
|
809
|
-
|
|
810
|
-
if (el) {
|
|
811
|
-
el.focus();
|
|
812
|
-
if (mode === 'replace') {
|
|
813
|
-
// Select all and replace
|
|
814
|
-
const range = document.createRange();
|
|
815
|
-
range.selectNodeContents(el);
|
|
816
|
-
const sel = window.getSelection();
|
|
817
|
-
sel?.removeAllRanges();
|
|
818
|
-
sel?.addRange(range);
|
|
819
|
-
exec("delete"); // Clear content safely
|
|
820
|
-
exec("insertHTML", fullHtml);
|
|
821
|
-
}
|
|
822
|
-
else {
|
|
823
|
-
// Append
|
|
824
|
-
const range = document.createRange();
|
|
825
|
-
range.selectNodeContents(el);
|
|
826
|
-
range.collapse(false); // End
|
|
827
|
-
const sel = window.getSelection();
|
|
828
|
-
sel?.removeAllRanges();
|
|
829
|
-
sel?.addRange(range);
|
|
830
|
-
exec("insertHTML", "<br>" + fullHtml);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
892
|
+
insertImportedHtml(fullHtml, mode);
|
|
833
893
|
}
|
|
834
894
|
catch (error) {
|
|
835
895
|
console.error('Error reading PDF:', error);
|
|
@@ -859,55 +919,14 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
859
919
|
try {
|
|
860
920
|
setLoadingDocx(true);
|
|
861
921
|
const arrayBuffer = await file.arrayBuffer();
|
|
862
|
-
const
|
|
863
|
-
|
|
922
|
+
const html = preserveDocxStyles
|
|
923
|
+
? await convertDocxToStyledHtml(arrayBuffer)
|
|
924
|
+
: await convertDocxWithMammoth(arrayBuffer);
|
|
864
925
|
if (html) {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
const tables = temp.querySelectorAll('table');
|
|
869
|
-
tables.forEach(tbl => {
|
|
870
|
-
tbl.style.borderCollapse = 'collapse';
|
|
871
|
-
tbl.style.minWidth = '100%';
|
|
872
|
-
// Browser parser auto-adds tbody, but we verify styles
|
|
873
|
-
const cells = tbl.querySelectorAll('td, th');
|
|
874
|
-
cells.forEach(cell => {
|
|
875
|
-
cell.style.border = '1px solid #000';
|
|
876
|
-
cell.style.padding = '8px';
|
|
877
|
-
cell.style.verticalAlign = 'top';
|
|
878
|
-
});
|
|
926
|
+
insertImportedHtml(`<div class="srte-preserve-colors">${html}</div>`, mode, {
|
|
927
|
+
preserveColors: preserveDocxStyles,
|
|
928
|
+
preserveDocumentLayout: preserveDocxStyles,
|
|
879
929
|
});
|
|
880
|
-
html = temp.innerHTML;
|
|
881
|
-
const el = editableRef.current;
|
|
882
|
-
if (el) {
|
|
883
|
-
el.focus();
|
|
884
|
-
if (mode === 'replace') {
|
|
885
|
-
const range = document.createRange();
|
|
886
|
-
range.selectNodeContents(el);
|
|
887
|
-
const sel = window.getSelection();
|
|
888
|
-
sel?.removeAllRanges();
|
|
889
|
-
sel?.addRange(range);
|
|
890
|
-
exec("delete");
|
|
891
|
-
exec("insertHTML", html);
|
|
892
|
-
}
|
|
893
|
-
else {
|
|
894
|
-
const range = document.createRange();
|
|
895
|
-
range.selectNodeContents(el);
|
|
896
|
-
range.collapse(false);
|
|
897
|
-
const sel = window.getSelection();
|
|
898
|
-
sel?.removeAllRanges();
|
|
899
|
-
sel?.addRange(range);
|
|
900
|
-
exec("insertHTML", "<br>" + html);
|
|
901
|
-
}
|
|
902
|
-
// Initialize handlers for the new content
|
|
903
|
-
// We use setTimeout to let the DOM settle after execCommand
|
|
904
|
-
setTimeout(() => {
|
|
905
|
-
ensureTableWrappers(el);
|
|
906
|
-
addTableResizeHandles();
|
|
907
|
-
fixNegativeMargins(el);
|
|
908
|
-
handleInput();
|
|
909
|
-
}, 10);
|
|
910
|
-
}
|
|
911
930
|
}
|
|
912
931
|
}
|
|
913
932
|
catch (error) {
|
|
@@ -917,6 +936,341 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
917
936
|
setLoadingDocx(false);
|
|
918
937
|
}
|
|
919
938
|
};
|
|
939
|
+
const convertDocxWithMammoth = async (arrayBuffer) => {
|
|
940
|
+
const result = await mammoth.convertToHtml({ arrayBuffer });
|
|
941
|
+
const temp = document.createElement('div');
|
|
942
|
+
temp.innerHTML = result.value;
|
|
943
|
+
enhanceImportedTables(temp);
|
|
944
|
+
return temp.innerHTML;
|
|
945
|
+
};
|
|
946
|
+
const convertDocxToStyledHtml = async (arrayBuffer) => {
|
|
947
|
+
try {
|
|
948
|
+
const zip = await JSZip.loadAsync(arrayBuffer);
|
|
949
|
+
const documentXml = await zip.file('word/document.xml')?.async('text');
|
|
950
|
+
if (!documentXml)
|
|
951
|
+
return convertDocxWithMammoth(arrayBuffer);
|
|
952
|
+
const parser = new DOMParser();
|
|
953
|
+
const doc = parser.parseFromString(documentXml, 'application/xml');
|
|
954
|
+
if (doc.querySelector('parsererror'))
|
|
955
|
+
return convertDocxWithMammoth(arrayBuffer);
|
|
956
|
+
const body = Array.from(doc.getElementsByTagName('*')).find((node) => node.localName === 'body');
|
|
957
|
+
if (!body)
|
|
958
|
+
return convertDocxWithMammoth(arrayBuffer);
|
|
959
|
+
const html = directChildren(body)
|
|
960
|
+
.filter((node) => node.localName !== 'sectPr')
|
|
961
|
+
.map((node) => {
|
|
962
|
+
if (node.localName === 'p')
|
|
963
|
+
return convertDocxParagraph(node);
|
|
964
|
+
if (node.localName === 'tbl')
|
|
965
|
+
return convertDocxTable(node);
|
|
966
|
+
return '';
|
|
967
|
+
})
|
|
968
|
+
.join('');
|
|
969
|
+
if (!html.trim())
|
|
970
|
+
return convertDocxWithMammoth(arrayBuffer);
|
|
971
|
+
const temp = document.createElement('div');
|
|
972
|
+
temp.innerHTML = html;
|
|
973
|
+
enhanceImportedTables(temp);
|
|
974
|
+
return temp.innerHTML;
|
|
975
|
+
}
|
|
976
|
+
catch (error) {
|
|
977
|
+
console.warn('Falling back to Mammoth DOCX import:', error);
|
|
978
|
+
return convertDocxWithMammoth(arrayBuffer);
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
const directChildren = (node) => Array.from(node.children);
|
|
982
|
+
const firstChildByName = (node, localName) => node
|
|
983
|
+
? directChildren(node).find((child) => child.localName === localName)
|
|
984
|
+
: undefined;
|
|
985
|
+
const childrenByName = (node, localName) => node
|
|
986
|
+
? directChildren(node).filter((child) => child.localName === localName)
|
|
987
|
+
: [];
|
|
988
|
+
const docxAttr = (node, name) => {
|
|
989
|
+
if (!node)
|
|
990
|
+
return '';
|
|
991
|
+
return (node.getAttribute(`w:${name}`) ||
|
|
992
|
+
node.getAttribute(name) ||
|
|
993
|
+
node.getAttributeNS('http://schemas.openxmlformats.org/wordprocessingml/2006/main', name) ||
|
|
994
|
+
'');
|
|
995
|
+
};
|
|
996
|
+
const docxHexColor = (value) => {
|
|
997
|
+
if (!value || value.toLowerCase() === 'auto')
|
|
998
|
+
return '';
|
|
999
|
+
const normalized = value.replace(/[^0-9a-f]/gi, '');
|
|
1000
|
+
return normalized.length === 6 ? `#${normalized}` : '';
|
|
1001
|
+
};
|
|
1002
|
+
const twipsToPt = (value) => {
|
|
1003
|
+
const n = Number(value);
|
|
1004
|
+
return Number.isFinite(n) ? `${Math.max(n / 20, 0)}pt` : '';
|
|
1005
|
+
};
|
|
1006
|
+
const halfPointsToPt = (value) => {
|
|
1007
|
+
const n = Number(value);
|
|
1008
|
+
return Number.isFinite(n) ? `${Math.max(n / 2, 1)}pt` : '';
|
|
1009
|
+
};
|
|
1010
|
+
const cssRules = (rules) => rules
|
|
1011
|
+
.filter(([, value]) => Boolean(value))
|
|
1012
|
+
.map(([name, value]) => `${name}: ${value}`)
|
|
1013
|
+
.join('; ');
|
|
1014
|
+
const styleAttr = (style) => (style ? ` style="${escapeHtml(style)}"` : '');
|
|
1015
|
+
const convertDocxParagraphStyle = (paragraph) => {
|
|
1016
|
+
const pPr = firstChildByName(paragraph, 'pPr');
|
|
1017
|
+
if (!pPr)
|
|
1018
|
+
return '';
|
|
1019
|
+
const spacing = firstChildByName(pPr, 'spacing');
|
|
1020
|
+
const jc = firstChildByName(pPr, 'jc');
|
|
1021
|
+
const indent = firstChildByName(pPr, 'ind');
|
|
1022
|
+
const borderBottom = firstChildByName(firstChildByName(pPr, 'pBdr'), 'bottom');
|
|
1023
|
+
const line = docxAttr(spacing, 'line');
|
|
1024
|
+
const lineRule = docxAttr(spacing, 'lineRule');
|
|
1025
|
+
return cssRules([
|
|
1026
|
+
['text-align', docxAttr(jc, 'val')],
|
|
1027
|
+
['margin-top', twipsToPt(docxAttr(spacing, 'before'))],
|
|
1028
|
+
['margin-bottom', twipsToPt(docxAttr(spacing, 'after'))],
|
|
1029
|
+
['margin-left', twipsToPt(docxAttr(indent, 'left'))],
|
|
1030
|
+
['text-indent', twipsToPt(docxAttr(indent, 'firstLine'))],
|
|
1031
|
+
['line-height', line && lineRule === 'auto' ? `${Number(line) / 240}` : ''],
|
|
1032
|
+
['border-bottom', docxBorderCss(borderBottom)],
|
|
1033
|
+
]);
|
|
1034
|
+
};
|
|
1035
|
+
const convertDocxRunStyle = (run) => {
|
|
1036
|
+
const rPr = firstChildByName(run, 'rPr');
|
|
1037
|
+
if (!rPr)
|
|
1038
|
+
return '';
|
|
1039
|
+
const color = docxHexColor(docxAttr(firstChildByName(rPr, 'color'), 'val'));
|
|
1040
|
+
const highlight = docxHexColor(docxAttr(firstChildByName(rPr, 'highlight'), 'val'));
|
|
1041
|
+
const shade = docxHexColor(docxAttr(firstChildByName(rPr, 'shd'), 'fill'));
|
|
1042
|
+
const size = halfPointsToPt(docxAttr(firstChildByName(rPr, 'sz'), 'val'));
|
|
1043
|
+
const underline = firstChildByName(rPr, 'u');
|
|
1044
|
+
return cssRules([
|
|
1045
|
+
['font-weight', firstChildByName(rPr, 'b') ? '700' : ''],
|
|
1046
|
+
['font-style', firstChildByName(rPr, 'i') ? 'italic' : ''],
|
|
1047
|
+
['text-decoration', underline ? 'underline' : ''],
|
|
1048
|
+
['color', color],
|
|
1049
|
+
['background-color', highlight || shade],
|
|
1050
|
+
['font-size', size],
|
|
1051
|
+
]);
|
|
1052
|
+
};
|
|
1053
|
+
const convertDocxRun = (run) => {
|
|
1054
|
+
const rPr = firstChildByName(run, 'rPr');
|
|
1055
|
+
const vertAlign = docxAttr(firstChildByName(rPr, 'vertAlign'), 'val');
|
|
1056
|
+
const style = convertDocxRunStyle(run);
|
|
1057
|
+
const content = directChildren(run)
|
|
1058
|
+
.map((child) => {
|
|
1059
|
+
if (child.localName === 't')
|
|
1060
|
+
return escapeHtml(child.textContent || '');
|
|
1061
|
+
if (child.localName === 'tab')
|
|
1062
|
+
return ' ';
|
|
1063
|
+
if (child.localName === 'br') {
|
|
1064
|
+
return docxAttr(child, 'type') === 'page'
|
|
1065
|
+
? '<hr class="srte-docx-page-break">'
|
|
1066
|
+
: '<br>';
|
|
1067
|
+
}
|
|
1068
|
+
return '';
|
|
1069
|
+
})
|
|
1070
|
+
.join('');
|
|
1071
|
+
if (!content)
|
|
1072
|
+
return '';
|
|
1073
|
+
const tag = vertAlign === 'superscript' ? 'sup' : vertAlign === 'subscript' ? 'sub' : 'span';
|
|
1074
|
+
return `<${tag}${styleAttr(style)}>${content}</${tag}>`;
|
|
1075
|
+
};
|
|
1076
|
+
const convertDocxParagraph = (paragraph) => {
|
|
1077
|
+
const style = convertDocxParagraphStyle(paragraph);
|
|
1078
|
+
const content = childrenByName(paragraph, 'r').map(convertDocxRun).join('');
|
|
1079
|
+
return `<p${styleAttr(style)}>${content || '<br>'}</p>`;
|
|
1080
|
+
};
|
|
1081
|
+
const docxBorderCss = (border) => {
|
|
1082
|
+
if (!border)
|
|
1083
|
+
return '';
|
|
1084
|
+
const val = docxAttr(border, 'val');
|
|
1085
|
+
if (!val || val === 'nil' || val === 'none')
|
|
1086
|
+
return '';
|
|
1087
|
+
const size = Number(docxAttr(border, 'sz')) || 4;
|
|
1088
|
+
const width = Math.max(size / 8, 0.5);
|
|
1089
|
+
const color = docxHexColor(docxAttr(border, 'color')) || '#d1d5db';
|
|
1090
|
+
return `${width}px solid ${color}`;
|
|
1091
|
+
};
|
|
1092
|
+
const convertDocxCellStyle = (cell) => {
|
|
1093
|
+
const tcPr = firstChildByName(cell, 'tcPr');
|
|
1094
|
+
const width = twipsToPt(docxAttr(firstChildByName(tcPr, 'tcW'), 'w'));
|
|
1095
|
+
const shade = docxHexColor(docxAttr(firstChildByName(tcPr, 'shd'), 'fill'));
|
|
1096
|
+
const borders = firstChildByName(tcPr, 'tcBorders');
|
|
1097
|
+
const top = docxBorderCss(firstChildByName(borders, 'top'));
|
|
1098
|
+
const right = docxBorderCss(firstChildByName(borders, 'right'));
|
|
1099
|
+
const bottom = docxBorderCss(firstChildByName(borders, 'bottom'));
|
|
1100
|
+
const left = docxBorderCss(firstChildByName(borders, 'left'));
|
|
1101
|
+
return cssRules([
|
|
1102
|
+
['width', width],
|
|
1103
|
+
['background-color', shade],
|
|
1104
|
+
['border-top', top],
|
|
1105
|
+
['border-right', right],
|
|
1106
|
+
['border-bottom', bottom],
|
|
1107
|
+
['border-left', left],
|
|
1108
|
+
['padding', '8px'],
|
|
1109
|
+
['vertical-align', 'top'],
|
|
1110
|
+
]);
|
|
1111
|
+
};
|
|
1112
|
+
const convertDocxTable = (table) => {
|
|
1113
|
+
const rows = childrenByName(table, 'tr')
|
|
1114
|
+
.map((row) => {
|
|
1115
|
+
const cells = childrenByName(row, 'tc')
|
|
1116
|
+
.map((cell) => {
|
|
1117
|
+
const content = directChildren(cell)
|
|
1118
|
+
.filter((child) => child.localName === 'p' || child.localName === 'tbl')
|
|
1119
|
+
.map((child) => child.localName === 'p' ? convertDocxParagraph(child) : convertDocxTable(child))
|
|
1120
|
+
.join('');
|
|
1121
|
+
return `<td${styleAttr(convertDocxCellStyle(cell))}>${content || '<p><br></p>'}</td>`;
|
|
1122
|
+
})
|
|
1123
|
+
.join('');
|
|
1124
|
+
return `<tr>${cells}</tr>`;
|
|
1125
|
+
})
|
|
1126
|
+
.join('');
|
|
1127
|
+
return `<table style="border-collapse: collapse; width: 100%; margin: 12px 0;"><tbody>${rows}</tbody></table>`;
|
|
1128
|
+
};
|
|
1129
|
+
const enhanceImportedTables = (root) => {
|
|
1130
|
+
const tables = root.querySelectorAll('table');
|
|
1131
|
+
tables.forEach(tbl => {
|
|
1132
|
+
tbl.style.borderCollapse = tbl.style.borderCollapse || 'collapse';
|
|
1133
|
+
tbl.style.width = tbl.style.width || '100%';
|
|
1134
|
+
const cells = tbl.querySelectorAll('td, th');
|
|
1135
|
+
cells.forEach(cell => {
|
|
1136
|
+
const el = cell;
|
|
1137
|
+
if (!el.style.border && !el.style.borderTop && !el.style.borderRight && !el.style.borderBottom && !el.style.borderLeft) {
|
|
1138
|
+
el.style.border = '1px solid #d1d5db';
|
|
1139
|
+
}
|
|
1140
|
+
el.style.padding = el.style.padding || '8px';
|
|
1141
|
+
el.style.verticalAlign = el.style.verticalAlign || 'top';
|
|
1142
|
+
});
|
|
1143
|
+
});
|
|
1144
|
+
};
|
|
1145
|
+
const escapeHtml = (value) => value
|
|
1146
|
+
.replace(/&/g, "&")
|
|
1147
|
+
.replace(/</g, "<")
|
|
1148
|
+
.replace(/>/g, ">");
|
|
1149
|
+
const markdownToHtml = (markdown) => {
|
|
1150
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
1151
|
+
let html = "";
|
|
1152
|
+
let listType = null;
|
|
1153
|
+
const closeList = () => {
|
|
1154
|
+
if (listType) {
|
|
1155
|
+
html += `</${listType}>`;
|
|
1156
|
+
listType = null;
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
const inline = (text) => escapeHtml(text)
|
|
1160
|
+
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
|
|
1161
|
+
.replace(/\*([^*]+)\*/g, "<em>$1</em>")
|
|
1162
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
1163
|
+
lines.forEach((line) => {
|
|
1164
|
+
const trimmed = line.trim();
|
|
1165
|
+
if (!trimmed) {
|
|
1166
|
+
closeList();
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
const heading = /^(#{1,6})\s+(.+)$/.exec(trimmed);
|
|
1170
|
+
if (heading) {
|
|
1171
|
+
closeList();
|
|
1172
|
+
const level = heading[1].length;
|
|
1173
|
+
html += `<h${level}>${inline(heading[2])}</h${level}>`;
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const bullet = /^[-*]\s+(.+)$/.exec(trimmed);
|
|
1177
|
+
if (bullet) {
|
|
1178
|
+
if (listType !== "ul") {
|
|
1179
|
+
closeList();
|
|
1180
|
+
html += "<ul>";
|
|
1181
|
+
listType = "ul";
|
|
1182
|
+
}
|
|
1183
|
+
html += `<li>${inline(bullet[1])}</li>`;
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
const numbered = /^\d+[.)]\s+(.+)$/.exec(trimmed);
|
|
1187
|
+
if (numbered) {
|
|
1188
|
+
if (listType !== "ol") {
|
|
1189
|
+
closeList();
|
|
1190
|
+
html += "<ol>";
|
|
1191
|
+
listType = "ol";
|
|
1192
|
+
}
|
|
1193
|
+
html += `<li>${inline(numbered[1])}</li>`;
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (trimmed.startsWith("> ")) {
|
|
1197
|
+
closeList();
|
|
1198
|
+
html += `<blockquote>${inline(trimmed.slice(2))}</blockquote>`;
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
closeList();
|
|
1202
|
+
html += `<p>${inline(trimmed)}</p>`;
|
|
1203
|
+
});
|
|
1204
|
+
closeList();
|
|
1205
|
+
return html;
|
|
1206
|
+
};
|
|
1207
|
+
const htmlToMarkdown = (html) => {
|
|
1208
|
+
const root = document.createElement("div");
|
|
1209
|
+
root.innerHTML = html;
|
|
1210
|
+
const walk = (node) => {
|
|
1211
|
+
if (node.nodeType === Node.TEXT_NODE)
|
|
1212
|
+
return node.textContent || "";
|
|
1213
|
+
if (!(node instanceof HTMLElement))
|
|
1214
|
+
return "";
|
|
1215
|
+
const content = Array.from(node.childNodes).map(walk).join("");
|
|
1216
|
+
const tag = node.tagName.toLowerCase();
|
|
1217
|
+
if (tag === "strong" || tag === "b")
|
|
1218
|
+
return `**${content}**`;
|
|
1219
|
+
if (tag === "em" || tag === "i")
|
|
1220
|
+
return `*${content}*`;
|
|
1221
|
+
if (tag === "code")
|
|
1222
|
+
return `\`${content}\``;
|
|
1223
|
+
if (tag === "br")
|
|
1224
|
+
return "\n";
|
|
1225
|
+
if (/h[1-6]/.test(tag))
|
|
1226
|
+
return `${"#".repeat(Number(tag[1]))} ${content.trim()}\n\n`;
|
|
1227
|
+
if (tag === "p")
|
|
1228
|
+
return `${content.trim()}\n\n`;
|
|
1229
|
+
if (tag === "li")
|
|
1230
|
+
return `- ${content.trim()}\n`;
|
|
1231
|
+
if (tag === "ul" || tag === "ol")
|
|
1232
|
+
return `${content}\n`;
|
|
1233
|
+
if (tag === "blockquote")
|
|
1234
|
+
return `> ${content.trim()}\n\n`;
|
|
1235
|
+
if (tag === "table")
|
|
1236
|
+
return `${node.outerHTML}\n\n`;
|
|
1237
|
+
if (tag === "img")
|
|
1238
|
+
return ` || ""})`;
|
|
1239
|
+
if (tag === "a")
|
|
1240
|
+
return `[${content}](${node.getAttribute("href") || ""})`;
|
|
1241
|
+
return content;
|
|
1242
|
+
};
|
|
1243
|
+
return Array.from(root.childNodes).map(walk).join("").replace(/\n{3,}/g, "\n\n").trim();
|
|
1244
|
+
};
|
|
1245
|
+
const importTextFile = async (files, type) => {
|
|
1246
|
+
if (!files || files.length === 0)
|
|
1247
|
+
return;
|
|
1248
|
+
const file = files[0];
|
|
1249
|
+
const text = await file.text();
|
|
1250
|
+
const html = type === "html" ? text : markdownToHtml(text);
|
|
1251
|
+
const el = editableRef.current;
|
|
1252
|
+
const hasContent = el && el.textContent && el.textContent.trim().length > 0;
|
|
1253
|
+
insertImportedHtml(html, hasContent ? "append" : "replace");
|
|
1254
|
+
};
|
|
1255
|
+
const downloadText = (filename, content, mimeType) => {
|
|
1256
|
+
const blob = new Blob([content], { type: mimeType });
|
|
1257
|
+
const url = URL.createObjectURL(blob);
|
|
1258
|
+
const link = document.createElement("a");
|
|
1259
|
+
link.href = url;
|
|
1260
|
+
link.download = filename;
|
|
1261
|
+
document.body.appendChild(link);
|
|
1262
|
+
link.click();
|
|
1263
|
+
link.remove();
|
|
1264
|
+
URL.revokeObjectURL(url);
|
|
1265
|
+
};
|
|
1266
|
+
const exportHtml = () => {
|
|
1267
|
+
const html = editableRef.current?.innerHTML || "";
|
|
1268
|
+
downloadText("smart-rte-export.html", html, "text/html");
|
|
1269
|
+
};
|
|
1270
|
+
const exportMarkdown = () => {
|
|
1271
|
+
const html = editableRef.current?.innerHTML || "";
|
|
1272
|
+
downloadText("smart-rte-export.md", htmlToMarkdown(html), "text/markdown");
|
|
1273
|
+
};
|
|
920
1274
|
const fixNegativeMargins = (root) => {
|
|
921
1275
|
try {
|
|
922
1276
|
const nodes = root.querySelectorAll('*');
|
|
@@ -929,6 +1283,152 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
929
1283
|
}
|
|
930
1284
|
catch { }
|
|
931
1285
|
};
|
|
1286
|
+
const cleanPastedHtml = (html, options = {}) => {
|
|
1287
|
+
const shouldPreserveColors = options.preserveColors ?? preserveColors;
|
|
1288
|
+
const shouldPreserveDocumentLayout = options.preserveDocumentLayout ?? false;
|
|
1289
|
+
const template = document.createElement('template');
|
|
1290
|
+
template.innerHTML = html
|
|
1291
|
+
.replace(/ /gi, ' ')
|
|
1292
|
+
.replace(/\u00a0/g, ' ')
|
|
1293
|
+
.replace(/[\u200b\u200c\u200d]/g, '');
|
|
1294
|
+
template.content.querySelectorAll('meta, link, style, script').forEach((node) => node.remove());
|
|
1295
|
+
const allowedStyleNames = new Set([
|
|
1296
|
+
'font-weight',
|
|
1297
|
+
'font-style',
|
|
1298
|
+
'text-decoration',
|
|
1299
|
+
'text-align',
|
|
1300
|
+
'vertical-align',
|
|
1301
|
+
'border',
|
|
1302
|
+
'border-top',
|
|
1303
|
+
'border-right',
|
|
1304
|
+
'border-bottom',
|
|
1305
|
+
'border-left',
|
|
1306
|
+
'border-collapse',
|
|
1307
|
+
'padding',
|
|
1308
|
+
'padding-top',
|
|
1309
|
+
'padding-right',
|
|
1310
|
+
'padding-bottom',
|
|
1311
|
+
'padding-left',
|
|
1312
|
+
'list-style-type',
|
|
1313
|
+
'white-space',
|
|
1314
|
+
]);
|
|
1315
|
+
if (preserveFontFamily)
|
|
1316
|
+
allowedStyleNames.add('font-family');
|
|
1317
|
+
if (shouldPreserveColors) {
|
|
1318
|
+
allowedStyleNames.add('color');
|
|
1319
|
+
allowedStyleNames.add('background');
|
|
1320
|
+
allowedStyleNames.add('background-color');
|
|
1321
|
+
}
|
|
1322
|
+
if (shouldPreserveDocumentLayout) {
|
|
1323
|
+
[
|
|
1324
|
+
'font-size',
|
|
1325
|
+
'line-height',
|
|
1326
|
+
'margin',
|
|
1327
|
+
'margin-top',
|
|
1328
|
+
'margin-right',
|
|
1329
|
+
'margin-bottom',
|
|
1330
|
+
'margin-left',
|
|
1331
|
+
'text-indent',
|
|
1332
|
+
'width',
|
|
1333
|
+
'min-width',
|
|
1334
|
+
].forEach((name) => allowedStyleNames.add(name));
|
|
1335
|
+
}
|
|
1336
|
+
template.content.querySelectorAll('*').forEach((node) => {
|
|
1337
|
+
const className = node.getAttribute('class');
|
|
1338
|
+
if (className !== 'srte-preserve-colors')
|
|
1339
|
+
node.removeAttribute('class');
|
|
1340
|
+
node.removeAttribute('id');
|
|
1341
|
+
if (!shouldPreserveDocumentLayout) {
|
|
1342
|
+
node.removeAttribute('width');
|
|
1343
|
+
node.removeAttribute('height');
|
|
1344
|
+
}
|
|
1345
|
+
const style = node.getAttribute('style');
|
|
1346
|
+
if (!style)
|
|
1347
|
+
return;
|
|
1348
|
+
const safeRules = style
|
|
1349
|
+
.split(';')
|
|
1350
|
+
.map((rule) => rule.trim())
|
|
1351
|
+
.filter(Boolean)
|
|
1352
|
+
.filter((rule) => {
|
|
1353
|
+
const separator = rule.indexOf(':');
|
|
1354
|
+
if (separator === -1)
|
|
1355
|
+
return false;
|
|
1356
|
+
const name = rule.slice(0, separator).trim().toLowerCase();
|
|
1357
|
+
const value = rule.slice(separator + 1).trim().toLowerCase();
|
|
1358
|
+
if (!allowedStyleNames.has(name))
|
|
1359
|
+
return false;
|
|
1360
|
+
if (value.includes('position') || value.includes('expression') || value.includes('javascript:'))
|
|
1361
|
+
return false;
|
|
1362
|
+
if (name === 'white-space' && value !== 'pre-wrap')
|
|
1363
|
+
return false;
|
|
1364
|
+
if ((name === 'width' || name === 'min-width') && !/^[\d.]+(px|pt|em|rem|%)$/.test(value))
|
|
1365
|
+
return false;
|
|
1366
|
+
return true;
|
|
1367
|
+
});
|
|
1368
|
+
if (safeRules.length)
|
|
1369
|
+
node.setAttribute('style', safeRules.join('; '));
|
|
1370
|
+
else
|
|
1371
|
+
node.removeAttribute('style');
|
|
1372
|
+
});
|
|
1373
|
+
return template.innerHTML;
|
|
1374
|
+
};
|
|
1375
|
+
const normalizeEditorContent = () => {
|
|
1376
|
+
const el = editableRef.current;
|
|
1377
|
+
if (!el)
|
|
1378
|
+
return;
|
|
1379
|
+
fixNegativeMargins(el);
|
|
1380
|
+
ensureTableWrappers(el);
|
|
1381
|
+
addTableResizeHandles();
|
|
1382
|
+
};
|
|
1383
|
+
const insertHtmlAtEnd = (html) => {
|
|
1384
|
+
const el = editableRef.current;
|
|
1385
|
+
if (!el)
|
|
1386
|
+
return;
|
|
1387
|
+
el.focus();
|
|
1388
|
+
const range = document.createRange();
|
|
1389
|
+
range.selectNodeContents(el);
|
|
1390
|
+
range.collapse(false);
|
|
1391
|
+
const sel = window.getSelection();
|
|
1392
|
+
sel?.removeAllRanges();
|
|
1393
|
+
sel?.addRange(range);
|
|
1394
|
+
const separator = el.textContent?.trim() ? '<p><br></p>' : '';
|
|
1395
|
+
document.execCommand('insertHTML', false, `${separator}${html}`);
|
|
1396
|
+
};
|
|
1397
|
+
const replaceEditorHtml = (html) => {
|
|
1398
|
+
const el = editableRef.current;
|
|
1399
|
+
if (!el)
|
|
1400
|
+
return;
|
|
1401
|
+
el.innerHTML = html;
|
|
1402
|
+
el.focus();
|
|
1403
|
+
const range = document.createRange();
|
|
1404
|
+
range.selectNodeContents(el);
|
|
1405
|
+
range.collapse(false);
|
|
1406
|
+
const sel = window.getSelection();
|
|
1407
|
+
sel?.removeAllRanges();
|
|
1408
|
+
sel?.addRange(range);
|
|
1409
|
+
};
|
|
1410
|
+
const insertImportedHtml = (html, mode, cleanOptions) => {
|
|
1411
|
+
try {
|
|
1412
|
+
const cleanHtml = cleanPastedHtml(html, cleanOptions);
|
|
1413
|
+
if (mode === 'replace')
|
|
1414
|
+
replaceEditorHtml(cleanHtml);
|
|
1415
|
+
else
|
|
1416
|
+
insertHtmlAtEnd(cleanHtml);
|
|
1417
|
+
normalizeEditorContent();
|
|
1418
|
+
emitChange();
|
|
1419
|
+
}
|
|
1420
|
+
catch (error) {
|
|
1421
|
+
console.error('Error inserting imported content:', error);
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
const insertCleanHtml = (html) => {
|
|
1425
|
+
try {
|
|
1426
|
+
document.execCommand("insertHTML", false, cleanPastedHtml(html));
|
|
1427
|
+
normalizeEditorContent();
|
|
1428
|
+
emitChange();
|
|
1429
|
+
}
|
|
1430
|
+
catch { }
|
|
1431
|
+
};
|
|
932
1432
|
const ensureTableWrappers = (root) => {
|
|
933
1433
|
try {
|
|
934
1434
|
const tables = root.querySelectorAll('table');
|
|
@@ -1335,6 +1835,27 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1335
1835
|
firstRow.replaceChild(td, c);
|
|
1336
1836
|
}
|
|
1337
1837
|
}
|
|
1838
|
+
handleInput();
|
|
1839
|
+
};
|
|
1840
|
+
const toggleHeaderColumn = (cell) => {
|
|
1841
|
+
const pos = getCellPosition(cell);
|
|
1842
|
+
if (!pos)
|
|
1843
|
+
return;
|
|
1844
|
+
const { tbody, cIdx } = pos;
|
|
1845
|
+
const rows = Array.from(tbody.querySelectorAll("tr"));
|
|
1846
|
+
const columnCells = rows
|
|
1847
|
+
.map((row) => cellsOfRow(row)[cIdx])
|
|
1848
|
+
.filter(Boolean);
|
|
1849
|
+
const shouldMakeHeader = columnCells.some((c) => c.tagName !== "TH");
|
|
1850
|
+
for (const c of columnCells) {
|
|
1851
|
+
const replacement = document.createElement(shouldMakeHeader ? "th" : "td");
|
|
1852
|
+
replacement.innerHTML = c.innerHTML || " ";
|
|
1853
|
+
replacement.style.border = c.style.border || "1px solid var(--srte-border)";
|
|
1854
|
+
replacement.style.padding = c.style.padding || "6px";
|
|
1855
|
+
replacement.style.minWidth = c.style.minWidth || "60px";
|
|
1856
|
+
c.parentElement?.replaceChild(replacement, c);
|
|
1857
|
+
}
|
|
1858
|
+
handleInput();
|
|
1338
1859
|
};
|
|
1339
1860
|
const applyBgToSelection = (hex, fallbackCell) => {
|
|
1340
1861
|
const sel = selectionRef.current;
|
|
@@ -1552,6 +2073,12 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1552
2073
|
} }), _jsx("input", { ref: docxInputRef, type: "file", accept: ".docx", style: { display: "none" }, onChange: (e) => {
|
|
1553
2074
|
handleDocxFiles(e.currentTarget.files);
|
|
1554
2075
|
e.currentTarget.value = "";
|
|
2076
|
+
} }), _jsx("input", { ref: htmlInputRef, type: "file", accept: ".html,.htm,text/html", style: { display: "none" }, onChange: (e) => {
|
|
2077
|
+
importTextFile(e.currentTarget.files, "html");
|
|
2078
|
+
e.currentTarget.value = "";
|
|
2079
|
+
} }), _jsx("input", { ref: mdInputRef, type: "file", accept: ".md,.markdown,text/markdown,text/plain", style: { display: "none" }, onChange: (e) => {
|
|
2080
|
+
importTextFile(e.currentTarget.files, "md");
|
|
2081
|
+
e.currentTarget.value = "";
|
|
1555
2082
|
} }), _jsxs("select", { defaultValue: "p", onChange: (e) => {
|
|
1556
2083
|
const val = e.target.value;
|
|
1557
2084
|
if (val === "p")
|
|
@@ -1621,7 +2148,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1621
2148
|
borderRadius: 6,
|
|
1622
2149
|
background: "var(--srte-input-bg)",
|
|
1623
2150
|
color: "var(--srte-input-text)",
|
|
1624
|
-
}, children: [_jsx("option", { value: "", disabled: true, children: "Size" }), _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("select", { value: currentFont, onMouseDown: () => {
|
|
2151
|
+
}, children: [_jsx("option", { value: "", disabled: true, children: "Size" }), _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" })] }), preserveFontFamily && (_jsxs("select", { value: currentFont, onMouseDown: () => {
|
|
1625
2152
|
const sel = window.getSelection();
|
|
1626
2153
|
if (sel && sel.rangeCount > 0) {
|
|
1627
2154
|
const range = sel.getRangeAt(0);
|
|
@@ -1638,7 +2165,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1638
2165
|
background: "var(--srte-input-bg)",
|
|
1639
2166
|
color: "var(--srte-input-text)",
|
|
1640
2167
|
maxWidth: 100,
|
|
1641
|
-
}, children: [_jsx("option", { value: "", disabled: true, children: "Font" }), fonts.map((f) => (_jsx("option", { value: f.value, children: f.name }, f.value)))] }), _jsx("button", { title: "Text Color", onClick: () => {
|
|
2168
|
+
}, children: [_jsx("option", { value: "", disabled: true, children: "Font" }), fonts.map((f) => (_jsx("option", { value: f.value, children: f.name }, f.value)))] })), _jsx("button", { title: "Text Color", onClick: () => {
|
|
1642
2169
|
setColorPickerType('text');
|
|
1643
2170
|
setShowColorPicker(true);
|
|
1644
2171
|
}, style: {
|
|
@@ -1650,7 +2177,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1650
2177
|
background: "var(--srte-input-bg)",
|
|
1651
2178
|
color: "var(--srte-input-text)",
|
|
1652
2179
|
position: "relative",
|
|
1653
|
-
}, children: _jsx("span", { style: { fontWeight: 700 }, children: "A" }) }), _jsx("button", { title: "Background Color", onClick: () => {
|
|
2180
|
+
}, children: _jsx("span", { style: { fontWeight: 700, borderBottom: "3px solid currentColor", lineHeight: 1 }, children: "A" }) }), _jsx("button", { title: "Background Color", onClick: () => {
|
|
1654
2181
|
setColorPickerType('background');
|
|
1655
2182
|
setShowColorPicker(true);
|
|
1656
2183
|
}, style: {
|
|
@@ -1661,7 +2188,23 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1661
2188
|
borderRadius: 6,
|
|
1662
2189
|
background: "var(--srte-input-bg)",
|
|
1663
2190
|
color: "var(--srte-input-text)",
|
|
1664
|
-
}, children: _jsx("span", { style: { fontWeight: 700, padding: "
|
|
2191
|
+
}, children: _jsx("span", { style: { fontWeight: 700, padding: "1px 4px", background: "var(--srte-accent-bg)", borderRadius: 3 }, children: "A" }) }), _jsxs("button", { title: "Subscript", onClick: () => exec("subscript"), style: {
|
|
2192
|
+
height: 32,
|
|
2193
|
+
minWidth: 32,
|
|
2194
|
+
padding: "0 8px",
|
|
2195
|
+
border: "1px solid var(--srte-input-border)",
|
|
2196
|
+
borderRadius: 6,
|
|
2197
|
+
background: "var(--srte-input-bg)",
|
|
2198
|
+
color: "var(--srte-input-text)",
|
|
2199
|
+
}, children: ["X", _jsx("sub", { children: "2" })] }), _jsxs("button", { title: "Superscript", onClick: () => exec("superscript"), style: {
|
|
2200
|
+
height: 32,
|
|
2201
|
+
minWidth: 32,
|
|
2202
|
+
padding: "0 8px",
|
|
2203
|
+
border: "1px solid var(--srte-input-border)",
|
|
2204
|
+
borderRadius: 6,
|
|
2205
|
+
background: "var(--srte-input-bg)",
|
|
2206
|
+
color: "var(--srte-input-text)",
|
|
2207
|
+
}, children: ["X", _jsx("sup", { children: "2" })] }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), style: {
|
|
1665
2208
|
height: 32,
|
|
1666
2209
|
padding: "0 10px",
|
|
1667
2210
|
border: "1px solid var(--srte-input-border)",
|
|
@@ -1675,7 +2218,15 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1675
2218
|
borderRadius: 6,
|
|
1676
2219
|
background: "var(--srte-input-bg)",
|
|
1677
2220
|
color: "var(--srte-input-text)",
|
|
1678
|
-
}, children: "1. List" }), _jsx("button", { title: "Blockquote", onClick:
|
|
2221
|
+
}, children: "1. List" }), _jsx("button", { title: "Blockquote", onClick: toggleBlockquote, style: {
|
|
2222
|
+
height: 32,
|
|
2223
|
+
minWidth: 32,
|
|
2224
|
+
padding: "0 8px",
|
|
2225
|
+
border: "1px solid var(--srte-input-border)",
|
|
2226
|
+
borderRadius: 6,
|
|
2227
|
+
background: "var(--srte-input-bg)",
|
|
2228
|
+
color: "var(--srte-input-text)",
|
|
2229
|
+
}, children: "\u275D" }), _jsx("button", { title: "Special characters", onClick: () => setShowSpecialChars(true), style: {
|
|
1679
2230
|
height: 32,
|
|
1680
2231
|
minWidth: 32,
|
|
1681
2232
|
padding: "0 8px",
|
|
@@ -1683,7 +2234,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1683
2234
|
borderRadius: 6,
|
|
1684
2235
|
background: "var(--srte-input-bg)",
|
|
1685
2236
|
color: "var(--srte-input-text)",
|
|
1686
|
-
}, children: "\
|
|
2237
|
+
}, children: "\u03A9" }), _jsx("button", { title: "Code block", onClick: () => exec("formatBlock", "<pre>"), style: {
|
|
1687
2238
|
height: 32,
|
|
1688
2239
|
minWidth: 36,
|
|
1689
2240
|
padding: "0 8px",
|
|
@@ -1744,7 +2295,35 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1744
2295
|
background: "var(--srte-input-bg)",
|
|
1745
2296
|
color: "var(--srte-input-text)",
|
|
1746
2297
|
opacity: loadingDocx ? 0.5 : 1,
|
|
1747
|
-
}, children: loadingDocx ? '⌛ Importing...' : '📝 DOCX' }),
|
|
2298
|
+
}, children: loadingDocx ? '⌛ Importing...' : '📝 DOCX' }), _jsx("button", { title: "Import HTML", onClick: () => htmlInputRef.current?.click(), style: {
|
|
2299
|
+
height: 32,
|
|
2300
|
+
padding: "0 10px",
|
|
2301
|
+
border: "1px solid var(--srte-input-border)",
|
|
2302
|
+
borderRadius: 6,
|
|
2303
|
+
background: "var(--srte-input-bg)",
|
|
2304
|
+
color: "var(--srte-input-text)",
|
|
2305
|
+
}, children: "HTML" }), _jsx("button", { title: "Import Markdown", onClick: () => mdInputRef.current?.click(), style: {
|
|
2306
|
+
height: 32,
|
|
2307
|
+
padding: "0 10px",
|
|
2308
|
+
border: "1px solid var(--srte-input-border)",
|
|
2309
|
+
borderRadius: 6,
|
|
2310
|
+
background: "var(--srte-input-bg)",
|
|
2311
|
+
color: "var(--srte-input-text)",
|
|
2312
|
+
}, children: "MD" }), _jsx("button", { title: "Export HTML", onClick: exportHtml, style: {
|
|
2313
|
+
height: 32,
|
|
2314
|
+
padding: "0 10px",
|
|
2315
|
+
border: "1px solid var(--srte-input-border)",
|
|
2316
|
+
borderRadius: 6,
|
|
2317
|
+
background: "var(--srte-input-bg)",
|
|
2318
|
+
color: "var(--srte-input-text)",
|
|
2319
|
+
}, children: "Export HTML" }), _jsx("button", { title: "Export Markdown", onClick: exportMarkdown, style: {
|
|
2320
|
+
height: 32,
|
|
2321
|
+
padding: "0 10px",
|
|
2322
|
+
border: "1px solid var(--srte-input-border)",
|
|
2323
|
+
borderRadius: 6,
|
|
2324
|
+
background: "var(--srte-input-bg)",
|
|
2325
|
+
color: "var(--srte-input-text)",
|
|
2326
|
+
}, children: "Export MD" }), _jsxs("div", { style: {
|
|
1748
2327
|
display: "inline-flex",
|
|
1749
2328
|
gap: 4,
|
|
1750
2329
|
alignItems: "center",
|
|
@@ -1823,7 +2402,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1823
2402
|
color: "var(--srte-input-text)",
|
|
1824
2403
|
}, children: "\u293E Redo" })] }), media && mediaManager && (_jsx(MediaManager, { open: showMediaManager, onClose: () => setShowMediaManager(false), adapter: mediaManager, onSelect: (item) => {
|
|
1825
2404
|
if (item?.url)
|
|
1826
|
-
insertImageAtSelection(item
|
|
2405
|
+
insertImageAtSelection(item);
|
|
1827
2406
|
} })), table && showTableDialog && (_jsx("div", { style: {
|
|
1828
2407
|
position: "fixed",
|
|
1829
2408
|
inset: 0,
|
|
@@ -1988,6 +2567,50 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1988
2567
|
background: 'var(--srte-input-bg)',
|
|
1989
2568
|
color: 'var(--srte-input-text)',
|
|
1990
2569
|
cursor: 'pointer',
|
|
2570
|
+
}, children: "Close" }) })] }) })), showSpecialChars && (_jsx("div", { style: {
|
|
2571
|
+
position: "fixed",
|
|
2572
|
+
inset: 0,
|
|
2573
|
+
background: "var(--srte-modal-backdrop)",
|
|
2574
|
+
display: "flex",
|
|
2575
|
+
alignItems: "center",
|
|
2576
|
+
justifyContent: "center",
|
|
2577
|
+
zIndex: 50,
|
|
2578
|
+
}, onClick: () => setShowSpecialChars(false), children: _jsxs("div", { style: {
|
|
2579
|
+
background: "var(--srte-modal-bg)",
|
|
2580
|
+
color: "var(--srte-modal-text)",
|
|
2581
|
+
padding: 16,
|
|
2582
|
+
borderRadius: 8,
|
|
2583
|
+
width: 420,
|
|
2584
|
+
maxWidth: "90vw",
|
|
2585
|
+
boxShadow: "var(--srte-menu-shadow)",
|
|
2586
|
+
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 12 }, children: "Special characters" }), [
|
|
2587
|
+
{
|
|
2588
|
+
label: "Greek",
|
|
2589
|
+
chars: ["α", "β", "γ", "δ", "ε", "ζ", "η", "θ", "ι", "κ", "λ", "μ", "ν", "ξ", "π", "ρ", "σ", "τ", "φ", "χ", "ψ", "ω", "Δ", "Σ", "Ω"],
|
|
2590
|
+
},
|
|
2591
|
+
{
|
|
2592
|
+
label: "Medical / Math",
|
|
2593
|
+
chars: ["±", "≤", "≥", "≠", "≈", "∞", "°", "µ", "×", "÷", "→", "←", "↑", "↓", "∴", "∵", "√", "∑", "∫", "₂", "₃", "²", "³"],
|
|
2594
|
+
},
|
|
2595
|
+
].map((group) => (_jsxs("div", { style: { marginBottom: 12 }, children: [_jsx("div", { style: { fontSize: 12, color: "var(--srte-text-muted)", marginBottom: 6 }, children: group.label }), _jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: group.chars.map((char) => (_jsx("button", { type: "button", onClick: () => {
|
|
2596
|
+
insertTextAtSelection(char);
|
|
2597
|
+
setShowSpecialChars(false);
|
|
2598
|
+
}, style: {
|
|
2599
|
+
height: 32,
|
|
2600
|
+
minWidth: 32,
|
|
2601
|
+
padding: "0 8px",
|
|
2602
|
+
border: "1px solid var(--srte-input-border)",
|
|
2603
|
+
borderRadius: 6,
|
|
2604
|
+
background: "var(--srte-input-bg)",
|
|
2605
|
+
color: "var(--srte-input-text)",
|
|
2606
|
+
fontSize: 16,
|
|
2607
|
+
}, children: char }, char))) })] }, group.label))), _jsx("div", { style: { display: "flex", justifyContent: "flex-end" }, children: _jsx("button", { type: "button", onClick: () => setShowSpecialChars(false), style: {
|
|
2608
|
+
padding: "6px 16px",
|
|
2609
|
+
border: "1px solid var(--srte-input-border)",
|
|
2610
|
+
borderRadius: 6,
|
|
2611
|
+
background: "var(--srte-input-bg)",
|
|
2612
|
+
color: "var(--srte-input-text)",
|
|
2613
|
+
cursor: "pointer",
|
|
1991
2614
|
}, children: "Close" }) })] }) })), formula && showFormulaDialog && (_jsx("div", { style: {
|
|
1992
2615
|
position: "fixed",
|
|
1993
2616
|
inset: 0,
|
|
@@ -2109,8 +2732,14 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2109
2732
|
if (hasImage) {
|
|
2110
2733
|
e.preventDefault();
|
|
2111
2734
|
handleLocalImageFiles(items);
|
|
2735
|
+
return;
|
|
2112
2736
|
}
|
|
2113
2737
|
}
|
|
2738
|
+
const html = e.clipboardData?.getData("text/html");
|
|
2739
|
+
if (html) {
|
|
2740
|
+
e.preventDefault();
|
|
2741
|
+
insertCleanHtml(cleanPastedHtml(html));
|
|
2742
|
+
}
|
|
2114
2743
|
}, onDragOver: (e) => {
|
|
2115
2744
|
// Allow dragging images within editor and file drops
|
|
2116
2745
|
if (draggedImageRef.current ||
|
|
@@ -2613,7 +3242,16 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2613
3242
|
}, onClick: () => {
|
|
2614
3243
|
toggleHeaderRow(tableMenu.cell);
|
|
2615
3244
|
setTableMenu(null);
|
|
2616
|
-
}, children: [_jsx("span", { children: "H\u2081" }), _jsx("span", { children: "Toggle header row" })] }),
|
|
3245
|
+
}, children: [_jsx("span", { children: "H\u2081" }), _jsx("span", { children: "Toggle header row" })] }), _jsxs("button", { style: {
|
|
3246
|
+
display: "flex",
|
|
3247
|
+
alignItems: "center",
|
|
3248
|
+
gap: 8,
|
|
3249
|
+
padding: "6px 8px",
|
|
3250
|
+
fontSize: 12,
|
|
3251
|
+
}, onClick: () => {
|
|
3252
|
+
toggleHeaderColumn(tableMenu.cell);
|
|
3253
|
+
setTableMenu(null);
|
|
3254
|
+
}, children: [_jsx("span", { children: "H\u2195" }), _jsx("span", { children: "Toggle header column" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { style: {
|
|
2617
3255
|
display: "flex",
|
|
2618
3256
|
alignItems: "center",
|
|
2619
3257
|
gap: 8,
|
|
@@ -2640,7 +3278,9 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2640
3278
|
borderRadius: 8,
|
|
2641
3279
|
boxShadow: "var(--srte-menu-shadow)",
|
|
2642
3280
|
padding: 8,
|
|
2643
|
-
width:
|
|
3281
|
+
width: 280,
|
|
3282
|
+
maxHeight: "80vh",
|
|
3283
|
+
overflowY: "auto",
|
|
2644
3284
|
color: "var(--srte-menu-text)",
|
|
2645
3285
|
}, 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"
|
|
2646
3286
|
? imageMenu.img.parentElement.href
|
|
@@ -2742,7 +3382,58 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2742
3382
|
img.style.margin = "0 0 8px 8px";
|
|
2743
3383
|
scheduleImageOverlay();
|
|
2744
3384
|
handleInput();
|
|
2745
|
-
}, children: "\u27F9" })] }), _jsxs("div", { style: {
|
|
3385
|
+
}, children: "\u27F9" })] }), _jsxs("div", { style: {
|
|
3386
|
+
borderTop: "1px solid var(--srte-border-light)",
|
|
3387
|
+
paddingTop: 6,
|
|
3388
|
+
display: "grid",
|
|
3389
|
+
gap: 6,
|
|
3390
|
+
}, children: [_jsx("div", { style: { fontWeight: 600, fontSize: 11 }, children: "License" }), _jsx("input", { placeholder: "Work name", defaultValue: imageMenu.img.dataset.workName || "", onChange: (e) => {
|
|
3391
|
+
imageMenu.img.dataset.workName = e.target.value;
|
|
3392
|
+
handleInput();
|
|
3393
|
+
}, style: {
|
|
3394
|
+
padding: "4px 6px",
|
|
3395
|
+
border: "1px solid var(--srte-border-light)",
|
|
3396
|
+
borderRadius: 4,
|
|
3397
|
+
color: "var(--srte-input-text)",
|
|
3398
|
+
background: "var(--srte-input-bg)",
|
|
3399
|
+
} }), _jsx("input", { placeholder: "Author", defaultValue: imageMenu.img.dataset.licenseAuthor || "", onChange: (e) => {
|
|
3400
|
+
imageMenu.img.dataset.licenseAuthor = e.target.value;
|
|
3401
|
+
handleInput();
|
|
3402
|
+
}, style: {
|
|
3403
|
+
padding: "4px 6px",
|
|
3404
|
+
border: "1px solid var(--srte-border-light)",
|
|
3405
|
+
borderRadius: 4,
|
|
3406
|
+
color: "var(--srte-input-text)",
|
|
3407
|
+
background: "var(--srte-input-bg)",
|
|
3408
|
+
} }), _jsxs("select", { defaultValue: imageMenu.img.dataset.licenseType || "", onChange: (e) => {
|
|
3409
|
+
imageMenu.img.dataset.licenseType = e.target.value;
|
|
3410
|
+
handleInput();
|
|
3411
|
+
}, style: {
|
|
3412
|
+
height: 28,
|
|
3413
|
+
padding: "0 6px",
|
|
3414
|
+
border: "1px solid var(--srte-border-light)",
|
|
3415
|
+
borderRadius: 4,
|
|
3416
|
+
color: "var(--srte-input-text)",
|
|
3417
|
+
background: "var(--srte-input-bg)",
|
|
3418
|
+
}, children: [_jsx("option", { value: "", children: "License type" }), _jsx("option", { value: "public-domain", children: "Public domain" }), _jsx("option", { value: "cc0", children: "CC0" }), _jsx("option", { value: "cc-by", children: "CC BY" }), _jsx("option", { value: "cc-by-sa", children: "CC BY-SA" }), _jsx("option", { value: "cc-by-nc", children: "CC BY-NC" }), _jsx("option", { value: "rights-managed", children: "Rights managed" }), _jsx("option", { value: "custom", children: "Custom" })] }), _jsx("input", { placeholder: "License notes", defaultValue: imageMenu.img.dataset.licenseText || "", onChange: (e) => {
|
|
3419
|
+
imageMenu.img.dataset.licenseText = e.target.value;
|
|
3420
|
+
handleInput();
|
|
3421
|
+
}, style: {
|
|
3422
|
+
padding: "4px 6px",
|
|
3423
|
+
border: "1px solid var(--srte-border-light)",
|
|
3424
|
+
borderRadius: 4,
|
|
3425
|
+
color: "var(--srte-input-text)",
|
|
3426
|
+
background: "var(--srte-input-bg)",
|
|
3427
|
+
} }), _jsx("input", { placeholder: "Source URL", defaultValue: imageMenu.img.dataset.licenseUrl || "", onChange: (e) => {
|
|
3428
|
+
imageMenu.img.dataset.licenseUrl = e.target.value;
|
|
3429
|
+
handleInput();
|
|
3430
|
+
}, style: {
|
|
3431
|
+
padding: "4px 6px",
|
|
3432
|
+
border: "1px solid var(--srte-border-light)",
|
|
3433
|
+
borderRadius: 4,
|
|
3434
|
+
color: "var(--srte-input-text)",
|
|
3435
|
+
background: "var(--srte-input-bg)",
|
|
3436
|
+
} })] }), _jsxs("div", { style: { display: "flex", gap: 6 }, children: [_jsx("button", { onClick: () => {
|
|
2746
3437
|
replaceTargetRef.current = imageMenu.img;
|
|
2747
3438
|
fileInputRef.current?.click();
|
|
2748
3439
|
}, children: "Replace\u2026" }), _jsx("button", { onClick: () => {
|
|
@@ -10,6 +10,13 @@ export type MediaItem = {
|
|
|
10
10
|
title?: string;
|
|
11
11
|
alt?: string;
|
|
12
12
|
tags?: string[];
|
|
13
|
+
license?: {
|
|
14
|
+
author?: string;
|
|
15
|
+
licenseType?: string;
|
|
16
|
+
licenseText?: string;
|
|
17
|
+
sourceUrl?: string;
|
|
18
|
+
workName?: string;
|
|
19
|
+
};
|
|
13
20
|
};
|
|
14
21
|
export type MediaSearchQuery = {
|
|
15
22
|
q?: string;
|
|
@@ -7,6 +7,7 @@ export function MediaManager(props) {
|
|
|
7
7
|
const [error, setError] = useState(null);
|
|
8
8
|
const [query, setQuery] = useState("");
|
|
9
9
|
const [results, setResults] = useState([]);
|
|
10
|
+
const [infoItem, setInfoItem] = useState(null);
|
|
10
11
|
const fileInputRef = useRef(null);
|
|
11
12
|
useEffect(() => {
|
|
12
13
|
if (!open)
|
|
@@ -151,22 +152,72 @@ export function MediaManager(props) {
|
|
|
151
152
|
gap: 12,
|
|
152
153
|
overflowY: "auto",
|
|
153
154
|
paddingBottom: 16,
|
|
154
|
-
}, children: results.map((it) => (_jsxs("
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
display: "block",
|
|
155
|
+
}, children: results.map((it) => (_jsxs("div", { title: it.title || it.url, style: {
|
|
156
|
+
display: "flex",
|
|
157
|
+
flexDirection: "column",
|
|
158
|
+
gap: 6,
|
|
159
159
|
border: "1px solid var(--srte-border-light)",
|
|
160
160
|
borderRadius: 8,
|
|
161
161
|
padding: 6,
|
|
162
162
|
background: "var(--srte-input-bg)",
|
|
163
|
-
|
|
164
|
-
}, children: [_jsx("
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
color: "var(--srte-input-text)",
|
|
164
|
+
}, children: [_jsx("button", { type: "button", onClick: () => {
|
|
165
|
+
onSelect(it);
|
|
166
|
+
onClose();
|
|
167
|
+
}, style: {
|
|
168
|
+
border: "none",
|
|
169
|
+
padding: 0,
|
|
170
|
+
background: "transparent",
|
|
171
|
+
cursor: "pointer",
|
|
172
|
+
}, children: _jsx("img", { src: it.url, alt: it.alt || "", style: {
|
|
173
|
+
maxWidth: "100%",
|
|
174
|
+
maxHeight: 100,
|
|
175
|
+
display: "block",
|
|
176
|
+
margin: "0 auto",
|
|
177
|
+
objectFit: "cover",
|
|
178
|
+
borderRadius: 6,
|
|
179
|
+
} }) }), _jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 6 }, children: [_jsx("div", { style: { fontSize: 11, color: "var(--srte-text-muted)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: it.title || it.alt || (it.width && it.height ? `${it.width}×${it.height}` : "Image") }), _jsx("button", { type: "button", onClick: () => setInfoItem(it), title: "Image info", style: {
|
|
180
|
+
width: 24,
|
|
181
|
+
height: 24,
|
|
182
|
+
border: "1px solid var(--srte-border)",
|
|
183
|
+
borderRadius: 999,
|
|
184
|
+
background: "var(--srte-surface-subtle)",
|
|
185
|
+
color: "var(--srte-input-text)",
|
|
186
|
+
cursor: "pointer",
|
|
187
|
+
flex: "0 0 auto",
|
|
188
|
+
}, children: "i" })] }), it.tags && it.tags.length > 0 && (_jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: it.tags.slice(0, 3).map((tag) => (_jsx("span", { style: {
|
|
189
|
+
fontSize: 10,
|
|
190
|
+
padding: "1px 5px",
|
|
191
|
+
borderRadius: 999,
|
|
192
|
+
background: "var(--srte-surface-subtle)",
|
|
193
|
+
color: "var(--srte-text-muted)",
|
|
194
|
+
}, children: tag }, tag))) }))] }, it.id || it.url))) })] })), infoItem && (_jsx("div", { style: {
|
|
195
|
+
position: "fixed",
|
|
196
|
+
inset: 0,
|
|
197
|
+
background: "var(--srte-modal-backdrop)",
|
|
198
|
+
display: "flex",
|
|
199
|
+
alignItems: "center",
|
|
200
|
+
justifyContent: "center",
|
|
201
|
+
zIndex: 90,
|
|
202
|
+
}, onClick: () => setInfoItem(null), children: _jsxs("div", { style: {
|
|
203
|
+
width: 420,
|
|
204
|
+
maxWidth: "90vw",
|
|
205
|
+
background: "var(--srte-modal-bg)",
|
|
206
|
+
color: "var(--srte-modal-text)",
|
|
207
|
+
borderRadius: 10,
|
|
208
|
+
boxShadow: "var(--srte-menu-shadow)",
|
|
209
|
+
padding: 16,
|
|
210
|
+
}, onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: 12, marginBottom: 12 }, children: [_jsx("div", { style: { fontWeight: 600 }, children: "Image info" }), _jsx("button", { type: "button", onClick: () => setInfoItem(null), children: "\u2715" })] }), _jsx("img", { src: infoItem.url, alt: infoItem.alt || "", style: { maxWidth: "100%", maxHeight: 180, display: "block", margin: "0 auto 12px", borderRadius: 8 } }), [
|
|
211
|
+
["Title", infoItem.title],
|
|
212
|
+
["Alt text", infoItem.alt],
|
|
213
|
+
["Dimensions", infoItem.width && infoItem.height ? `${infoItem.width}×${infoItem.height}` : undefined],
|
|
214
|
+
["MIME type", infoItem.mimeType],
|
|
215
|
+
["Size", infoItem.sizeBytes ? `${Math.round(infoItem.sizeBytes / 1024)} KB` : undefined],
|
|
216
|
+
["Created", infoItem.createdAt],
|
|
217
|
+
["Tags", infoItem.tags?.join(", ")],
|
|
218
|
+
["Work", infoItem.license?.workName],
|
|
219
|
+
["Author", infoItem.license?.author],
|
|
220
|
+
["License", [infoItem.license?.licenseType, infoItem.license?.licenseText].filter(Boolean).join(" - ")],
|
|
221
|
+
["Source", infoItem.license?.sourceUrl],
|
|
222
|
+
].filter(([, value]) => value).map(([label, value]) => (_jsxs("div", { style: { display: "grid", gridTemplateColumns: "92px 1fr", gap: 8, fontSize: 12, marginBottom: 6 }, children: [_jsx("div", { style: { color: "var(--srte-text-muted)" }, children: label }), _jsx("div", { style: { overflowWrap: "anywhere" }, children: value })] }, label)))] }) }))] }) }));
|
|
172
223
|
}
|
package/dist/theme.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type SrteTheme = 'light' | 'dark';
|
|
2
|
-
export declare const SRTE_DEFAULT_CSS = "\n.srte-editor {\n --srte-bg: #ffffff;\n --srte-text: #111111;\n --srte-text-muted: #4b5563;\n --srte-border: #dddddd;\n --srte-border-light: #eeeeee;\n --srte-toolbar-bg: #ffffff;\n --srte-input-bg: #ffffff;\n --srte-input-text: #111111;\n --srte-input-border: #e5e7eb;\n --srte-modal-backdrop: rgba(0, 0, 0, 0.35);\n --srte-modal-bg: #ffffff;\n --srte-modal-text: #000000;\n --srte-menu-bg: #ffffff;\n --srte-menu-text: #111111;\n --srte-menu-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n --srte-accent: #1e90ff;\n --srte-accent-bg: rgba(30, 144, 255, 0.15);\n --srte-danger: #dc2626;\n --srte-primary: #2563eb;\n --srte-surface-subtle: #f3f4f6;\n --srte-on-primary: #ffffff;\n --srte-cancel-bg: #f3f4f6;\n}\n.srte-editor.srte-dark {\n --srte-bg: #1e1e1e;\n --srte-text: #e0e0e0;\n --srte-text-muted: #9ca3af;\n --srte-border: #3a3a3a;\n --srte-border-light: #2e2e2e;\n --srte-toolbar-bg: #252525;\n --srte-input-bg: #2a2a2a;\n --srte-input-text: #e0e0e0;\n --srte-input-border: #444444;\n --srte-modal-backdrop: rgba(0, 0, 0, 0.6);\n --srte-modal-bg: #252525;\n --srte-modal-text: #e0e0e0;\n --srte-menu-bg: #2a2a2a;\n --srte-menu-text: #e0e0e0;\n --srte-menu-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);\n --srte-accent: #3b9eff;\n --srte-accent-bg: rgba(59, 158, 255, 0.2);\n --srte-danger: #ef4444;\n --srte-primary: #3b82f6;\n --srte-surface-subtle: #333333;\n --srte-on-primary: #ffffff;\n --srte-cancel-bg: #333333;\n}\n";
|
|
2
|
+
export declare const SRTE_DEFAULT_CSS = "\n.srte-editor {\n --srte-bg: #ffffff;\n --srte-text: #111111;\n --srte-text-muted: #4b5563;\n --srte-border: #dddddd;\n --srte-border-light: #eeeeee;\n --srte-toolbar-bg: #ffffff;\n --srte-input-bg: #ffffff;\n --srte-input-text: #111111;\n --srte-input-border: #e5e7eb;\n --srte-modal-backdrop: rgba(0, 0, 0, 0.35);\n --srte-modal-bg: #ffffff;\n --srte-modal-text: #000000;\n --srte-menu-bg: #ffffff;\n --srte-menu-text: #111111;\n --srte-menu-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n --srte-accent: #1e90ff;\n --srte-accent-bg: rgba(30, 144, 255, 0.15);\n --srte-danger: #dc2626;\n --srte-primary: #2563eb;\n --srte-surface-subtle: #f3f4f6;\n --srte-on-primary: #ffffff;\n --srte-cancel-bg: #f3f4f6;\n}\n.srte-editor.srte-dark {\n --srte-bg: #1e1e1e;\n --srte-text: #e0e0e0;\n --srte-text-muted: #9ca3af;\n --srte-border: #3a3a3a;\n --srte-border-light: #2e2e2e;\n --srte-toolbar-bg: #252525;\n --srte-input-bg: #2a2a2a;\n --srte-input-text: #e0e0e0;\n --srte-input-border: #444444;\n --srte-modal-backdrop: rgba(0, 0, 0, 0.6);\n --srte-modal-bg: #252525;\n --srte-modal-text: #e0e0e0;\n --srte-menu-bg: #2a2a2a;\n --srte-menu-text: #e0e0e0;\n --srte-menu-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);\n --srte-accent: #3b9eff;\n --srte-accent-bg: rgba(59, 158, 255, 0.2);\n --srte-danger: #ef4444;\n --srte-primary: #3b82f6;\n --srte-surface-subtle: #333333;\n --srte-on-primary: #ffffff;\n --srte-cancel-bg: #333333;\n}\n.srte-editor [contenteditable] blockquote {\n border-left: 4px solid var(--srte-accent);\n margin: 0.75em 0;\n padding: 0.5em 1em;\n background: var(--srte-surface-subtle);\n color: var(--srte-text);\n}\n.srte-editor.srte-dark [contenteditable] [style*=\"color\"]:not(.srte-preserve-colors):not(.srte-preserve-colors *),\n.srte-editor.srte-dark [contenteditable] [style*=\"background\"]:not(.srte-preserve-colors):not(.srte-preserve-colors *) {\n color: var(--srte-text) !important;\n background: transparent !important;\n background-color: transparent !important;\n}\n.srte-editor [contenteditable] sub,\n.srte-editor [contenteditable] sup {\n line-height: 0;\n}\n";
|
|
3
3
|
export declare function ensureStyleSheet(): void;
|
package/dist/theme.js
CHANGED
|
@@ -47,6 +47,23 @@ export const SRTE_DEFAULT_CSS = `
|
|
|
47
47
|
--srte-on-primary: #ffffff;
|
|
48
48
|
--srte-cancel-bg: #333333;
|
|
49
49
|
}
|
|
50
|
+
.srte-editor [contenteditable] blockquote {
|
|
51
|
+
border-left: 4px solid var(--srte-accent);
|
|
52
|
+
margin: 0.75em 0;
|
|
53
|
+
padding: 0.5em 1em;
|
|
54
|
+
background: var(--srte-surface-subtle);
|
|
55
|
+
color: var(--srte-text);
|
|
56
|
+
}
|
|
57
|
+
.srte-editor.srte-dark [contenteditable] [style*="color"]:not(.srte-preserve-colors):not(.srte-preserve-colors *),
|
|
58
|
+
.srte-editor.srte-dark [contenteditable] [style*="background"]:not(.srte-preserve-colors):not(.srte-preserve-colors *) {
|
|
59
|
+
color: var(--srte-text) !important;
|
|
60
|
+
background: transparent !important;
|
|
61
|
+
background-color: transparent !important;
|
|
62
|
+
}
|
|
63
|
+
.srte-editor [contenteditable] sub,
|
|
64
|
+
.srte-editor [contenteditable] sup {
|
|
65
|
+
line-height: 0;
|
|
66
|
+
}
|
|
50
67
|
`;
|
|
51
68
|
const SRTE_STYLE_ID = 'srte-theme-defaults';
|
|
52
69
|
export function ensureStyleSheet() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smartrte-react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "A powerful, feature-rich Rich Text Editor for React with support for tables, mathematical formulas (LaTeX/KaTeX), and media management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,6 +38,16 @@
|
|
|
38
38
|
},
|
|
39
39
|
"author": "Smart RTE Contributors",
|
|
40
40
|
"license": "MIT",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.json",
|
|
43
|
+
"prepublishOnly": "pnpm run build",
|
|
44
|
+
"dev": "pnpm build",
|
|
45
|
+
"lint": "eslint . || true",
|
|
46
|
+
"test": "vitest run || true",
|
|
47
|
+
"storybook": "storybook dev -p 6006",
|
|
48
|
+
"build-storybook": "storybook build",
|
|
49
|
+
"e2e": "playwright test || true"
|
|
50
|
+
},
|
|
41
51
|
"publishConfig": {
|
|
42
52
|
"access": "public"
|
|
43
53
|
},
|
|
@@ -62,16 +72,8 @@
|
|
|
62
72
|
"vitest": "^2.1.4"
|
|
63
73
|
},
|
|
64
74
|
"dependencies": {
|
|
75
|
+
"jszip": "^3.10.1",
|
|
65
76
|
"mammoth": "^1.11.0",
|
|
66
77
|
"pdfjs-dist": "^5.4.530"
|
|
67
|
-
},
|
|
68
|
-
"scripts": {
|
|
69
|
-
"build": "tsc -p tsconfig.json",
|
|
70
|
-
"dev": "pnpm build",
|
|
71
|
-
"lint": "eslint . || true",
|
|
72
|
-
"test": "vitest run || true",
|
|
73
|
-
"storybook": "storybook dev -p 6006",
|
|
74
|
-
"build-storybook": "storybook build",
|
|
75
|
-
"e2e": "playwright test || true"
|
|
76
78
|
}
|
|
77
|
-
}
|
|
79
|
+
}
|