smartrte-react 0.2.1 → 0.2.3
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 +1081 -108
- 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
|
@@ -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
|
}
|
|
@@ -586,6 +669,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
586
669
|
let fullHtml = '';
|
|
587
670
|
for (let i = 1; i <= pdf.numPages; i++) {
|
|
588
671
|
const page = await pdf.getPage(i);
|
|
672
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
589
673
|
const textContent = await page.getTextContent();
|
|
590
674
|
const styles = textContent.styles;
|
|
591
675
|
// 1. Group items into lines
|
|
@@ -657,8 +741,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
657
741
|
const width = item.width;
|
|
658
742
|
const fontName = item.fontName;
|
|
659
743
|
const fontObj = styles[fontName];
|
|
660
|
-
const
|
|
661
|
-
|
|
744
|
+
const fontFamily = fontObj?.fontFamily?.toLowerCase() || '';
|
|
745
|
+
const isBold = fontFamily.includes('bold') || false;
|
|
746
|
+
const isItalic = fontFamily.includes('italic') || fontFamily.includes('oblique');
|
|
747
|
+
const fontSize = Math.max(8, Math.round(Math.abs(item.transform[3])));
|
|
662
748
|
if (lastX > 0) {
|
|
663
749
|
const gap = x - lastX;
|
|
664
750
|
if (gap > 2) { // Minimal space threshold
|
|
@@ -677,10 +763,12 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
677
763
|
itemXs.push(x);
|
|
678
764
|
}
|
|
679
765
|
// Append text style
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
766
|
+
const chunkStyle = cssRules([
|
|
767
|
+
['font-size', `${fontSize}px`],
|
|
768
|
+
['font-weight', isBold ? '700' : ''],
|
|
769
|
+
['font-style', isItalic ? 'italic' : ''],
|
|
770
|
+
]);
|
|
771
|
+
let chunk = `<span${styleAttr(chunkStyle)}>${escapeHtml(item.str)}</span>`;
|
|
684
772
|
lineText += item.str;
|
|
685
773
|
lineHtmlContent += chunk;
|
|
686
774
|
lastX = x + width;
|
|
@@ -751,8 +839,15 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
751
839
|
const w = item.width;
|
|
752
840
|
const txt = item.str;
|
|
753
841
|
const fontObj = styles[item.fontName];
|
|
754
|
-
const
|
|
755
|
-
const
|
|
842
|
+
const fontFamily = fontObj?.fontFamily?.toLowerCase() || '';
|
|
843
|
+
const isBold = fontFamily.includes('bold');
|
|
844
|
+
const isItalic = fontFamily.includes('italic') || fontFamily.includes('oblique');
|
|
845
|
+
const fontSize = Math.max(8, Math.round(Math.abs(item.transform[3])));
|
|
846
|
+
const styledTxt = `<span${styleAttr(cssRules([
|
|
847
|
+
['font-size', `${fontSize}px`],
|
|
848
|
+
['font-weight', isBold ? '700' : ''],
|
|
849
|
+
['font-style', isItalic ? 'italic' : ''],
|
|
850
|
+
]))}>${escapeHtml(txt)}</span>`;
|
|
756
851
|
// Decide which column this belongs to
|
|
757
852
|
// Find closest column to the left (or close enough)
|
|
758
853
|
let colIdx = 0;
|
|
@@ -794,10 +889,23 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
794
889
|
closeList();
|
|
795
890
|
if (isHeader) {
|
|
796
891
|
const tag = maxH > medianHeight * 1.5 ? 'h2' : 'h3';
|
|
797
|
-
|
|
892
|
+
const firstX = line.items[0]?.transform?.[4] || 0;
|
|
893
|
+
const lastItem = line.items[line.items.length - 1];
|
|
894
|
+
const lastRight = lastItem ? lastItem.transform[4] + lastItem.width : firstX;
|
|
895
|
+
const center = (firstX + lastRight) / 2;
|
|
896
|
+
const align = Math.abs(center - viewport.width / 2) < viewport.width * 0.12 ? 'center' : firstX > viewport.width * 0.55 ? 'right' : '';
|
|
897
|
+
html += `<${tag}${styleAttr(cssRules([['text-align', align]]))}>${lineHtmlContent}</${tag}>`;
|
|
798
898
|
}
|
|
799
899
|
else {
|
|
800
|
-
|
|
900
|
+
const firstX = line.items[0]?.transform?.[4] || 0;
|
|
901
|
+
const lastItem = line.items[line.items.length - 1];
|
|
902
|
+
const lastRight = lastItem ? lastItem.transform[4] + lastItem.width : firstX;
|
|
903
|
+
const center = (firstX + lastRight) / 2;
|
|
904
|
+
const align = Math.abs(center - viewport.width / 2) < viewport.width * 0.12 ? 'center' : firstX > viewport.width * 0.55 ? 'right' : '';
|
|
905
|
+
html += `<p${styleAttr(cssRules([
|
|
906
|
+
['text-align', align],
|
|
907
|
+
['margin-left', firstX > 40 && !align ? `${Math.round(firstX)}px` : ''],
|
|
908
|
+
]))}>${lineHtmlContent}</p>`;
|
|
801
909
|
}
|
|
802
910
|
}
|
|
803
911
|
}
|
|
@@ -806,30 +914,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
806
914
|
closeTable();
|
|
807
915
|
fullHtml += html;
|
|
808
916
|
}
|
|
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
|
-
}
|
|
917
|
+
insertImportedHtml(fullHtml, mode, { preserveColors: true, preserveDocumentLayout: true });
|
|
833
918
|
}
|
|
834
919
|
catch (error) {
|
|
835
920
|
console.error('Error reading PDF:', error);
|
|
@@ -859,55 +944,14 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
859
944
|
try {
|
|
860
945
|
setLoadingDocx(true);
|
|
861
946
|
const arrayBuffer = await file.arrayBuffer();
|
|
862
|
-
const
|
|
863
|
-
|
|
947
|
+
const html = preserveDocxStyles
|
|
948
|
+
? await convertDocxToStyledHtml(arrayBuffer)
|
|
949
|
+
: await convertDocxWithMammoth(arrayBuffer);
|
|
864
950
|
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
|
-
});
|
|
951
|
+
insertImportedHtml(`<div class="srte-preserve-colors">${html}</div>`, mode, {
|
|
952
|
+
preserveColors: preserveDocxStyles,
|
|
953
|
+
preserveDocumentLayout: preserveDocxStyles,
|
|
879
954
|
});
|
|
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
955
|
}
|
|
912
956
|
}
|
|
913
957
|
catch (error) {
|
|
@@ -917,6 +961,578 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
917
961
|
setLoadingDocx(false);
|
|
918
962
|
}
|
|
919
963
|
};
|
|
964
|
+
const convertDocxWithMammoth = async (arrayBuffer) => {
|
|
965
|
+
const result = await mammoth.convertToHtml({ arrayBuffer });
|
|
966
|
+
const temp = document.createElement('div');
|
|
967
|
+
temp.innerHTML = result.value;
|
|
968
|
+
enhanceImportedTables(temp);
|
|
969
|
+
return temp.innerHTML;
|
|
970
|
+
};
|
|
971
|
+
const convertDocxToStyledHtml = async (arrayBuffer) => {
|
|
972
|
+
try {
|
|
973
|
+
const zip = await JSZip.loadAsync(arrayBuffer);
|
|
974
|
+
const documentXml = await zip.file('word/document.xml')?.async('text');
|
|
975
|
+
if (!documentXml)
|
|
976
|
+
return convertDocxWithMammoth(arrayBuffer);
|
|
977
|
+
const parser = new DOMParser();
|
|
978
|
+
const doc = parser.parseFromString(documentXml, 'application/xml');
|
|
979
|
+
if (doc.querySelector('parsererror'))
|
|
980
|
+
return convertDocxWithMammoth(arrayBuffer);
|
|
981
|
+
const body = Array.from(doc.getElementsByTagName('*')).find((node) => node.localName === 'body');
|
|
982
|
+
if (!body)
|
|
983
|
+
return convertDocxWithMammoth(arrayBuffer);
|
|
984
|
+
const html = directChildren(body)
|
|
985
|
+
.filter((node) => node.localName !== 'sectPr')
|
|
986
|
+
.map((node) => {
|
|
987
|
+
if (node.localName === 'p')
|
|
988
|
+
return convertDocxParagraph(node);
|
|
989
|
+
if (node.localName === 'tbl')
|
|
990
|
+
return convertDocxTable(node);
|
|
991
|
+
return '';
|
|
992
|
+
})
|
|
993
|
+
.join('');
|
|
994
|
+
if (!html.trim())
|
|
995
|
+
return convertDocxWithMammoth(arrayBuffer);
|
|
996
|
+
const temp = document.createElement('div');
|
|
997
|
+
temp.innerHTML = html;
|
|
998
|
+
enhanceImportedTables(temp);
|
|
999
|
+
return temp.innerHTML;
|
|
1000
|
+
}
|
|
1001
|
+
catch (error) {
|
|
1002
|
+
console.warn('Falling back to Mammoth DOCX import:', error);
|
|
1003
|
+
return convertDocxWithMammoth(arrayBuffer);
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
const directChildren = (node) => Array.from(node.children);
|
|
1007
|
+
const firstChildByName = (node, localName) => node
|
|
1008
|
+
? directChildren(node).find((child) => child.localName === localName)
|
|
1009
|
+
: undefined;
|
|
1010
|
+
const childrenByName = (node, localName) => node
|
|
1011
|
+
? directChildren(node).filter((child) => child.localName === localName)
|
|
1012
|
+
: [];
|
|
1013
|
+
const docxAttr = (node, name) => {
|
|
1014
|
+
if (!node)
|
|
1015
|
+
return '';
|
|
1016
|
+
return (node.getAttribute(`w:${name}`) ||
|
|
1017
|
+
node.getAttribute(name) ||
|
|
1018
|
+
node.getAttributeNS('http://schemas.openxmlformats.org/wordprocessingml/2006/main', name) ||
|
|
1019
|
+
'');
|
|
1020
|
+
};
|
|
1021
|
+
const docxHexColor = (value) => {
|
|
1022
|
+
if (!value || value.toLowerCase() === 'auto')
|
|
1023
|
+
return '';
|
|
1024
|
+
const normalized = value.replace(/[^0-9a-f]/gi, '');
|
|
1025
|
+
return normalized.length === 6 ? `#${normalized}` : '';
|
|
1026
|
+
};
|
|
1027
|
+
const twipsToPt = (value) => {
|
|
1028
|
+
const n = Number(value);
|
|
1029
|
+
return Number.isFinite(n) ? `${Math.max(n / 20, 0)}pt` : '';
|
|
1030
|
+
};
|
|
1031
|
+
const halfPointsToPt = (value) => {
|
|
1032
|
+
const n = Number(value);
|
|
1033
|
+
return Number.isFinite(n) ? `${Math.max(n / 2, 1)}pt` : '';
|
|
1034
|
+
};
|
|
1035
|
+
const cssRules = (rules) => rules
|
|
1036
|
+
.filter(([, value]) => Boolean(value))
|
|
1037
|
+
.map(([name, value]) => `${name}: ${value}`)
|
|
1038
|
+
.join('; ');
|
|
1039
|
+
const styleAttr = (style) => (style ? ` style="${escapeHtml(style)}"` : '');
|
|
1040
|
+
const convertDocxParagraphStyle = (paragraph) => {
|
|
1041
|
+
const pPr = firstChildByName(paragraph, 'pPr');
|
|
1042
|
+
if (!pPr)
|
|
1043
|
+
return '';
|
|
1044
|
+
const spacing = firstChildByName(pPr, 'spacing');
|
|
1045
|
+
const jc = firstChildByName(pPr, 'jc');
|
|
1046
|
+
const indent = firstChildByName(pPr, 'ind');
|
|
1047
|
+
const borderBottom = firstChildByName(firstChildByName(pPr, 'pBdr'), 'bottom');
|
|
1048
|
+
const line = docxAttr(spacing, 'line');
|
|
1049
|
+
const lineRule = docxAttr(spacing, 'lineRule');
|
|
1050
|
+
return cssRules([
|
|
1051
|
+
['text-align', docxAttr(jc, 'val')],
|
|
1052
|
+
['margin-top', twipsToPt(docxAttr(spacing, 'before'))],
|
|
1053
|
+
['margin-bottom', twipsToPt(docxAttr(spacing, 'after'))],
|
|
1054
|
+
['margin-left', twipsToPt(docxAttr(indent, 'left'))],
|
|
1055
|
+
['text-indent', twipsToPt(docxAttr(indent, 'firstLine'))],
|
|
1056
|
+
['line-height', line && lineRule === 'auto' ? `${Number(line) / 240}` : ''],
|
|
1057
|
+
['border-bottom', docxBorderCss(borderBottom)],
|
|
1058
|
+
]);
|
|
1059
|
+
};
|
|
1060
|
+
const convertDocxRunStyle = (run) => {
|
|
1061
|
+
const rPr = firstChildByName(run, 'rPr');
|
|
1062
|
+
if (!rPr)
|
|
1063
|
+
return '';
|
|
1064
|
+
const color = docxHexColor(docxAttr(firstChildByName(rPr, 'color'), 'val'));
|
|
1065
|
+
const highlight = docxHexColor(docxAttr(firstChildByName(rPr, 'highlight'), 'val'));
|
|
1066
|
+
const shade = docxHexColor(docxAttr(firstChildByName(rPr, 'shd'), 'fill'));
|
|
1067
|
+
const size = halfPointsToPt(docxAttr(firstChildByName(rPr, 'sz'), 'val'));
|
|
1068
|
+
const underline = firstChildByName(rPr, 'u');
|
|
1069
|
+
return cssRules([
|
|
1070
|
+
['font-weight', firstChildByName(rPr, 'b') ? '700' : ''],
|
|
1071
|
+
['font-style', firstChildByName(rPr, 'i') ? 'italic' : ''],
|
|
1072
|
+
['text-decoration', underline ? 'underline' : ''],
|
|
1073
|
+
['color', color],
|
|
1074
|
+
['background-color', highlight || shade],
|
|
1075
|
+
['font-size', size],
|
|
1076
|
+
]);
|
|
1077
|
+
};
|
|
1078
|
+
const convertDocxRun = (run) => {
|
|
1079
|
+
const rPr = firstChildByName(run, 'rPr');
|
|
1080
|
+
const vertAlign = docxAttr(firstChildByName(rPr, 'vertAlign'), 'val');
|
|
1081
|
+
const style = convertDocxRunStyle(run);
|
|
1082
|
+
const content = directChildren(run)
|
|
1083
|
+
.map((child) => {
|
|
1084
|
+
if (child.localName === 't')
|
|
1085
|
+
return escapeHtml(child.textContent || '');
|
|
1086
|
+
if (child.localName === 'tab')
|
|
1087
|
+
return ' ';
|
|
1088
|
+
if (child.localName === 'br') {
|
|
1089
|
+
return docxAttr(child, 'type') === 'page'
|
|
1090
|
+
? '<hr class="srte-docx-page-break">'
|
|
1091
|
+
: '<br>';
|
|
1092
|
+
}
|
|
1093
|
+
return '';
|
|
1094
|
+
})
|
|
1095
|
+
.join('');
|
|
1096
|
+
if (!content)
|
|
1097
|
+
return '';
|
|
1098
|
+
const tag = vertAlign === 'superscript' ? 'sup' : vertAlign === 'subscript' ? 'sub' : 'span';
|
|
1099
|
+
return `<${tag}${styleAttr(style)}>${content}</${tag}>`;
|
|
1100
|
+
};
|
|
1101
|
+
const convertDocxParagraph = (paragraph) => {
|
|
1102
|
+
const style = convertDocxParagraphStyle(paragraph);
|
|
1103
|
+
const content = childrenByName(paragraph, 'r').map(convertDocxRun).join('');
|
|
1104
|
+
return `<p${styleAttr(style)}>${content || '<br>'}</p>`;
|
|
1105
|
+
};
|
|
1106
|
+
const docxBorderCss = (border) => {
|
|
1107
|
+
if (!border)
|
|
1108
|
+
return '';
|
|
1109
|
+
const val = docxAttr(border, 'val');
|
|
1110
|
+
if (!val || val === 'nil' || val === 'none')
|
|
1111
|
+
return '';
|
|
1112
|
+
const size = Number(docxAttr(border, 'sz')) || 4;
|
|
1113
|
+
const width = Math.max(size / 8, 0.5);
|
|
1114
|
+
const color = docxHexColor(docxAttr(border, 'color')) || '#d1d5db';
|
|
1115
|
+
return `${width}px solid ${color}`;
|
|
1116
|
+
};
|
|
1117
|
+
const convertDocxCellStyle = (cell) => {
|
|
1118
|
+
const tcPr = firstChildByName(cell, 'tcPr');
|
|
1119
|
+
const width = twipsToPt(docxAttr(firstChildByName(tcPr, 'tcW'), 'w'));
|
|
1120
|
+
const shade = docxHexColor(docxAttr(firstChildByName(tcPr, 'shd'), 'fill'));
|
|
1121
|
+
const borders = firstChildByName(tcPr, 'tcBorders');
|
|
1122
|
+
const top = docxBorderCss(firstChildByName(borders, 'top'));
|
|
1123
|
+
const right = docxBorderCss(firstChildByName(borders, 'right'));
|
|
1124
|
+
const bottom = docxBorderCss(firstChildByName(borders, 'bottom'));
|
|
1125
|
+
const left = docxBorderCss(firstChildByName(borders, 'left'));
|
|
1126
|
+
return cssRules([
|
|
1127
|
+
['width', width],
|
|
1128
|
+
['background-color', shade],
|
|
1129
|
+
['border-top', top],
|
|
1130
|
+
['border-right', right],
|
|
1131
|
+
['border-bottom', bottom],
|
|
1132
|
+
['border-left', left],
|
|
1133
|
+
['padding', '8px'],
|
|
1134
|
+
['vertical-align', 'top'],
|
|
1135
|
+
]);
|
|
1136
|
+
};
|
|
1137
|
+
const convertDocxTable = (table) => {
|
|
1138
|
+
const rows = childrenByName(table, 'tr')
|
|
1139
|
+
.map((row) => {
|
|
1140
|
+
const cells = childrenByName(row, 'tc')
|
|
1141
|
+
.map((cell) => {
|
|
1142
|
+
const content = directChildren(cell)
|
|
1143
|
+
.filter((child) => child.localName === 'p' || child.localName === 'tbl')
|
|
1144
|
+
.map((child) => child.localName === 'p' ? convertDocxParagraph(child) : convertDocxTable(child))
|
|
1145
|
+
.join('');
|
|
1146
|
+
return `<td${styleAttr(convertDocxCellStyle(cell))}>${content || '<p><br></p>'}</td>`;
|
|
1147
|
+
})
|
|
1148
|
+
.join('');
|
|
1149
|
+
return `<tr>${cells}</tr>`;
|
|
1150
|
+
})
|
|
1151
|
+
.join('');
|
|
1152
|
+
return `<table style="border-collapse: collapse; width: 100%; margin: 12px 0;"><tbody>${rows}</tbody></table>`;
|
|
1153
|
+
};
|
|
1154
|
+
const enhanceImportedTables = (root) => {
|
|
1155
|
+
const tables = root.querySelectorAll('table');
|
|
1156
|
+
tables.forEach(tbl => {
|
|
1157
|
+
tbl.style.borderCollapse = tbl.style.borderCollapse || 'collapse';
|
|
1158
|
+
tbl.style.width = tbl.style.width || '100%';
|
|
1159
|
+
const cells = tbl.querySelectorAll('td, th');
|
|
1160
|
+
cells.forEach(cell => {
|
|
1161
|
+
const el = cell;
|
|
1162
|
+
if (!el.style.border && !el.style.borderTop && !el.style.borderRight && !el.style.borderBottom && !el.style.borderLeft) {
|
|
1163
|
+
el.style.border = '1px solid #d1d5db';
|
|
1164
|
+
}
|
|
1165
|
+
el.style.padding = el.style.padding || '8px';
|
|
1166
|
+
el.style.verticalAlign = el.style.verticalAlign || 'top';
|
|
1167
|
+
});
|
|
1168
|
+
});
|
|
1169
|
+
};
|
|
1170
|
+
const escapeHtml = (value) => value
|
|
1171
|
+
.replace(/&/g, "&")
|
|
1172
|
+
.replace(/</g, "<")
|
|
1173
|
+
.replace(/>/g, ">");
|
|
1174
|
+
const escapeHtmlAttribute = (value) => escapeHtml(value).replace(/"/g, """);
|
|
1175
|
+
const markdownToHtml = (markdown) => {
|
|
1176
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
1177
|
+
let html = "";
|
|
1178
|
+
let listType = null;
|
|
1179
|
+
let paragraph = [];
|
|
1180
|
+
let codeFence = null;
|
|
1181
|
+
const closeList = () => {
|
|
1182
|
+
if (listType) {
|
|
1183
|
+
html += `</${listType}>`;
|
|
1184
|
+
listType = null;
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
const inline = (text) => {
|
|
1188
|
+
const codeTokens = [];
|
|
1189
|
+
let value = text.replace(/`([^`]+)`/g, (_match, code) => {
|
|
1190
|
+
const token = `@@SRTE_CODE_${codeTokens.length}@@`;
|
|
1191
|
+
codeTokens.push(`<code>${escapeHtml(code)}</code>`);
|
|
1192
|
+
return token;
|
|
1193
|
+
});
|
|
1194
|
+
value = escapeHtml(value)
|
|
1195
|
+
.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/g, (_match, alt, src, title) => {
|
|
1196
|
+
const titleAttr = title ? ` title="${escapeHtmlAttribute(title)}"` : "";
|
|
1197
|
+
return `<img src="${escapeHtmlAttribute(src)}" alt="${escapeHtmlAttribute(alt)}"${titleAttr}>`;
|
|
1198
|
+
})
|
|
1199
|
+
.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/g, (_match, label, href, title) => {
|
|
1200
|
+
const titleAttr = title ? ` title="${escapeHtmlAttribute(title)}"` : "";
|
|
1201
|
+
return `<a href="${escapeHtmlAttribute(href)}"${titleAttr}>${label}</a>`;
|
|
1202
|
+
})
|
|
1203
|
+
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
|
|
1204
|
+
.replace(/__([^_]+)__/g, "<strong>$1</strong>")
|
|
1205
|
+
.replace(/~~([^~]+)~~/g, "<s>$1</s>")
|
|
1206
|
+
.replace(/(^|[^*])\*([^*\n]+)\*/g, "$1<em>$2</em>")
|
|
1207
|
+
.replace(/(^|[^_])_([^_\n]+)_/g, "$1<em>$2</em>");
|
|
1208
|
+
codeTokens.forEach((replacement, index) => {
|
|
1209
|
+
value = value.replace(`@@SRTE_CODE_${index}@@`, replacement);
|
|
1210
|
+
});
|
|
1211
|
+
return value;
|
|
1212
|
+
};
|
|
1213
|
+
const closeParagraph = () => {
|
|
1214
|
+
if (!paragraph.length)
|
|
1215
|
+
return;
|
|
1216
|
+
html += `<p>${inline(paragraph.join(" "))}</p>`;
|
|
1217
|
+
paragraph = [];
|
|
1218
|
+
};
|
|
1219
|
+
const isTableSeparator = (line) => /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line);
|
|
1220
|
+
const parseTableRow = (line) => {
|
|
1221
|
+
let value = line.trim();
|
|
1222
|
+
if (value.startsWith("|"))
|
|
1223
|
+
value = value.slice(1);
|
|
1224
|
+
if (value.endsWith("|"))
|
|
1225
|
+
value = value.slice(0, -1);
|
|
1226
|
+
return value.split("|").map((cell) => cell.trim());
|
|
1227
|
+
};
|
|
1228
|
+
const renderTable = (startIndex) => {
|
|
1229
|
+
const header = parseTableRow(lines[startIndex]);
|
|
1230
|
+
let index = startIndex + 2;
|
|
1231
|
+
const rows = [];
|
|
1232
|
+
while (index < lines.length && lines[index].includes("|") && lines[index].trim()) {
|
|
1233
|
+
rows.push(parseTableRow(lines[index]));
|
|
1234
|
+
index += 1;
|
|
1235
|
+
}
|
|
1236
|
+
const headHtml = `<thead><tr>${header.map((cell) => `<th>${inline(cell)}</th>`).join("")}</tr></thead>`;
|
|
1237
|
+
const bodyHtml = rows.length
|
|
1238
|
+
? `<tbody>${rows.map((row) => `<tr>${header.map((_cell, cellIndex) => `<td>${inline(row[cellIndex] || "")}</td>`).join("")}</tr>`).join("")}</tbody>`
|
|
1239
|
+
: "";
|
|
1240
|
+
html += `<table style="border-collapse: collapse; width: 100%; margin: 12px 0;">${headHtml}${bodyHtml}</table>`;
|
|
1241
|
+
return index;
|
|
1242
|
+
};
|
|
1243
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
1244
|
+
const line = lines[i];
|
|
1245
|
+
const trimmed = line.trim();
|
|
1246
|
+
const fence = /^```([A-Za-z0-9_-]+)?\s*$/.exec(trimmed);
|
|
1247
|
+
if (fence) {
|
|
1248
|
+
closeParagraph();
|
|
1249
|
+
closeList();
|
|
1250
|
+
if (codeFence) {
|
|
1251
|
+
const langClass = codeFence.lang ? ` class="language-${escapeHtmlAttribute(codeFence.lang)}"` : "";
|
|
1252
|
+
html += `<pre><code${langClass}>${escapeHtml(codeFence.lines.join("\n"))}</code></pre>`;
|
|
1253
|
+
codeFence = null;
|
|
1254
|
+
}
|
|
1255
|
+
else {
|
|
1256
|
+
codeFence = { lang: fence[1] || "", lines: [] };
|
|
1257
|
+
}
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
if (codeFence) {
|
|
1261
|
+
codeFence.lines.push(line);
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
if (!trimmed) {
|
|
1265
|
+
closeParagraph();
|
|
1266
|
+
closeList();
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
|
|
1270
|
+
closeParagraph();
|
|
1271
|
+
closeList();
|
|
1272
|
+
html += "<hr>";
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
if (i + 1 < lines.length && trimmed.includes("|") && isTableSeparator(lines[i + 1])) {
|
|
1276
|
+
closeParagraph();
|
|
1277
|
+
closeList();
|
|
1278
|
+
i = renderTable(i) - 1;
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
const heading = /^(#{1,6})\s+(.+)$/.exec(trimmed);
|
|
1282
|
+
if (heading) {
|
|
1283
|
+
closeParagraph();
|
|
1284
|
+
closeList();
|
|
1285
|
+
const level = heading[1].length;
|
|
1286
|
+
html += `<h${level}>${inline(heading[2])}</h${level}>`;
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
const bullet = /^[-*+]\s+(.+)$/.exec(trimmed);
|
|
1290
|
+
if (bullet) {
|
|
1291
|
+
closeParagraph();
|
|
1292
|
+
if (listType !== "ul") {
|
|
1293
|
+
closeList();
|
|
1294
|
+
html += "<ul>";
|
|
1295
|
+
listType = "ul";
|
|
1296
|
+
}
|
|
1297
|
+
html += `<li>${inline(bullet[1])}</li>`;
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
const numbered = /^\d+[.)]\s+(.+)$/.exec(trimmed);
|
|
1301
|
+
if (numbered) {
|
|
1302
|
+
closeParagraph();
|
|
1303
|
+
if (listType !== "ol") {
|
|
1304
|
+
closeList();
|
|
1305
|
+
html += "<ol>";
|
|
1306
|
+
listType = "ol";
|
|
1307
|
+
}
|
|
1308
|
+
html += `<li>${inline(numbered[1])}</li>`;
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
const quote = /^>\s?(.*)$/.exec(trimmed);
|
|
1312
|
+
if (quote) {
|
|
1313
|
+
closeParagraph();
|
|
1314
|
+
closeList();
|
|
1315
|
+
html += `<blockquote>${inline(quote[1]) || "<br>"}</blockquote>`;
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
closeList();
|
|
1319
|
+
paragraph.push(trimmed);
|
|
1320
|
+
}
|
|
1321
|
+
if (codeFence) {
|
|
1322
|
+
const langClass = codeFence.lang ? ` class="language-${escapeHtmlAttribute(codeFence.lang)}"` : "";
|
|
1323
|
+
html += `<pre><code${langClass}>${escapeHtml(codeFence.lines.join("\n"))}</code></pre>`;
|
|
1324
|
+
}
|
|
1325
|
+
closeParagraph();
|
|
1326
|
+
closeList();
|
|
1327
|
+
const root = document.createElement("div");
|
|
1328
|
+
root.innerHTML = html;
|
|
1329
|
+
enhanceImportedTables(root);
|
|
1330
|
+
return root.innerHTML;
|
|
1331
|
+
};
|
|
1332
|
+
const htmlToMarkdown = (html) => {
|
|
1333
|
+
const root = document.createElement("div");
|
|
1334
|
+
root.innerHTML = html;
|
|
1335
|
+
const walk = (node) => {
|
|
1336
|
+
if (node.nodeType === Node.TEXT_NODE)
|
|
1337
|
+
return node.textContent || "";
|
|
1338
|
+
if (!(node instanceof HTMLElement))
|
|
1339
|
+
return "";
|
|
1340
|
+
const content = Array.from(node.childNodes).map(walk).join("");
|
|
1341
|
+
const tag = node.tagName.toLowerCase();
|
|
1342
|
+
if (tag === "strong" || tag === "b")
|
|
1343
|
+
return `**${content}**`;
|
|
1344
|
+
if (tag === "em" || tag === "i")
|
|
1345
|
+
return `*${content}*`;
|
|
1346
|
+
if (tag === "code")
|
|
1347
|
+
return `\`${content}\``;
|
|
1348
|
+
if (tag === "br")
|
|
1349
|
+
return "\n";
|
|
1350
|
+
if (/h[1-6]/.test(tag))
|
|
1351
|
+
return `${"#".repeat(Number(tag[1]))} ${content.trim()}\n\n`;
|
|
1352
|
+
if (tag === "p")
|
|
1353
|
+
return `${content.trim()}\n\n`;
|
|
1354
|
+
if (tag === "li")
|
|
1355
|
+
return `- ${content.trim()}\n`;
|
|
1356
|
+
if (tag === "ul" || tag === "ol")
|
|
1357
|
+
return `${content}\n`;
|
|
1358
|
+
if (tag === "blockquote")
|
|
1359
|
+
return `> ${content.trim()}\n\n`;
|
|
1360
|
+
if (tag === "table")
|
|
1361
|
+
return `${node.outerHTML}\n\n`;
|
|
1362
|
+
if (tag === "img")
|
|
1363
|
+
return ` || ""})`;
|
|
1364
|
+
if (tag === "a")
|
|
1365
|
+
return `[${content}](${node.getAttribute("href") || ""})`;
|
|
1366
|
+
return content;
|
|
1367
|
+
};
|
|
1368
|
+
return Array.from(root.childNodes).map(walk).join("").replace(/\n{3,}/g, "\n\n").trim();
|
|
1369
|
+
};
|
|
1370
|
+
const importTextFile = async (files, type) => {
|
|
1371
|
+
if (!files || files.length === 0)
|
|
1372
|
+
return;
|
|
1373
|
+
const file = files[0];
|
|
1374
|
+
const text = await file.text();
|
|
1375
|
+
const html = type === "html" ? text : markdownToHtml(text);
|
|
1376
|
+
const el = editableRef.current;
|
|
1377
|
+
const hasContent = el && el.textContent && el.textContent.trim().length > 0;
|
|
1378
|
+
insertImportedHtml(html, hasContent ? "append" : "replace", {
|
|
1379
|
+
preserveColors: true,
|
|
1380
|
+
preserveDocumentLayout: true,
|
|
1381
|
+
});
|
|
1382
|
+
};
|
|
1383
|
+
const downloadText = (filename, content, mimeType) => {
|
|
1384
|
+
const blob = new Blob([content], { type: mimeType });
|
|
1385
|
+
const url = URL.createObjectURL(blob);
|
|
1386
|
+
const link = document.createElement("a");
|
|
1387
|
+
link.href = url;
|
|
1388
|
+
link.download = filename;
|
|
1389
|
+
document.body.appendChild(link);
|
|
1390
|
+
link.click();
|
|
1391
|
+
link.remove();
|
|
1392
|
+
URL.revokeObjectURL(url);
|
|
1393
|
+
};
|
|
1394
|
+
const exportHtml = () => {
|
|
1395
|
+
const html = editableRef.current?.innerHTML || "";
|
|
1396
|
+
downloadText("smart-rte-export.html", html, "text/html");
|
|
1397
|
+
};
|
|
1398
|
+
const exportMarkdown = () => {
|
|
1399
|
+
const html = editableRef.current?.innerHTML || "";
|
|
1400
|
+
downloadText("smart-rte-export.md", htmlToMarkdown(html), "text/markdown");
|
|
1401
|
+
};
|
|
1402
|
+
const htmlToDocxXml = (html) => {
|
|
1403
|
+
const root = document.createElement("div");
|
|
1404
|
+
root.innerHTML = html;
|
|
1405
|
+
const xmlEscape = (value) => value
|
|
1406
|
+
.replace(/&/g, "&")
|
|
1407
|
+
.replace(/</g, "<")
|
|
1408
|
+
.replace(/>/g, ">")
|
|
1409
|
+
.replace(/"/g, """);
|
|
1410
|
+
const colorValue = (value) => {
|
|
1411
|
+
const hex = /^#([0-9a-f]{6})$/i.exec(value.trim());
|
|
1412
|
+
if (hex)
|
|
1413
|
+
return hex[1].toUpperCase();
|
|
1414
|
+
const rgb = /^rgb\(\s*(\d+),\s*(\d+),\s*(\d+)\s*\)$/i.exec(value.trim());
|
|
1415
|
+
if (!rgb)
|
|
1416
|
+
return "";
|
|
1417
|
+
return [rgb[1], rgb[2], rgb[3]]
|
|
1418
|
+
.map((part) => Math.max(0, Math.min(255, Number(part))).toString(16).padStart(2, "0"))
|
|
1419
|
+
.join("")
|
|
1420
|
+
.toUpperCase();
|
|
1421
|
+
};
|
|
1422
|
+
const sizeToHalfPoints = (value) => {
|
|
1423
|
+
const trimmed = value.trim();
|
|
1424
|
+
const match = /^([\d.]+)(px|pt)$/i.exec(trimmed);
|
|
1425
|
+
if (!match)
|
|
1426
|
+
return "";
|
|
1427
|
+
const raw = Number(match[1]);
|
|
1428
|
+
const pt = match[2].toLowerCase() === "px" ? raw * 0.75 : raw;
|
|
1429
|
+
return String(Math.max(2, Math.round(pt * 2)));
|
|
1430
|
+
};
|
|
1431
|
+
const runProperties = (el) => {
|
|
1432
|
+
const style = el.style;
|
|
1433
|
+
const color = colorValue(style.color);
|
|
1434
|
+
const size = sizeToHalfPoints(style.fontSize);
|
|
1435
|
+
const isBold = el.tagName === "B" || el.tagName === "STRONG" || /bold|700|800|900/.test(style.fontWeight);
|
|
1436
|
+
const isItalic = el.tagName === "I" || el.tagName === "EM" || style.fontStyle === "italic";
|
|
1437
|
+
const isUnderline = el.tagName === "U" || style.textDecoration.includes("underline");
|
|
1438
|
+
return [
|
|
1439
|
+
isBold ? "<w:b/>" : "",
|
|
1440
|
+
isItalic ? "<w:i/>" : "",
|
|
1441
|
+
isUnderline ? '<w:u w:val="single"/>' : "",
|
|
1442
|
+
color ? `<w:color w:val="${color}"/>` : "",
|
|
1443
|
+
size ? `<w:sz w:val="${size}"/>` : "",
|
|
1444
|
+
].join("");
|
|
1445
|
+
};
|
|
1446
|
+
const runs = (node, inheritedProps = "") => {
|
|
1447
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1448
|
+
const text = node.textContent || "";
|
|
1449
|
+
return text ? `<w:r>${inheritedProps ? `<w:rPr>${inheritedProps}</w:rPr>` : ""}<w:t xml:space="preserve">${xmlEscape(text)}</w:t></w:r>` : "";
|
|
1450
|
+
}
|
|
1451
|
+
if (!(node instanceof HTMLElement))
|
|
1452
|
+
return "";
|
|
1453
|
+
if (node.tagName === "BR")
|
|
1454
|
+
return "<w:r><w:br/></w:r>";
|
|
1455
|
+
if (node.tagName === "IMG") {
|
|
1456
|
+
const alt = node.getAttribute("alt") || node.getAttribute("title") || "Image";
|
|
1457
|
+
return `<w:r><w:t>[Image: ${xmlEscape(alt)}]</w:t></w:r>`;
|
|
1458
|
+
}
|
|
1459
|
+
const props = `${inheritedProps}${runProperties(node)}`;
|
|
1460
|
+
return Array.from(node.childNodes).map((child) => runs(child, props)).join("");
|
|
1461
|
+
};
|
|
1462
|
+
const paragraph = (el, fallbackTag = "p") => {
|
|
1463
|
+
const tag = el.tagName.toLowerCase();
|
|
1464
|
+
const headingMatch = /^h([1-6])$/.exec(tag);
|
|
1465
|
+
const style = el.style;
|
|
1466
|
+
const align = style.textAlign ? `<w:jc w:val="${xmlEscape(style.textAlign)}"/>` : "";
|
|
1467
|
+
const headingSize = headingMatch ? `<w:rPr><w:b/><w:sz w:val="${Math.max(24, 40 - Number(headingMatch[1]) * 4)}"/></w:rPr>` : "";
|
|
1468
|
+
const body = runs(el);
|
|
1469
|
+
return `<w:p><w:pPr>${align}${headingSize}</w:pPr>${body || "<w:r><w:t></w:t></w:r>"}</w:p>`;
|
|
1470
|
+
};
|
|
1471
|
+
const tableCell = (cell) => {
|
|
1472
|
+
const fill = colorValue(cell.style.backgroundColor);
|
|
1473
|
+
const shading = fill ? `<w:shd w:val="clear" w:color="auto" w:fill="${fill}"/>` : "";
|
|
1474
|
+
const cellContent = Array.from(cell.childNodes)
|
|
1475
|
+
.map((child) => child instanceof HTMLElement && ["P", "DIV", "H1", "H2", "H3", "H4", "H5", "H6"].includes(child.tagName)
|
|
1476
|
+
? paragraph(child)
|
|
1477
|
+
: `<w:p>${runs(child)}</w:p>`)
|
|
1478
|
+
.join("");
|
|
1479
|
+
return `<w:tc><w:tcPr>${shading}<w:tcBorders><w:top w:val="single" w:sz="4" w:color="D1D5DB"/><w:left w:val="single" w:sz="4" w:color="D1D5DB"/><w:bottom w:val="single" w:sz="4" w:color="D1D5DB"/><w:right w:val="single" w:sz="4" w:color="D1D5DB"/></w:tcBorders></w:tcPr>${cellContent || "<w:p/>"}</w:tc>`;
|
|
1480
|
+
};
|
|
1481
|
+
const tableXml = (table) => {
|
|
1482
|
+
const rows = Array.from(table.querySelectorAll("tr"));
|
|
1483
|
+
return `<w:tbl><w:tblPr><w:tblW w:w="0" w:type="auto"/><w:tblBorders><w:top w:val="single" w:sz="4" w:color="D1D5DB"/><w:left w:val="single" w:sz="4" w:color="D1D5DB"/><w:bottom w:val="single" w:sz="4" w:color="D1D5DB"/><w:right w:val="single" w:sz="4" w:color="D1D5DB"/><w:insideH w:val="single" w:sz="4" w:color="D1D5DB"/><w:insideV w:val="single" w:sz="4" w:color="D1D5DB"/></w:tblBorders></w:tblPr>${rows.map((row) => `<w:tr>${Array.from(row.children).map((cell) => tableCell(cell)).join("")}</w:tr>`).join("")}</w:tbl>`;
|
|
1484
|
+
};
|
|
1485
|
+
const blockXml = (node) => {
|
|
1486
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1487
|
+
const text = node.textContent?.trim();
|
|
1488
|
+
return text ? `<w:p>${runs(node)}</w:p>` : "";
|
|
1489
|
+
}
|
|
1490
|
+
if (!(node instanceof HTMLElement))
|
|
1491
|
+
return "";
|
|
1492
|
+
if (node.tagName === "TABLE")
|
|
1493
|
+
return tableXml(node);
|
|
1494
|
+
if (node.tagName === "UL" || node.tagName === "OL") {
|
|
1495
|
+
return Array.from(node.children).map((li) => `<w:p><w:r><w:t>• </w:t></w:r>${runs(li)}</w:p>`).join("");
|
|
1496
|
+
}
|
|
1497
|
+
if (node.tagName === "BLOCKQUOTE") {
|
|
1498
|
+
return `<w:p><w:pPr><w:ind w:left="720"/></w:pPr>${runs(node)}</w:p>`;
|
|
1499
|
+
}
|
|
1500
|
+
if (node.tagName === "HR")
|
|
1501
|
+
return '<w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:color="D1D5DB"/></w:pBdr></w:pPr></w:p>';
|
|
1502
|
+
if (["P", "DIV", "PRE", "H1", "H2", "H3", "H4", "H5", "H6"].includes(node.tagName))
|
|
1503
|
+
return paragraph(node);
|
|
1504
|
+
return Array.from(node.childNodes).map(blockXml).join("");
|
|
1505
|
+
};
|
|
1506
|
+
const body = Array.from(root.childNodes).map(blockXml).join("");
|
|
1507
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>${body}<w:sectPr><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:top="720" w:right="720" w:bottom="720" w:left="720"/></w:sectPr></w:body></w:document>`;
|
|
1508
|
+
};
|
|
1509
|
+
const exportDocx = async () => {
|
|
1510
|
+
const html = editableRef.current?.innerHTML || "";
|
|
1511
|
+
const zip = new JSZip();
|
|
1512
|
+
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/></Types>`);
|
|
1513
|
+
zip.folder("_rels")?.file(".rels", `<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>`);
|
|
1514
|
+
zip.folder("word")?.file("document.xml", htmlToDocxXml(html));
|
|
1515
|
+
zip.folder("word")?.folder("_rels")?.file("document.xml.rels", `<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>`);
|
|
1516
|
+
const blob = await zip.generateAsync({ type: "blob", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
|
|
1517
|
+
const url = URL.createObjectURL(blob);
|
|
1518
|
+
const link = document.createElement("a");
|
|
1519
|
+
link.href = url;
|
|
1520
|
+
link.download = "smart-rte-export.docx";
|
|
1521
|
+
document.body.appendChild(link);
|
|
1522
|
+
link.click();
|
|
1523
|
+
link.remove();
|
|
1524
|
+
URL.revokeObjectURL(url);
|
|
1525
|
+
};
|
|
1526
|
+
const exportPdf = () => {
|
|
1527
|
+
const html = editableRef.current?.innerHTML || "";
|
|
1528
|
+
const printWindow = window.open("", "_blank", "noopener,noreferrer,width=900,height=700");
|
|
1529
|
+
if (!printWindow)
|
|
1530
|
+
return;
|
|
1531
|
+
printWindow.document.write(`<!doctype html><html><head><title>Export PDF</title><style>body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.6;padding:32px;color:#111}table{border-collapse:collapse;width:100%;margin:12px 0}td,th{border:1px solid #d1d5db;padding:8px;vertical-align:top}img{max-width:100%;height:auto}blockquote{border-left:4px solid #d1d5db;padding-left:12px;color:#374151}pre,code{background:#f3f4f6}pre{padding:12px;white-space:pre-wrap}@media print{body{padding:0}}</style></head><body>${html}</body></html>`);
|
|
1532
|
+
printWindow.document.close();
|
|
1533
|
+
printWindow.focus();
|
|
1534
|
+
setTimeout(() => printWindow.print(), 250);
|
|
1535
|
+
};
|
|
920
1536
|
const fixNegativeMargins = (root) => {
|
|
921
1537
|
try {
|
|
922
1538
|
const nodes = root.querySelectorAll('*');
|
|
@@ -929,6 +1545,152 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
929
1545
|
}
|
|
930
1546
|
catch { }
|
|
931
1547
|
};
|
|
1548
|
+
const cleanPastedHtml = (html, options = {}) => {
|
|
1549
|
+
const shouldPreserveColors = options.preserveColors ?? preserveColors;
|
|
1550
|
+
const shouldPreserveDocumentLayout = options.preserveDocumentLayout ?? false;
|
|
1551
|
+
const template = document.createElement('template');
|
|
1552
|
+
template.innerHTML = html
|
|
1553
|
+
.replace(/ /gi, ' ')
|
|
1554
|
+
.replace(/\u00a0/g, ' ')
|
|
1555
|
+
.replace(/[\u200b\u200c\u200d]/g, '');
|
|
1556
|
+
template.content.querySelectorAll('meta, link, style, script').forEach((node) => node.remove());
|
|
1557
|
+
const allowedStyleNames = new Set([
|
|
1558
|
+
'font-weight',
|
|
1559
|
+
'font-style',
|
|
1560
|
+
'text-decoration',
|
|
1561
|
+
'text-align',
|
|
1562
|
+
'vertical-align',
|
|
1563
|
+
'border',
|
|
1564
|
+
'border-top',
|
|
1565
|
+
'border-right',
|
|
1566
|
+
'border-bottom',
|
|
1567
|
+
'border-left',
|
|
1568
|
+
'border-collapse',
|
|
1569
|
+
'padding',
|
|
1570
|
+
'padding-top',
|
|
1571
|
+
'padding-right',
|
|
1572
|
+
'padding-bottom',
|
|
1573
|
+
'padding-left',
|
|
1574
|
+
'list-style-type',
|
|
1575
|
+
'white-space',
|
|
1576
|
+
]);
|
|
1577
|
+
if (preserveFontFamily)
|
|
1578
|
+
allowedStyleNames.add('font-family');
|
|
1579
|
+
if (shouldPreserveColors) {
|
|
1580
|
+
allowedStyleNames.add('color');
|
|
1581
|
+
allowedStyleNames.add('background');
|
|
1582
|
+
allowedStyleNames.add('background-color');
|
|
1583
|
+
}
|
|
1584
|
+
if (shouldPreserveDocumentLayout) {
|
|
1585
|
+
[
|
|
1586
|
+
'font-size',
|
|
1587
|
+
'line-height',
|
|
1588
|
+
'margin',
|
|
1589
|
+
'margin-top',
|
|
1590
|
+
'margin-right',
|
|
1591
|
+
'margin-bottom',
|
|
1592
|
+
'margin-left',
|
|
1593
|
+
'text-indent',
|
|
1594
|
+
'width',
|
|
1595
|
+
'min-width',
|
|
1596
|
+
].forEach((name) => allowedStyleNames.add(name));
|
|
1597
|
+
}
|
|
1598
|
+
template.content.querySelectorAll('*').forEach((node) => {
|
|
1599
|
+
const className = node.getAttribute('class');
|
|
1600
|
+
if (className !== 'srte-preserve-colors')
|
|
1601
|
+
node.removeAttribute('class');
|
|
1602
|
+
node.removeAttribute('id');
|
|
1603
|
+
if (!shouldPreserveDocumentLayout) {
|
|
1604
|
+
node.removeAttribute('width');
|
|
1605
|
+
node.removeAttribute('height');
|
|
1606
|
+
}
|
|
1607
|
+
const style = node.getAttribute('style');
|
|
1608
|
+
if (!style)
|
|
1609
|
+
return;
|
|
1610
|
+
const safeRules = style
|
|
1611
|
+
.split(';')
|
|
1612
|
+
.map((rule) => rule.trim())
|
|
1613
|
+
.filter(Boolean)
|
|
1614
|
+
.filter((rule) => {
|
|
1615
|
+
const separator = rule.indexOf(':');
|
|
1616
|
+
if (separator === -1)
|
|
1617
|
+
return false;
|
|
1618
|
+
const name = rule.slice(0, separator).trim().toLowerCase();
|
|
1619
|
+
const value = rule.slice(separator + 1).trim().toLowerCase();
|
|
1620
|
+
if (!allowedStyleNames.has(name))
|
|
1621
|
+
return false;
|
|
1622
|
+
if (value.includes('position') || value.includes('expression') || value.includes('javascript:'))
|
|
1623
|
+
return false;
|
|
1624
|
+
if (name === 'white-space' && value !== 'pre-wrap')
|
|
1625
|
+
return false;
|
|
1626
|
+
if ((name === 'width' || name === 'min-width') && !/^[\d.]+(px|pt|em|rem|%)$/.test(value))
|
|
1627
|
+
return false;
|
|
1628
|
+
return true;
|
|
1629
|
+
});
|
|
1630
|
+
if (safeRules.length)
|
|
1631
|
+
node.setAttribute('style', safeRules.join('; '));
|
|
1632
|
+
else
|
|
1633
|
+
node.removeAttribute('style');
|
|
1634
|
+
});
|
|
1635
|
+
return template.innerHTML;
|
|
1636
|
+
};
|
|
1637
|
+
const normalizeEditorContent = () => {
|
|
1638
|
+
const el = editableRef.current;
|
|
1639
|
+
if (!el)
|
|
1640
|
+
return;
|
|
1641
|
+
fixNegativeMargins(el);
|
|
1642
|
+
ensureTableWrappers(el);
|
|
1643
|
+
addTableResizeHandles();
|
|
1644
|
+
};
|
|
1645
|
+
const insertHtmlAtEnd = (html) => {
|
|
1646
|
+
const el = editableRef.current;
|
|
1647
|
+
if (!el)
|
|
1648
|
+
return;
|
|
1649
|
+
el.focus();
|
|
1650
|
+
const range = document.createRange();
|
|
1651
|
+
range.selectNodeContents(el);
|
|
1652
|
+
range.collapse(false);
|
|
1653
|
+
const sel = window.getSelection();
|
|
1654
|
+
sel?.removeAllRanges();
|
|
1655
|
+
sel?.addRange(range);
|
|
1656
|
+
const separator = el.textContent?.trim() ? '<p><br></p>' : '';
|
|
1657
|
+
document.execCommand('insertHTML', false, `${separator}${html}`);
|
|
1658
|
+
};
|
|
1659
|
+
const replaceEditorHtml = (html) => {
|
|
1660
|
+
const el = editableRef.current;
|
|
1661
|
+
if (!el)
|
|
1662
|
+
return;
|
|
1663
|
+
el.innerHTML = html;
|
|
1664
|
+
el.focus();
|
|
1665
|
+
const range = document.createRange();
|
|
1666
|
+
range.selectNodeContents(el);
|
|
1667
|
+
range.collapse(false);
|
|
1668
|
+
const sel = window.getSelection();
|
|
1669
|
+
sel?.removeAllRanges();
|
|
1670
|
+
sel?.addRange(range);
|
|
1671
|
+
};
|
|
1672
|
+
const insertImportedHtml = (html, mode, cleanOptions) => {
|
|
1673
|
+
try {
|
|
1674
|
+
const cleanHtml = cleanPastedHtml(html, cleanOptions);
|
|
1675
|
+
if (mode === 'replace')
|
|
1676
|
+
replaceEditorHtml(cleanHtml);
|
|
1677
|
+
else
|
|
1678
|
+
insertHtmlAtEnd(cleanHtml);
|
|
1679
|
+
normalizeEditorContent();
|
|
1680
|
+
emitChange();
|
|
1681
|
+
}
|
|
1682
|
+
catch (error) {
|
|
1683
|
+
console.error('Error inserting imported content:', error);
|
|
1684
|
+
}
|
|
1685
|
+
};
|
|
1686
|
+
const insertCleanHtml = (html) => {
|
|
1687
|
+
try {
|
|
1688
|
+
document.execCommand("insertHTML", false, cleanPastedHtml(html));
|
|
1689
|
+
normalizeEditorContent();
|
|
1690
|
+
emitChange();
|
|
1691
|
+
}
|
|
1692
|
+
catch { }
|
|
1693
|
+
};
|
|
932
1694
|
const ensureTableWrappers = (root) => {
|
|
933
1695
|
try {
|
|
934
1696
|
const tables = root.querySelectorAll('table');
|
|
@@ -938,10 +1700,12 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
938
1700
|
const wrapper = document.createElement('div');
|
|
939
1701
|
wrapper.setAttribute('data-table-wrapper', 'true');
|
|
940
1702
|
wrapper.style.overflowX = 'auto';
|
|
1703
|
+
wrapper.style.overflowY = 'visible';
|
|
941
1704
|
wrapper.style.webkitOverflowScrolling = 'touch';
|
|
942
1705
|
wrapper.style.width = '100%';
|
|
943
1706
|
wrapper.style.maxWidth = '100%';
|
|
944
1707
|
wrapper.style.display = 'block';
|
|
1708
|
+
wrapper.style.paddingBottom = '8px';
|
|
945
1709
|
// Use insertBefore + appendChild to move element without losing too much state
|
|
946
1710
|
// simpler than replaceChild for wrapping
|
|
947
1711
|
parent.insertBefore(wrapper, table);
|
|
@@ -1335,6 +2099,27 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1335
2099
|
firstRow.replaceChild(td, c);
|
|
1336
2100
|
}
|
|
1337
2101
|
}
|
|
2102
|
+
handleInput();
|
|
2103
|
+
};
|
|
2104
|
+
const toggleHeaderColumn = (cell) => {
|
|
2105
|
+
const pos = getCellPosition(cell);
|
|
2106
|
+
if (!pos)
|
|
2107
|
+
return;
|
|
2108
|
+
const { tbody, cIdx } = pos;
|
|
2109
|
+
const rows = Array.from(tbody.querySelectorAll("tr"));
|
|
2110
|
+
const columnCells = rows
|
|
2111
|
+
.map((row) => cellsOfRow(row)[cIdx])
|
|
2112
|
+
.filter(Boolean);
|
|
2113
|
+
const shouldMakeHeader = columnCells.some((c) => c.tagName !== "TH");
|
|
2114
|
+
for (const c of columnCells) {
|
|
2115
|
+
const replacement = document.createElement(shouldMakeHeader ? "th" : "td");
|
|
2116
|
+
replacement.innerHTML = c.innerHTML || " ";
|
|
2117
|
+
replacement.style.border = c.style.border || "1px solid var(--srte-border)";
|
|
2118
|
+
replacement.style.padding = c.style.padding || "6px";
|
|
2119
|
+
replacement.style.minWidth = c.style.minWidth || "60px";
|
|
2120
|
+
c.parentElement?.replaceChild(replacement, c);
|
|
2121
|
+
}
|
|
2122
|
+
handleInput();
|
|
1338
2123
|
};
|
|
1339
2124
|
const applyBgToSelection = (hex, fallbackCell) => {
|
|
1340
2125
|
const sel = selectionRef.current;
|
|
@@ -1503,7 +2288,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1503
2288
|
borderRadius: 6,
|
|
1504
2289
|
width: "100%",
|
|
1505
2290
|
maxWidth: "100vw",
|
|
1506
|
-
overflow: "
|
|
2291
|
+
overflow: "visible",
|
|
1507
2292
|
display: "flex",
|
|
1508
2293
|
flexDirection: "column",
|
|
1509
2294
|
background: "var(--srte-bg)",
|
|
@@ -1552,6 +2337,12 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1552
2337
|
} }), _jsx("input", { ref: docxInputRef, type: "file", accept: ".docx", style: { display: "none" }, onChange: (e) => {
|
|
1553
2338
|
handleDocxFiles(e.currentTarget.files);
|
|
1554
2339
|
e.currentTarget.value = "";
|
|
2340
|
+
} }), _jsx("input", { ref: htmlInputRef, type: "file", accept: ".html,.htm,text/html", style: { display: "none" }, onChange: (e) => {
|
|
2341
|
+
importTextFile(e.currentTarget.files, "html");
|
|
2342
|
+
e.currentTarget.value = "";
|
|
2343
|
+
} }), _jsx("input", { ref: mdInputRef, type: "file", accept: ".md,.markdown,text/markdown,text/plain", style: { display: "none" }, onChange: (e) => {
|
|
2344
|
+
importTextFile(e.currentTarget.files, "md");
|
|
2345
|
+
e.currentTarget.value = "";
|
|
1555
2346
|
} }), _jsxs("select", { defaultValue: "p", onChange: (e) => {
|
|
1556
2347
|
const val = e.target.value;
|
|
1557
2348
|
if (val === "p")
|
|
@@ -1621,7 +2412,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1621
2412
|
borderRadius: 6,
|
|
1622
2413
|
background: "var(--srte-input-bg)",
|
|
1623
2414
|
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: () => {
|
|
2415
|
+
}, 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
2416
|
const sel = window.getSelection();
|
|
1626
2417
|
if (sel && sel.rangeCount > 0) {
|
|
1627
2418
|
const range = sel.getRangeAt(0);
|
|
@@ -1638,7 +2429,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1638
2429
|
background: "var(--srte-input-bg)",
|
|
1639
2430
|
color: "var(--srte-input-text)",
|
|
1640
2431
|
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: () => {
|
|
2432
|
+
}, 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
2433
|
setColorPickerType('text');
|
|
1643
2434
|
setShowColorPicker(true);
|
|
1644
2435
|
}, style: {
|
|
@@ -1650,7 +2441,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1650
2441
|
background: "var(--srte-input-bg)",
|
|
1651
2442
|
color: "var(--srte-input-text)",
|
|
1652
2443
|
position: "relative",
|
|
1653
|
-
}, children: _jsx("span", { style: { fontWeight: 700 }, children: "A" }) }), _jsx("button", { title: "Background Color", onClick: () => {
|
|
2444
|
+
}, children: _jsx("span", { style: { fontWeight: 700, borderBottom: "3px solid currentColor", lineHeight: 1 }, children: "A" }) }), _jsx("button", { title: "Background Color", onClick: () => {
|
|
1654
2445
|
setColorPickerType('background');
|
|
1655
2446
|
setShowColorPicker(true);
|
|
1656
2447
|
}, style: {
|
|
@@ -1661,7 +2452,23 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1661
2452
|
borderRadius: 6,
|
|
1662
2453
|
background: "var(--srte-input-bg)",
|
|
1663
2454
|
color: "var(--srte-input-text)",
|
|
1664
|
-
}, children: _jsx("span", { style: { fontWeight: 700, padding: "
|
|
2455
|
+
}, 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: {
|
|
2456
|
+
height: 32,
|
|
2457
|
+
minWidth: 32,
|
|
2458
|
+
padding: "0 8px",
|
|
2459
|
+
border: "1px solid var(--srte-input-border)",
|
|
2460
|
+
borderRadius: 6,
|
|
2461
|
+
background: "var(--srte-input-bg)",
|
|
2462
|
+
color: "var(--srte-input-text)",
|
|
2463
|
+
}, children: ["X", _jsx("sub", { children: "2" })] }), _jsxs("button", { title: "Superscript", onClick: () => exec("superscript"), style: {
|
|
2464
|
+
height: 32,
|
|
2465
|
+
minWidth: 32,
|
|
2466
|
+
padding: "0 8px",
|
|
2467
|
+
border: "1px solid var(--srte-input-border)",
|
|
2468
|
+
borderRadius: 6,
|
|
2469
|
+
background: "var(--srte-input-bg)",
|
|
2470
|
+
color: "var(--srte-input-text)",
|
|
2471
|
+
}, children: ["X", _jsx("sup", { children: "2" })] }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), style: {
|
|
1665
2472
|
height: 32,
|
|
1666
2473
|
padding: "0 10px",
|
|
1667
2474
|
border: "1px solid var(--srte-input-border)",
|
|
@@ -1675,7 +2482,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1675
2482
|
borderRadius: 6,
|
|
1676
2483
|
background: "var(--srte-input-bg)",
|
|
1677
2484
|
color: "var(--srte-input-text)",
|
|
1678
|
-
}, children: "1. List" }), _jsx("button", { title: "Blockquote", onClick:
|
|
2485
|
+
}, children: "1. List" }), _jsx("button", { title: "Blockquote", onClick: toggleBlockquote, style: {
|
|
1679
2486
|
height: 32,
|
|
1680
2487
|
minWidth: 32,
|
|
1681
2488
|
padding: "0 8px",
|
|
@@ -1683,7 +2490,15 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1683
2490
|
borderRadius: 6,
|
|
1684
2491
|
background: "var(--srte-input-bg)",
|
|
1685
2492
|
color: "var(--srte-input-text)",
|
|
1686
|
-
}, children: "\u275D" }), _jsx("button", { title: "
|
|
2493
|
+
}, children: "\u275D" }), _jsx("button", { title: "Special characters", onClick: () => setShowSpecialChars(true), style: {
|
|
2494
|
+
height: 32,
|
|
2495
|
+
minWidth: 32,
|
|
2496
|
+
padding: "0 8px",
|
|
2497
|
+
border: "1px solid var(--srte-input-border)",
|
|
2498
|
+
borderRadius: 6,
|
|
2499
|
+
background: "var(--srte-input-bg)",
|
|
2500
|
+
color: "var(--srte-input-text)",
|
|
2501
|
+
}, children: "\u03A9" }), _jsx("button", { title: "Code block", onClick: () => exec("formatBlock", "<pre>"), style: {
|
|
1687
2502
|
height: 32,
|
|
1688
2503
|
minWidth: 36,
|
|
1689
2504
|
padding: "0 8px",
|
|
@@ -1744,7 +2559,49 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1744
2559
|
background: "var(--srte-input-bg)",
|
|
1745
2560
|
color: "var(--srte-input-text)",
|
|
1746
2561
|
opacity: loadingDocx ? 0.5 : 1,
|
|
1747
|
-
}, children: loadingDocx ? '⌛ Importing...' : '📝 DOCX' }),
|
|
2562
|
+
}, children: loadingDocx ? '⌛ Importing...' : '📝 DOCX' }), _jsx("button", { title: "Import HTML", onClick: () => htmlInputRef.current?.click(), style: {
|
|
2563
|
+
height: 32,
|
|
2564
|
+
padding: "0 10px",
|
|
2565
|
+
border: "1px solid var(--srte-input-border)",
|
|
2566
|
+
borderRadius: 6,
|
|
2567
|
+
background: "var(--srte-input-bg)",
|
|
2568
|
+
color: "var(--srte-input-text)",
|
|
2569
|
+
}, children: "HTML" }), _jsx("button", { title: "Import Markdown", onClick: () => mdInputRef.current?.click(), style: {
|
|
2570
|
+
height: 32,
|
|
2571
|
+
padding: "0 10px",
|
|
2572
|
+
border: "1px solid var(--srte-input-border)",
|
|
2573
|
+
borderRadius: 6,
|
|
2574
|
+
background: "var(--srte-input-bg)",
|
|
2575
|
+
color: "var(--srte-input-text)",
|
|
2576
|
+
}, children: "MD" }), _jsx("button", { title: "Export HTML", onClick: exportHtml, style: {
|
|
2577
|
+
height: 32,
|
|
2578
|
+
padding: "0 10px",
|
|
2579
|
+
border: "1px solid var(--srte-input-border)",
|
|
2580
|
+
borderRadius: 6,
|
|
2581
|
+
background: "var(--srte-input-bg)",
|
|
2582
|
+
color: "var(--srte-input-text)",
|
|
2583
|
+
}, children: "Export HTML" }), _jsx("button", { title: "Export Markdown", onClick: exportMarkdown, style: {
|
|
2584
|
+
height: 32,
|
|
2585
|
+
padding: "0 10px",
|
|
2586
|
+
border: "1px solid var(--srte-input-border)",
|
|
2587
|
+
borderRadius: 6,
|
|
2588
|
+
background: "var(--srte-input-bg)",
|
|
2589
|
+
color: "var(--srte-input-text)",
|
|
2590
|
+
}, children: "Export MD" }), _jsx("button", { title: "Export DOCX", onClick: exportDocx, style: {
|
|
2591
|
+
height: 32,
|
|
2592
|
+
padding: "0 10px",
|
|
2593
|
+
border: "1px solid var(--srte-input-border)",
|
|
2594
|
+
borderRadius: 6,
|
|
2595
|
+
background: "var(--srte-input-bg)",
|
|
2596
|
+
color: "var(--srte-input-text)",
|
|
2597
|
+
}, children: "Export DOCX" }), _jsx("button", { title: "Export PDF", onClick: exportPdf, style: {
|
|
2598
|
+
height: 32,
|
|
2599
|
+
padding: "0 10px",
|
|
2600
|
+
border: "1px solid var(--srte-input-border)",
|
|
2601
|
+
borderRadius: 6,
|
|
2602
|
+
background: "var(--srte-input-bg)",
|
|
2603
|
+
color: "var(--srte-input-text)",
|
|
2604
|
+
}, children: "Export PDF" }), _jsxs("div", { style: {
|
|
1748
2605
|
display: "inline-flex",
|
|
1749
2606
|
gap: 4,
|
|
1750
2607
|
alignItems: "center",
|
|
@@ -1823,7 +2680,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1823
2680
|
color: "var(--srte-input-text)",
|
|
1824
2681
|
}, children: "\u293E Redo" })] }), media && mediaManager && (_jsx(MediaManager, { open: showMediaManager, onClose: () => setShowMediaManager(false), adapter: mediaManager, onSelect: (item) => {
|
|
1825
2682
|
if (item?.url)
|
|
1826
|
-
insertImageAtSelection(item
|
|
2683
|
+
insertImageAtSelection(item);
|
|
1827
2684
|
} })), table && showTableDialog && (_jsx("div", { style: {
|
|
1828
2685
|
position: "fixed",
|
|
1829
2686
|
inset: 0,
|
|
@@ -1988,6 +2845,50 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1988
2845
|
background: 'var(--srte-input-bg)',
|
|
1989
2846
|
color: 'var(--srte-input-text)',
|
|
1990
2847
|
cursor: 'pointer',
|
|
2848
|
+
}, children: "Close" }) })] }) })), showSpecialChars && (_jsx("div", { style: {
|
|
2849
|
+
position: "fixed",
|
|
2850
|
+
inset: 0,
|
|
2851
|
+
background: "var(--srte-modal-backdrop)",
|
|
2852
|
+
display: "flex",
|
|
2853
|
+
alignItems: "center",
|
|
2854
|
+
justifyContent: "center",
|
|
2855
|
+
zIndex: 50,
|
|
2856
|
+
}, onClick: () => setShowSpecialChars(false), children: _jsxs("div", { style: {
|
|
2857
|
+
background: "var(--srte-modal-bg)",
|
|
2858
|
+
color: "var(--srte-modal-text)",
|
|
2859
|
+
padding: 16,
|
|
2860
|
+
borderRadius: 8,
|
|
2861
|
+
width: 420,
|
|
2862
|
+
maxWidth: "90vw",
|
|
2863
|
+
boxShadow: "var(--srte-menu-shadow)",
|
|
2864
|
+
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 12 }, children: "Special characters" }), [
|
|
2865
|
+
{
|
|
2866
|
+
label: "Greek",
|
|
2867
|
+
chars: ["α", "β", "γ", "δ", "ε", "ζ", "η", "θ", "ι", "κ", "λ", "μ", "ν", "ξ", "π", "ρ", "σ", "τ", "φ", "χ", "ψ", "ω", "Δ", "Σ", "Ω"],
|
|
2868
|
+
},
|
|
2869
|
+
{
|
|
2870
|
+
label: "Medical / Math",
|
|
2871
|
+
chars: ["±", "≤", "≥", "≠", "≈", "∞", "°", "µ", "×", "÷", "→", "←", "↑", "↓", "∴", "∵", "√", "∑", "∫", "₂", "₃", "²", "³"],
|
|
2872
|
+
},
|
|
2873
|
+
].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: () => {
|
|
2874
|
+
insertTextAtSelection(char);
|
|
2875
|
+
setShowSpecialChars(false);
|
|
2876
|
+
}, style: {
|
|
2877
|
+
height: 32,
|
|
2878
|
+
minWidth: 32,
|
|
2879
|
+
padding: "0 8px",
|
|
2880
|
+
border: "1px solid var(--srte-input-border)",
|
|
2881
|
+
borderRadius: 6,
|
|
2882
|
+
background: "var(--srte-input-bg)",
|
|
2883
|
+
color: "var(--srte-input-text)",
|
|
2884
|
+
fontSize: 16,
|
|
2885
|
+
}, children: char }, char))) })] }, group.label))), _jsx("div", { style: { display: "flex", justifyContent: "flex-end" }, children: _jsx("button", { type: "button", onClick: () => setShowSpecialChars(false), style: {
|
|
2886
|
+
padding: "6px 16px",
|
|
2887
|
+
border: "1px solid var(--srte-input-border)",
|
|
2888
|
+
borderRadius: 6,
|
|
2889
|
+
background: "var(--srte-input-bg)",
|
|
2890
|
+
color: "var(--srte-input-text)",
|
|
2891
|
+
cursor: "pointer",
|
|
1991
2892
|
}, children: "Close" }) })] }) })), formula && showFormulaDialog && (_jsx("div", { style: {
|
|
1992
2893
|
position: "fixed",
|
|
1993
2894
|
inset: 0,
|
|
@@ -2093,12 +2994,15 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2093
2994
|
width: "100%",
|
|
2094
2995
|
maxWidth: "100%",
|
|
2095
2996
|
flex: "1 1 auto",
|
|
2997
|
+
minWidth: 0,
|
|
2096
2998
|
minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight,
|
|
2097
2999
|
maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
|
|
2098
3000
|
overflowY: "auto",
|
|
2099
|
-
overflowX: "
|
|
3001
|
+
overflowX: "auto",
|
|
3002
|
+
overscrollBehavior: "contain",
|
|
2100
3003
|
boxSizing: "border-box",
|
|
2101
3004
|
position: "relative",
|
|
3005
|
+
scrollPaddingBottom: 24,
|
|
2102
3006
|
}, children: _jsx("div", { ref: editableRef, contentEditable: !readOnly, suppressContentEditableWarning: true, onInput: handleInput, onCompositionStart: () => (isComposingRef.current = true), onCompositionEnd: () => {
|
|
2103
3007
|
isComposingRef.current = false;
|
|
2104
3008
|
handleInput();
|
|
@@ -2109,8 +3013,14 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2109
3013
|
if (hasImage) {
|
|
2110
3014
|
e.preventDefault();
|
|
2111
3015
|
handleLocalImageFiles(items);
|
|
3016
|
+
return;
|
|
2112
3017
|
}
|
|
2113
3018
|
}
|
|
3019
|
+
const html = e.clipboardData?.getData("text/html");
|
|
3020
|
+
if (html) {
|
|
3021
|
+
e.preventDefault();
|
|
3022
|
+
insertCleanHtml(cleanPastedHtml(html));
|
|
3023
|
+
}
|
|
2114
3024
|
}, onDragOver: (e) => {
|
|
2115
3025
|
// Allow dragging images within editor and file drops
|
|
2116
3026
|
if (draggedImageRef.current ||
|
|
@@ -2231,10 +3141,11 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2231
3141
|
}, onDragEnd: () => {
|
|
2232
3142
|
draggedImageRef.current = null;
|
|
2233
3143
|
}, style: {
|
|
2234
|
-
minHeight: "
|
|
3144
|
+
minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight,
|
|
2235
3145
|
maxWidth: "100%",
|
|
2236
|
-
overflowX: "
|
|
3146
|
+
overflowX: "visible",
|
|
2237
3147
|
padding: "16px",
|
|
3148
|
+
paddingBottom: "32px",
|
|
2238
3149
|
outline: "none",
|
|
2239
3150
|
lineHeight: 1.6,
|
|
2240
3151
|
boxSizing: "border-box",
|
|
@@ -2613,7 +3524,16 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2613
3524
|
}, onClick: () => {
|
|
2614
3525
|
toggleHeaderRow(tableMenu.cell);
|
|
2615
3526
|
setTableMenu(null);
|
|
2616
|
-
}, children: [_jsx("span", { children: "H\u2081" }), _jsx("span", { children: "Toggle header row" })] }),
|
|
3527
|
+
}, children: [_jsx("span", { children: "H\u2081" }), _jsx("span", { children: "Toggle header row" })] }), _jsxs("button", { style: {
|
|
3528
|
+
display: "flex",
|
|
3529
|
+
alignItems: "center",
|
|
3530
|
+
gap: 8,
|
|
3531
|
+
padding: "6px 8px",
|
|
3532
|
+
fontSize: 12,
|
|
3533
|
+
}, onClick: () => {
|
|
3534
|
+
toggleHeaderColumn(tableMenu.cell);
|
|
3535
|
+
setTableMenu(null);
|
|
3536
|
+
}, children: [_jsx("span", { children: "H\u2195" }), _jsx("span", { children: "Toggle header column" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { style: {
|
|
2617
3537
|
display: "flex",
|
|
2618
3538
|
alignItems: "center",
|
|
2619
3539
|
gap: 8,
|
|
@@ -2640,7 +3560,9 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2640
3560
|
borderRadius: 8,
|
|
2641
3561
|
boxShadow: "var(--srte-menu-shadow)",
|
|
2642
3562
|
padding: 8,
|
|
2643
|
-
width:
|
|
3563
|
+
width: 280,
|
|
3564
|
+
maxHeight: "80vh",
|
|
3565
|
+
overflowY: "auto",
|
|
2644
3566
|
color: "var(--srte-menu-text)",
|
|
2645
3567
|
}, 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
3568
|
? imageMenu.img.parentElement.href
|
|
@@ -2742,7 +3664,58 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2742
3664
|
img.style.margin = "0 0 8px 8px";
|
|
2743
3665
|
scheduleImageOverlay();
|
|
2744
3666
|
handleInput();
|
|
2745
|
-
}, children: "\u27F9" })] }), _jsxs("div", { style: {
|
|
3667
|
+
}, children: "\u27F9" })] }), _jsxs("div", { style: {
|
|
3668
|
+
borderTop: "1px solid var(--srte-border-light)",
|
|
3669
|
+
paddingTop: 6,
|
|
3670
|
+
display: "grid",
|
|
3671
|
+
gap: 6,
|
|
3672
|
+
}, children: [_jsx("div", { style: { fontWeight: 600, fontSize: 11 }, children: "License" }), _jsx("input", { placeholder: "Work name", defaultValue: imageMenu.img.dataset.workName || "", onChange: (e) => {
|
|
3673
|
+
imageMenu.img.dataset.workName = e.target.value;
|
|
3674
|
+
handleInput();
|
|
3675
|
+
}, style: {
|
|
3676
|
+
padding: "4px 6px",
|
|
3677
|
+
border: "1px solid var(--srte-border-light)",
|
|
3678
|
+
borderRadius: 4,
|
|
3679
|
+
color: "var(--srte-input-text)",
|
|
3680
|
+
background: "var(--srte-input-bg)",
|
|
3681
|
+
} }), _jsx("input", { placeholder: "Author", defaultValue: imageMenu.img.dataset.licenseAuthor || "", onChange: (e) => {
|
|
3682
|
+
imageMenu.img.dataset.licenseAuthor = e.target.value;
|
|
3683
|
+
handleInput();
|
|
3684
|
+
}, style: {
|
|
3685
|
+
padding: "4px 6px",
|
|
3686
|
+
border: "1px solid var(--srte-border-light)",
|
|
3687
|
+
borderRadius: 4,
|
|
3688
|
+
color: "var(--srte-input-text)",
|
|
3689
|
+
background: "var(--srte-input-bg)",
|
|
3690
|
+
} }), _jsxs("select", { defaultValue: imageMenu.img.dataset.licenseType || "", onChange: (e) => {
|
|
3691
|
+
imageMenu.img.dataset.licenseType = e.target.value;
|
|
3692
|
+
handleInput();
|
|
3693
|
+
}, style: {
|
|
3694
|
+
height: 28,
|
|
3695
|
+
padding: "0 6px",
|
|
3696
|
+
border: "1px solid var(--srte-border-light)",
|
|
3697
|
+
borderRadius: 4,
|
|
3698
|
+
color: "var(--srte-input-text)",
|
|
3699
|
+
background: "var(--srte-input-bg)",
|
|
3700
|
+
}, 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) => {
|
|
3701
|
+
imageMenu.img.dataset.licenseText = e.target.value;
|
|
3702
|
+
handleInput();
|
|
3703
|
+
}, style: {
|
|
3704
|
+
padding: "4px 6px",
|
|
3705
|
+
border: "1px solid var(--srte-border-light)",
|
|
3706
|
+
borderRadius: 4,
|
|
3707
|
+
color: "var(--srte-input-text)",
|
|
3708
|
+
background: "var(--srte-input-bg)",
|
|
3709
|
+
} }), _jsx("input", { placeholder: "Source URL", defaultValue: imageMenu.img.dataset.licenseUrl || "", onChange: (e) => {
|
|
3710
|
+
imageMenu.img.dataset.licenseUrl = e.target.value;
|
|
3711
|
+
handleInput();
|
|
3712
|
+
}, style: {
|
|
3713
|
+
padding: "4px 6px",
|
|
3714
|
+
border: "1px solid var(--srte-border-light)",
|
|
3715
|
+
borderRadius: 4,
|
|
3716
|
+
color: "var(--srte-input-text)",
|
|
3717
|
+
background: "var(--srte-input-bg)",
|
|
3718
|
+
} })] }), _jsxs("div", { style: { display: "flex", gap: 6 }, children: [_jsx("button", { onClick: () => {
|
|
2746
3719
|
replaceTargetRef.current = imageMenu.img;
|
|
2747
3720
|
fileInputRef.current?.click();
|
|
2748
3721
|
}, children: "Replace\u2026" }), _jsx("button", { onClick: () => {
|