react-lite-rich-text-editor 1.1.5 → 1.1.6
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/index.cjs.js +107 -8
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +107 -8
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -427,6 +427,7 @@ function RichTextEditor({
|
|
|
427
427
|
const [editable, setEditable] = useState(initialEditable);
|
|
428
428
|
const [editorFocused, setEditorFocused] = useState(false);
|
|
429
429
|
const lastSynchronizedHtmlRef = useRef("");
|
|
430
|
+
const syncProcessedMediaRef = useRef(() => {});
|
|
430
431
|
useEffect(() => {
|
|
431
432
|
setEditable(initialEditable);
|
|
432
433
|
}, [initialEditable]);
|
|
@@ -533,7 +534,7 @@ function RichTextEditor({
|
|
|
533
534
|
}, [imageModalOpen]);
|
|
534
535
|
useEffect(() => {
|
|
535
536
|
if (editorRef.current && value && value !== lastSynchronizedHtmlRef.current) {
|
|
536
|
-
requestAnimationFrame(() =>
|
|
537
|
+
requestAnimationFrame(() => syncProcessedMediaRef.current(editorRef.current));
|
|
537
538
|
}
|
|
538
539
|
}, [value]);
|
|
539
540
|
|
|
@@ -542,7 +543,7 @@ function RichTextEditor({
|
|
|
542
543
|
if (!editable) {
|
|
543
544
|
setEditorFocused(false);
|
|
544
545
|
}
|
|
545
|
-
|
|
546
|
+
syncProcessedMediaRef.current(editorRef.current);
|
|
546
547
|
}, [editable]);
|
|
547
548
|
useEffect(() => {
|
|
548
549
|
if (!editorRef.current) return;
|
|
@@ -573,7 +574,7 @@ function RichTextEditor({
|
|
|
573
574
|
if (editorRef.current && editorRef.current.innerHTML !== newContent) {
|
|
574
575
|
editorRef.current.innerHTML = newContent;
|
|
575
576
|
}
|
|
576
|
-
requestAnimationFrame(() =>
|
|
577
|
+
requestAnimationFrame(() => syncProcessedMediaRef.current(editorRef.current));
|
|
577
578
|
updateMetrics();
|
|
578
579
|
}
|
|
579
580
|
} catch (e) {
|
|
@@ -588,10 +589,47 @@ function RichTextEditor({
|
|
|
588
589
|
}
|
|
589
590
|
}
|
|
590
591
|
}, [value, initialEditable, updateMetrics]);
|
|
592
|
+
const LIST_BLOCK_MEDIA_SELECTOR = ".video-container, .image-container, table";
|
|
593
|
+
const isListItemEffectivelyEmpty = listItem => {
|
|
594
|
+
if (!listItem) return true;
|
|
595
|
+
const clone = listItem.cloneNode(true);
|
|
596
|
+
clone.querySelectorAll(LIST_BLOCK_MEDIA_SELECTOR).forEach(el => el.remove());
|
|
597
|
+
clone.querySelectorAll("br").forEach(el => el.remove());
|
|
598
|
+
return clone.textContent.replace(/[\u200B\u00A0\s]/g, "").length === 0;
|
|
599
|
+
};
|
|
600
|
+
const hoistBlockMediaOutOfListItems = container => {
|
|
601
|
+
if (!container) return false;
|
|
602
|
+
let changed = false;
|
|
603
|
+
container.querySelectorAll("ol, ul").forEach(list => {
|
|
604
|
+
const items = Array.from(list.children).filter(child => child.tagName === "LI");
|
|
605
|
+
items.forEach(listItem => {
|
|
606
|
+
const blockMedia = Array.from(listItem.querySelectorAll(LIST_BLOCK_MEDIA_SELECTOR));
|
|
607
|
+
if (blockMedia.length === 0) return;
|
|
608
|
+
const hadText = !isListItemEffectivelyEmpty(listItem);
|
|
609
|
+
blockMedia.forEach(media => {
|
|
610
|
+
listItem.removeChild(media);
|
|
611
|
+
if (list.parentNode) {
|
|
612
|
+
list.parentNode.insertBefore(media, list.nextSibling);
|
|
613
|
+
}
|
|
614
|
+
changed = true;
|
|
615
|
+
});
|
|
616
|
+
if (!hadText || isListItemEffectivelyEmpty(listItem)) {
|
|
617
|
+
listItem.remove();
|
|
618
|
+
changed = true;
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
if (list.children.length === 0 && list.parentNode) {
|
|
622
|
+
list.remove();
|
|
623
|
+
changed = true;
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
return changed;
|
|
627
|
+
};
|
|
591
628
|
const processExistingMedia = container => {
|
|
592
|
-
if (!container) return;
|
|
629
|
+
if (!container) return false;
|
|
593
630
|
processExistingImages(container);
|
|
594
631
|
processExistingVideos(container);
|
|
632
|
+
return hoistBlockMediaOutOfListItems(container);
|
|
595
633
|
};
|
|
596
634
|
const getCleanHtml = () => {
|
|
597
635
|
if (!editorRef.current) return "";
|
|
@@ -607,6 +645,11 @@ function RichTextEditor({
|
|
|
607
645
|
lastSynchronizedHtmlRef.current = next;
|
|
608
646
|
onChange && onChange(next);
|
|
609
647
|
}, [onChange]);
|
|
648
|
+
syncProcessedMediaRef.current = container => {
|
|
649
|
+
if (processExistingMedia(container)) {
|
|
650
|
+
triggerChange();
|
|
651
|
+
}
|
|
652
|
+
};
|
|
610
653
|
|
|
611
654
|
// Helper to walk up DOM to find style tags or CSS style:
|
|
612
655
|
const isParentStyle = (node, ...tagNames) => {
|
|
@@ -1126,8 +1169,10 @@ function RichTextEditor({
|
|
|
1126
1169
|
}
|
|
1127
1170
|
setVideoModalOpen(false);
|
|
1128
1171
|
setVideoUrl("");
|
|
1129
|
-
|
|
1130
|
-
|
|
1172
|
+
requestAnimationFrame(() => {
|
|
1173
|
+
processExistingMedia(editorRef.current);
|
|
1174
|
+
triggerChange();
|
|
1175
|
+
});
|
|
1131
1176
|
} else {
|
|
1132
1177
|
console.warn("Invalid Video URL or Platform not supported");
|
|
1133
1178
|
}
|
|
@@ -1374,6 +1419,12 @@ function RichTextEditor({
|
|
|
1374
1419
|
txt.innerHTML = html;
|
|
1375
1420
|
return txt.value;
|
|
1376
1421
|
};
|
|
1422
|
+
const isCursorAtStartOfListItem = (range, listItem) => {
|
|
1423
|
+
const prefixRange = document.createRange();
|
|
1424
|
+
prefixRange.setStart(listItem, 0);
|
|
1425
|
+
prefixRange.setEnd(range.startContainer, range.startOffset);
|
|
1426
|
+
return prefixRange.toString().replace(/[\u200B\u00A0\s]/g, "").length === 0;
|
|
1427
|
+
};
|
|
1377
1428
|
const isCursorAtEndOfListItem = (range, listItem) => {
|
|
1378
1429
|
const suffixRange = document.createRange();
|
|
1379
1430
|
suffixRange.setStart(range.startContainer, range.startOffset);
|
|
@@ -1418,7 +1469,7 @@ function RichTextEditor({
|
|
|
1418
1469
|
// If we're at the end of a list item, add a new one
|
|
1419
1470
|
if (range.collapsed && isCursorAtEndOfListItem(range, listItem)) {
|
|
1420
1471
|
// If it's empty, create a regular paragraph instead
|
|
1421
|
-
if (listItem
|
|
1472
|
+
if (isListItemEffectivelyEmpty(listItem)) {
|
|
1422
1473
|
document.execCommand('insertHTML', false, '<div><br></div>');
|
|
1423
1474
|
// Move the cursor to the new line
|
|
1424
1475
|
const newRange = document.createRange();
|
|
@@ -1450,7 +1501,7 @@ function RichTextEditor({
|
|
|
1450
1501
|
} else {
|
|
1451
1502
|
list.appendChild(newItem);
|
|
1452
1503
|
}
|
|
1453
|
-
if (
|
|
1504
|
+
if (isListItemEffectivelyEmpty(newItem)) {
|
|
1454
1505
|
newItem.textContent = "";
|
|
1455
1506
|
prepareListItemForTyping(newItem, selection);
|
|
1456
1507
|
} else {
|
|
@@ -1468,6 +1519,54 @@ function RichTextEditor({
|
|
|
1468
1519
|
triggerChange();
|
|
1469
1520
|
return;
|
|
1470
1521
|
}
|
|
1522
|
+
if (e.key === "Backspace") {
|
|
1523
|
+
var _node$closest, _node, _editorRef$current3;
|
|
1524
|
+
const selection = window.getSelection();
|
|
1525
|
+
if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
|
|
1526
|
+
const range = selection.getRangeAt(0);
|
|
1527
|
+
if (!range.collapsed) return;
|
|
1528
|
+
let node = range.startContainer;
|
|
1529
|
+
if (node.nodeType === 3) {
|
|
1530
|
+
node = node.parentNode;
|
|
1531
|
+
}
|
|
1532
|
+
const listItem = (_node$closest = (_node = node).closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(_node, "li");
|
|
1533
|
+
if (!listItem || !((_editorRef$current3 = editorRef.current) !== null && _editorRef$current3 !== void 0 && _editorRef$current3.contains(listItem))) return;
|
|
1534
|
+
const list = listItem.parentNode;
|
|
1535
|
+
if (isListItemEffectivelyEmpty(listItem)) {
|
|
1536
|
+
e.preventDefault();
|
|
1537
|
+
const prevLi = listItem.previousElementSibling;
|
|
1538
|
+
const blockMedia = Array.from(listItem.querySelectorAll(LIST_BLOCK_MEDIA_SELECTOR));
|
|
1539
|
+
blockMedia.forEach(media => {
|
|
1540
|
+
var _list$parentNode;
|
|
1541
|
+
(_list$parentNode = list.parentNode) === null || _list$parentNode === void 0 || _list$parentNode.insertBefore(media, list.nextSibling);
|
|
1542
|
+
});
|
|
1543
|
+
listItem.remove();
|
|
1544
|
+
if (list.children.length === 0) {
|
|
1545
|
+
list.remove();
|
|
1546
|
+
}
|
|
1547
|
+
if ((prevLi === null || prevLi === void 0 ? void 0 : prevLi.tagName) === "LI") {
|
|
1548
|
+
const newRange = document.createRange();
|
|
1549
|
+
newRange.selectNodeContents(prevLi);
|
|
1550
|
+
newRange.collapse(false);
|
|
1551
|
+
selection.removeAllRanges();
|
|
1552
|
+
selection.addRange(newRange);
|
|
1553
|
+
}
|
|
1554
|
+
triggerChange();
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
if (isCursorAtStartOfListItem(range, listItem)) {
|
|
1558
|
+
const prevLi = listItem.previousElementSibling;
|
|
1559
|
+
if ((prevLi === null || prevLi === void 0 ? void 0 : prevLi.tagName) === "LI" && isListItemEffectivelyEmpty(prevLi)) {
|
|
1560
|
+
e.preventDefault();
|
|
1561
|
+
prevLi.remove();
|
|
1562
|
+
if (list.children.length === 0) {
|
|
1563
|
+
list.remove();
|
|
1564
|
+
}
|
|
1565
|
+
triggerChange();
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1471
1570
|
|
|
1472
1571
|
// Handle Ctrl/Cmd + B/I/U for bold/italic/underline
|
|
1473
1572
|
if ((e.ctrlKey || e.metaKey) && e.key === "b") {
|