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