leksy-editor 2.1.4 → 2.2.0
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/README.md +1 -0
- package/constant.js +1 -0
- package/index.js +14 -4
- package/package.json +1 -1
- package/style.css +66 -21
- package/utilities.js +603 -19
package/README.md
CHANGED
|
@@ -108,6 +108,7 @@ const app = createApp({
|
|
|
108
108
|
| `disabled` | To disabled editor |
|
|
109
109
|
| `onFullScreen` | To open custom preview |
|
|
110
110
|
| `showTabs` | Enables or disables the display of tabs. |
|
|
111
|
+
| `enableFindAndReplace` | To enabled find and replace feature|
|
|
111
112
|
|
|
112
113
|
### CSS Customization
|
|
113
114
|
|
package/constant.js
CHANGED
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import './style.css'
|
|
2
2
|
import { CLASSES, CSS, CSS_VARIABLES, ERRORS, REGEX, SVG } from "./constant"
|
|
3
3
|
import PLUGINS, { applyTextFormat } from './plugin';
|
|
4
|
-
import { showAnchorPopover, changeAllToolbarState, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, debounce, destroyImageResizer, destroyTableEditPlugin, initImageResizer, initTableEditPlugin, makeToolbarButton, makeToolbarColor, makeToolbarDropdown, makeToolbarSelect, rgbToHex, updateTableResizerPosition, destroyAnchorPopover, changeToolbarHtmlByName, showRemoteCursor, syncRemoteChangesDebounce, applyRemoteChanges, updateCursorPositionDebounce, buildTributeValues, updateHeight, getTableGrid, getCellPosition, makeUnorderedList, makeOrderedList, makeHeading, makeBlockQuote, makeStrikethrough, makeCodeBlock, makeSublist, showTooltip, makeToolbarButtonSelect, navigateToHeading, updateOutlineItems, highlightActiveOutline } from './utilities';
|
|
4
|
+
import { showAnchorPopover, changeAllToolbarState, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, debounce, destroyImageResizer, destroyTableEditPlugin, initImageResizer, initTableEditPlugin, makeToolbarButton, makeToolbarColor, makeToolbarDropdown, makeToolbarSelect, rgbToHex, updateTableResizerPosition, destroyAnchorPopover, changeToolbarHtmlByName, showRemoteCursor, syncRemoteChangesDebounce, applyRemoteChanges, updateCursorPositionDebounce, buildTributeValues, updateHeight, getTableGrid, getCellPosition, makeUnorderedList, makeOrderedList, makeHeading, makeBlockQuote, makeStrikethrough, makeCodeBlock, makeSublist, showTooltip, makeToolbarButtonSelect, navigateToHeading, updateOutlineItems, highlightActiveOutline, findAndReplace, handleBackspaceInList } from './utilities';
|
|
5
5
|
import { initTabs, findTab, renderTabs, prepareTabs } from './tab';
|
|
6
6
|
import { v4 as uuidv4 } from 'uuid';
|
|
7
7
|
|
|
@@ -32,6 +32,7 @@ class LeksyEditor {
|
|
|
32
32
|
* @property {Boolean} disabled
|
|
33
33
|
* @property {Function} onFullScreen
|
|
34
34
|
* @property {Boolean} showTabs
|
|
35
|
+
* @property {Boolean} enableFindAndReplace
|
|
35
36
|
*/
|
|
36
37
|
/**
|
|
37
38
|
*
|
|
@@ -177,6 +178,9 @@ class LeksyEditor {
|
|
|
177
178
|
syncRemoteChangesDebounce(core)
|
|
178
179
|
updateOutlineItems(core, options)
|
|
179
180
|
},
|
|
181
|
+
onSave:() => {
|
|
182
|
+
if (editorRef.onSave instanceof Function) editorRef.onSave()
|
|
183
|
+
},
|
|
180
184
|
onBlur: (html) => {
|
|
181
185
|
if (editorRef.onBlur instanceof Function) editorRef.onBlur(html)
|
|
182
186
|
},
|
|
@@ -292,6 +296,7 @@ class LeksyEditor {
|
|
|
292
296
|
},
|
|
293
297
|
getContents: () => core.state.tabs,
|
|
294
298
|
onChange: () => { },
|
|
299
|
+
onSave: () => { },
|
|
295
300
|
onBlur: () => { },
|
|
296
301
|
onAttachment: () => { },
|
|
297
302
|
manuplateImage: async () => { },
|
|
@@ -657,7 +662,7 @@ class LeksyEditor {
|
|
|
657
662
|
const isCtrlOrCmd = event.ctrlKey || event.metaKey;
|
|
658
663
|
|
|
659
664
|
// Check for Ctrl + Z (undo) or Ctrl + Y (redo)
|
|
660
|
-
if (isCtrlOrCmd && ['z', 'y', 'b', 'i', 'u'].includes(event.key)) {
|
|
665
|
+
if (isCtrlOrCmd && ['z', 'y', 'b', 'i', 'u', 'f', 'h', 's'].includes(event.key)) {
|
|
661
666
|
return event.preventDefault(); // Prevent the default undo/redo action
|
|
662
667
|
}
|
|
663
668
|
}
|
|
@@ -695,6 +700,10 @@ class LeksyEditor {
|
|
|
695
700
|
applyTextFormat(core, 'underline')
|
|
696
701
|
} else if (event.key === 'i') {
|
|
697
702
|
applyTextFormat(core, 'italic')
|
|
703
|
+
} else if (event.key === 'f' || event.key === 'h'){
|
|
704
|
+
findAndReplace(core, options, event)
|
|
705
|
+
} else if (event.key === 's'){
|
|
706
|
+
core.onSave();
|
|
698
707
|
}
|
|
699
708
|
}
|
|
700
709
|
|
|
@@ -846,6 +855,8 @@ class LeksyEditor {
|
|
|
846
855
|
range.setStart(textNode, textNode.length);
|
|
847
856
|
range.collapse(true);
|
|
848
857
|
}
|
|
858
|
+
|
|
859
|
+
handleBackspaceInList(event, core);
|
|
849
860
|
|
|
850
861
|
if (!isLinkCreated) core.elements.lastCreatedLink = null
|
|
851
862
|
|
|
@@ -996,8 +1007,7 @@ const convertHtmlToText = (html) => {
|
|
|
996
1007
|
const isHTMLEmpty = (html) => {
|
|
997
1008
|
const tempElement = document.createElement('div');
|
|
998
1009
|
tempElement.innerHTML = html
|
|
999
|
-
|
|
1000
|
-
return !tempElement.innerText.trim() && !tempElement.querySelector('img');
|
|
1010
|
+
return !tempElement.innerText.trim() && !tempElement.querySelector('img , ol, ul, blockquote, h1, h2, h3');
|
|
1001
1011
|
}
|
|
1002
1012
|
|
|
1003
1013
|
export default LeksyEditor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leksy-editor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Leksy Editor is an alternative to traditional WYSIWYG editors, designed primarily for creating mail templates, blogs, and documents without any content manipulation.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
package/style.css
CHANGED
|
@@ -472,6 +472,16 @@ button.leksy-editor-popover-tab.active {
|
|
|
472
472
|
background-color: rgba(0, 0, 0, 0.5);
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
+
.leksy-editor-modal.leksy-editor-draggable-modal {
|
|
476
|
+
position: fixed;
|
|
477
|
+
top: 50%;
|
|
478
|
+
left: 50%;
|
|
479
|
+
transform: translate(-50%, -50%);
|
|
480
|
+
background: inherit;
|
|
481
|
+
width: fit-content;
|
|
482
|
+
height: inherit;
|
|
483
|
+
}
|
|
484
|
+
|
|
475
485
|
.leksy-editor-modal-content {
|
|
476
486
|
margin: 10% auto;
|
|
477
487
|
border: 1px solid #898f9f;
|
|
@@ -479,8 +489,13 @@ button.leksy-editor-popover-tab.active {
|
|
|
479
489
|
border-radius: 12px;
|
|
480
490
|
}
|
|
481
491
|
|
|
492
|
+
.leksy-editor-modal.leksy-editor-draggable-modal .leksy-editor-modal-content {
|
|
493
|
+
margin: unset;
|
|
494
|
+
width: fit-content;
|
|
495
|
+
}
|
|
496
|
+
|
|
482
497
|
.leksy-editor-modal-header {
|
|
483
|
-
padding: 1.
|
|
498
|
+
padding: 1.25rem;
|
|
484
499
|
display: flex;
|
|
485
500
|
align-items: center;
|
|
486
501
|
justify-content: space-between;
|
|
@@ -502,7 +517,7 @@ button.leksy-editor-popover-tab.active {
|
|
|
502
517
|
|
|
503
518
|
.leksy-editor-modal-footer {
|
|
504
519
|
background-color: #f2f2f3;
|
|
505
|
-
padding: 1.
|
|
520
|
+
padding: 1.25rem;
|
|
506
521
|
display: flex;
|
|
507
522
|
justify-content: flex-end;
|
|
508
523
|
gap: 12px;
|
|
@@ -622,34 +637,64 @@ button.leksy-editor-popover-tab.active {
|
|
|
622
637
|
}
|
|
623
638
|
|
|
624
639
|
.leksy-editor-upload-img-box {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
640
|
+
height: 180px;
|
|
641
|
+
border: 2px dashed #d1d5db;
|
|
642
|
+
border-radius: 12px;
|
|
643
|
+
background: #f9fafb;
|
|
644
|
+
display: flex;
|
|
645
|
+
align-items: center;
|
|
646
|
+
justify-content: center;
|
|
647
|
+
cursor: pointer;
|
|
648
|
+
transition: border-color 0.2s ease;
|
|
634
649
|
}
|
|
635
650
|
|
|
636
651
|
.leksy-editor-upload-img-box:hover {
|
|
637
|
-
|
|
652
|
+
border-color: gray;
|
|
638
653
|
}
|
|
639
654
|
|
|
640
655
|
.leksy-editor-upload-img-preview-box {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
656
|
+
height: 180px;
|
|
657
|
+
border: 2px solid grey;
|
|
658
|
+
margin-bottom: 8px;
|
|
659
|
+
border-radius: 8px;
|
|
645
660
|
}
|
|
646
661
|
|
|
647
662
|
.leksy-editor-upload-img-preview {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
663
|
+
width: 100%;
|
|
664
|
+
height: 100%;
|
|
665
|
+
border-radius: 8px;
|
|
666
|
+
object-fit: cover;
|
|
667
|
+
object-position: top;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.leksy-editor-find-and-replace-modal-tab-btn {
|
|
671
|
+
background: transparent;
|
|
672
|
+
border: none;
|
|
673
|
+
cursor: pointer;
|
|
674
|
+
font-size: 16px;
|
|
675
|
+
padding: 0;
|
|
676
|
+
color: var(--primary);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.leksy-editor-find-and-replace-modal-tab-btn.active {
|
|
680
|
+
font-weight: bold;
|
|
681
|
+
border-bottom: 2px solid black;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.leksy-editor-find-and-replace-modal-label {
|
|
685
|
+
padding-left: 4px;
|
|
686
|
+
margin-bottom: 10px;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.leksy-editor-find-and-replace-modal-count {
|
|
690
|
+
position: absolute;
|
|
691
|
+
right: 8px;
|
|
692
|
+
top: 50%;
|
|
693
|
+
transform: translateY(-50%);
|
|
694
|
+
font-size: 12px;
|
|
695
|
+
color: #585555;
|
|
696
|
+
pointer-events: none;
|
|
697
|
+
user-select: none;
|
|
653
698
|
}
|
|
654
699
|
|
|
655
700
|
@keyframes leksy-editor-rotate {
|
package/utilities.js
CHANGED
|
@@ -1122,6 +1122,111 @@ const openModal = ({ title, bodyNode, footerNode }, core, options) => {
|
|
|
1122
1122
|
}
|
|
1123
1123
|
}
|
|
1124
1124
|
|
|
1125
|
+
const openDraggableModal = ({ title, bodyNode, footerNode, onClose }, core, options) => {
|
|
1126
|
+
const modalContainer = document.createElement('div');
|
|
1127
|
+
modalContainer.className = `${options.classPrefix}${CLASSES.MODAL} ${options.classPrefix}${CLASSES.DRAGGABLE_MODAL} `
|
|
1128
|
+
|
|
1129
|
+
const modalContent = document.createElement('div');
|
|
1130
|
+
modalContent.className = `${options.classPrefix}${CLASSES.MODAL_CONTENT} `
|
|
1131
|
+
|
|
1132
|
+
const modalHeader = document.createElement('div');
|
|
1133
|
+
modalHeader.className = `${options.classPrefix}${CLASSES.MODAL_HEADER} `
|
|
1134
|
+
if (title instanceof HTMLElement) {
|
|
1135
|
+
modalHeader.appendChild(title);
|
|
1136
|
+
} else {
|
|
1137
|
+
const modalTitle = document.createElement('span');
|
|
1138
|
+
modalTitle.innerText = title ?? ''
|
|
1139
|
+
modalHeader.appendChild(modalTitle);
|
|
1140
|
+
}
|
|
1141
|
+
const modalCloseButton = document.createElement('button');
|
|
1142
|
+
modalCloseButton.className = 'modal-close-btn';
|
|
1143
|
+
modalCloseButton.innerHTML = SVG.CLOSE;
|
|
1144
|
+
modalCloseButton.type = 'button'
|
|
1145
|
+
modalCloseButton.onclick = () => {
|
|
1146
|
+
modalContainer.remove();
|
|
1147
|
+
if (typeof onClose === 'function') onClose()
|
|
1148
|
+
}
|
|
1149
|
+
modalHeader.appendChild(modalCloseButton)
|
|
1150
|
+
|
|
1151
|
+
let isDragging = false;
|
|
1152
|
+
let currentX;
|
|
1153
|
+
let currentY;
|
|
1154
|
+
let initialX;
|
|
1155
|
+
let initialY;
|
|
1156
|
+
let xOffset = 0;
|
|
1157
|
+
let yOffset = 0;
|
|
1158
|
+
|
|
1159
|
+
const dragStart = (e) => {
|
|
1160
|
+
if (e.target.closest('button')) return;
|
|
1161
|
+
|
|
1162
|
+
if (e.type === "touchstart") {
|
|
1163
|
+
initialX = e.touches[0].clientX - xOffset;
|
|
1164
|
+
initialY = e.touches[0].clientY - yOffset;
|
|
1165
|
+
} else {
|
|
1166
|
+
initialX = e.clientX - xOffset;
|
|
1167
|
+
initialY = e.clientY - yOffset;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
isDragging = true;
|
|
1171
|
+
|
|
1172
|
+
document.addEventListener('mouseup', dragEnd);
|
|
1173
|
+
document.addEventListener('touchend', dragEnd);
|
|
1174
|
+
core.elements.iframeWindow.addEventListener('mouseup', dragEnd);
|
|
1175
|
+
core.elements.iframeWindow.addEventListener('touchend', dragEnd);
|
|
1176
|
+
document.addEventListener('mousemove', drag);
|
|
1177
|
+
document.addEventListener('touchmove', drag);
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
const dragEnd = () => {
|
|
1181
|
+
isDragging = false;
|
|
1182
|
+
document.removeEventListener('mouseup', dragEnd, false);
|
|
1183
|
+
document.removeEventListener('touchend', dragEnd, false);
|
|
1184
|
+
core.elements.iframeWindow.removeEventListener('mouseup', dragEnd, false);
|
|
1185
|
+
core.elements.iframeWindow.removeEventListener('touchend', dragEnd, false);
|
|
1186
|
+
document.removeEventListener('mousemove', drag, false);
|
|
1187
|
+
document.removeEventListener('touchmove', drag, false);
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
const drag = (e) => {
|
|
1191
|
+
if (isDragging) {
|
|
1192
|
+
e.preventDefault();
|
|
1193
|
+
currentX = (e.type === "touchmove" ? e.touches[0].clientX : e.clientX) - initialX;
|
|
1194
|
+
currentY = (e.type === "touchmove" ? e.touches[0].clientY : e.clientY) - initialY;
|
|
1195
|
+
|
|
1196
|
+
xOffset = currentX;
|
|
1197
|
+
yOffset = currentY;
|
|
1198
|
+
|
|
1199
|
+
modalContainer.style.transform = `translate(-50%, -50%) translate3d(${currentX}px, ${currentY}px, 0)`;
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
|
|
1203
|
+
modalHeader.addEventListener('mousedown', dragStart, false);
|
|
1204
|
+
modalHeader.addEventListener('touchstart', dragStart, false);
|
|
1205
|
+
|
|
1206
|
+
const modalBody = document.createElement('div');
|
|
1207
|
+
modalBody.className = `${options.classPrefix}${CLASSES.MODAL_BODY} `
|
|
1208
|
+
modalBody.appendChild(bodyNode)
|
|
1209
|
+
|
|
1210
|
+
const modalFooter = document.createElement('div');
|
|
1211
|
+
modalFooter.className = `${options.classPrefix}${CLASSES.MODAL_FOOTER} `
|
|
1212
|
+
modalFooter.appendChild(footerNode)
|
|
1213
|
+
|
|
1214
|
+
modalContent.appendChild(modalHeader);
|
|
1215
|
+
modalContent.appendChild(modalBody);
|
|
1216
|
+
modalContent.appendChild(modalFooter);
|
|
1217
|
+
|
|
1218
|
+
modalContainer.appendChild(modalContent)
|
|
1219
|
+
core.elements.base.appendChild(modalContainer)
|
|
1220
|
+
|
|
1221
|
+
return {
|
|
1222
|
+
close: () => {
|
|
1223
|
+
modalContainer.remove();
|
|
1224
|
+
if (typeof onClose === 'function') onClose()
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
|
|
1125
1230
|
const initImageResizer = (type, image, options, core) => {
|
|
1126
1231
|
destroyImageResizer(options, core)
|
|
1127
1232
|
changeAllToolbarState(core, 'disabled', ['link', 'align_justify', 'align_left', 'align_right', 'align_center'])
|
|
@@ -3061,14 +3166,18 @@ const indentListItem = (li, core) => {
|
|
|
3061
3166
|
const prevLi = li.previousElementSibling;
|
|
3062
3167
|
if (!prevLi) return;
|
|
3063
3168
|
|
|
3064
|
-
const
|
|
3065
|
-
|
|
3169
|
+
const parentList = li.parentElement;
|
|
3170
|
+
const parentTag = parentList.tagName; // UL or OL
|
|
3171
|
+
|
|
3172
|
+
let subList = prevLi.querySelector(`:scope > ${parentTag}`);
|
|
3066
3173
|
|
|
3174
|
+
// create sublist if it doesn't exist
|
|
3067
3175
|
if (!subList) {
|
|
3068
3176
|
subList = document.createElement(parentTag);
|
|
3069
3177
|
prevLi.appendChild(subList);
|
|
3070
3178
|
}
|
|
3071
3179
|
|
|
3180
|
+
// move the li into the sublist
|
|
3072
3181
|
subList.appendChild(li);
|
|
3073
3182
|
const selection = core.elements.iframeWindow.getSelection();
|
|
3074
3183
|
const range = document.createRange();
|
|
@@ -3078,7 +3187,6 @@ const indentListItem = (li, core) => {
|
|
|
3078
3187
|
selection.addRange(range);
|
|
3079
3188
|
core.elements.editor.focus();
|
|
3080
3189
|
core.updateCaretPosition();
|
|
3081
|
-
|
|
3082
3190
|
if (parentTag === 'OL' || parentTag === 'UL') {
|
|
3083
3191
|
const level = getListLevel(li);
|
|
3084
3192
|
applyListStyle(subList, level);
|
|
@@ -3089,13 +3197,12 @@ const outdentListItem = (li, core) => {
|
|
|
3089
3197
|
const parentOl = li.parentElement;
|
|
3090
3198
|
const parentLi = parentOl.closest('li');
|
|
3091
3199
|
if (!parentLi) return;
|
|
3092
|
-
|
|
3093
3200
|
parentLi.after(li);
|
|
3094
3201
|
|
|
3095
3202
|
const selection = core.elements.iframeWindow.getSelection();
|
|
3096
3203
|
const range = document.createRange();
|
|
3097
3204
|
range.selectNodeContents(li);
|
|
3098
|
-
range.collapse(
|
|
3205
|
+
range.collapse(true);
|
|
3099
3206
|
selection.removeAllRanges();
|
|
3100
3207
|
selection.addRange(range);
|
|
3101
3208
|
core.elements.editor.focus();
|
|
@@ -3118,26 +3225,37 @@ const makeSublist = (event, core) => {
|
|
|
3118
3225
|
const selection = core.elements.iframeWindow.getSelection();
|
|
3119
3226
|
if (!selection.rangeCount) return;
|
|
3120
3227
|
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3228
|
+
const range = selection.getRangeAt(0);
|
|
3229
|
+
|
|
3230
|
+
let container = range.commonAncestorContainer;
|
|
3231
|
+
|
|
3232
|
+
if (container.nodeType === Node.TEXT_NODE) {
|
|
3233
|
+
container = container.parentElement;
|
|
3124
3234
|
}
|
|
3235
|
+
// find the nearest list container
|
|
3236
|
+
const list = container.closest('ul, ol');
|
|
3237
|
+
if (!list) return;
|
|
3238
|
+
const allLis = list.querySelectorAll('li');
|
|
3125
3239
|
|
|
3126
|
-
const
|
|
3127
|
-
if (!li) return;
|
|
3240
|
+
const selectedLis = [];
|
|
3128
3241
|
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3242
|
+
allLis.forEach(li => {
|
|
3243
|
+
if (range.intersectsNode(li)) {
|
|
3244
|
+
selectedLis.push(li);
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3247
|
+
|
|
3248
|
+
if (!selectedLis.length) return;
|
|
3133
3249
|
|
|
3134
3250
|
event.preventDefault();
|
|
3135
3251
|
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3252
|
+
selectedLis.forEach(li => {
|
|
3253
|
+
if (event.shiftKey) {
|
|
3254
|
+
outdentListItem(li, core);
|
|
3255
|
+
} else {
|
|
3256
|
+
indentListItem(li, core);
|
|
3257
|
+
}
|
|
3258
|
+
});
|
|
3141
3259
|
};
|
|
3142
3260
|
|
|
3143
3261
|
const transformCase = (text, type) => {
|
|
@@ -3350,6 +3468,470 @@ const updateOutlineItems = debounce((core, options) => {
|
|
|
3350
3468
|
highlightActiveOutline(core, options, outlineContainer);
|
|
3351
3469
|
}, 300)
|
|
3352
3470
|
|
|
3471
|
+
const findAndReplace = (core, options, event) => {
|
|
3472
|
+
if (!options.enableFindAndReplace) return;
|
|
3473
|
+
if (core.state.findAndReplaceModalOpen) return;
|
|
3474
|
+
|
|
3475
|
+
core.state.findAndReplaceModalOpen = true
|
|
3476
|
+
const editor = core.elements.editor;
|
|
3477
|
+
const iframeWindow = core.elements.iframeWindow;
|
|
3478
|
+
|
|
3479
|
+
const headerContainer = document.createElement('div');
|
|
3480
|
+
headerContainer.style.display = 'flex';
|
|
3481
|
+
headerContainer.style.gap = '15px';
|
|
3482
|
+
headerContainer.style.alignItems = 'center';
|
|
3483
|
+
|
|
3484
|
+
const findTab = document.createElement('button');
|
|
3485
|
+
findTab.type = 'button';
|
|
3486
|
+
findTab.innerText = 'Find';
|
|
3487
|
+
findTab.className = 'leksy-editor-find-and-replace-modal-tab-btn';
|
|
3488
|
+
|
|
3489
|
+
const replaceTab = document.createElement('button');
|
|
3490
|
+
replaceTab.type = 'button';
|
|
3491
|
+
replaceTab.innerText = 'Replace';
|
|
3492
|
+
replaceTab.className = 'leksy-editor-find-and-replace-modal-tab-btn';
|
|
3493
|
+
|
|
3494
|
+
headerContainer.append(findTab, replaceTab);
|
|
3495
|
+
|
|
3496
|
+
const body = document.createElement('div');
|
|
3497
|
+
|
|
3498
|
+
const findLabel = document.createElement("label");
|
|
3499
|
+
findLabel.innerText = "Find";
|
|
3500
|
+
findLabel.style.paddingLeft = "4px";
|
|
3501
|
+
|
|
3502
|
+
const findInputWrapper = document.createElement('div');
|
|
3503
|
+
findInputWrapper.style.position = 'relative';
|
|
3504
|
+
findInputWrapper.style.marginBottom = '10px';
|
|
3505
|
+
|
|
3506
|
+
const findInput = document.createElement('input');
|
|
3507
|
+
findInput.type = 'text';
|
|
3508
|
+
findInput.placeholder = 'Search text';
|
|
3509
|
+
findInput.style.width = '100%';
|
|
3510
|
+
findInput.style.boxSizing = 'border-box';
|
|
3511
|
+
findInput.style.paddingRight = '60px';
|
|
3512
|
+
|
|
3513
|
+
const countSpan = document.createElement('span');
|
|
3514
|
+
countSpan.className = 'leksy-editor-find-and-replace-modal-count';
|
|
3515
|
+
|
|
3516
|
+
findInputWrapper.append(findInput, countSpan);
|
|
3517
|
+
|
|
3518
|
+
const replaceLabel = document.createElement("label");
|
|
3519
|
+
replaceLabel.innerText = "Replace with";
|
|
3520
|
+
replaceLabel.style.display = "block";
|
|
3521
|
+
replaceLabel.style.paddingLeft = "4px";
|
|
3522
|
+
const replaceInput = document.createElement('input');
|
|
3523
|
+
replaceInput.type = 'text';
|
|
3524
|
+
replaceInput.placeholder = 'Replace text';
|
|
3525
|
+
replaceInput.style.marginBottom = '10px';
|
|
3526
|
+
|
|
3527
|
+
const optionsContainer = document.createElement('div');
|
|
3528
|
+
optionsContainer.style.marginBottom = '10px';
|
|
3529
|
+
optionsContainer.style.display = 'flex';
|
|
3530
|
+
optionsContainer.style.flexDirection = 'column'
|
|
3531
|
+
|
|
3532
|
+
const matchCaseCheckboxContainer = document.createElement('div');
|
|
3533
|
+
const matchCaseCheckbox = document.createElement('input');
|
|
3534
|
+
matchCaseCheckbox.type = 'checkbox';
|
|
3535
|
+
matchCaseCheckbox.id = 'match-case-opt';
|
|
3536
|
+
matchCaseCheckbox.style.marginRight = '5px';
|
|
3537
|
+
matchCaseCheckbox.style.width = '24px';
|
|
3538
|
+
|
|
3539
|
+
const matchCaseLabel = document.createElement('label');
|
|
3540
|
+
matchCaseLabel.innerText = "Match case";
|
|
3541
|
+
matchCaseLabel.htmlFor = 'match-case-opt';
|
|
3542
|
+
matchCaseLabel.style.userSelect = 'none';
|
|
3543
|
+
matchCaseCheckboxContainer.append(matchCaseCheckbox, matchCaseLabel)
|
|
3544
|
+
|
|
3545
|
+
const wholeWordCheckboxContainer = document.createElement('div');
|
|
3546
|
+
const wholeWordCheckbox = document.createElement('input');
|
|
3547
|
+
wholeWordCheckbox.type = 'checkbox';
|
|
3548
|
+
wholeWordCheckbox.id = 'whole-word-opt';
|
|
3549
|
+
wholeWordCheckbox.style.marginRight = '5px';
|
|
3550
|
+
wholeWordCheckbox.style.width = '24px';
|
|
3551
|
+
|
|
3552
|
+
const wholeWordLabel = document.createElement('label');
|
|
3553
|
+
wholeWordLabel.innerText = "Whole word";
|
|
3554
|
+
wholeWordLabel.htmlFor = 'whole-word-opt';
|
|
3555
|
+
wholeWordLabel.style.userSelect = 'none';
|
|
3556
|
+
wholeWordCheckboxContainer.append(wholeWordCheckbox, wholeWordLabel);
|
|
3557
|
+
|
|
3558
|
+
const regexCheckboxContainer = document.createElement('div');
|
|
3559
|
+
const useRegexCheckbox = document.createElement('input');
|
|
3560
|
+
useRegexCheckbox.type = 'checkbox';
|
|
3561
|
+
useRegexCheckbox.id = 'use-regex-opt';
|
|
3562
|
+
useRegexCheckbox.style.marginRight = '5px';
|
|
3563
|
+
useRegexCheckbox.style.width = '24px';
|
|
3564
|
+
|
|
3565
|
+
const useRegexLabel = document.createElement('label');
|
|
3566
|
+
useRegexLabel.innerText = "Use regular expressions";
|
|
3567
|
+
useRegexLabel.htmlFor = 'use-regex-opt';
|
|
3568
|
+
useRegexLabel.style.userSelect = 'none';
|
|
3569
|
+
regexCheckboxContainer.append(useRegexCheckbox, useRegexLabel)
|
|
3570
|
+
|
|
3571
|
+
optionsContainer.append(matchCaseCheckboxContainer, regexCheckboxContainer, wholeWordCheckboxContainer);
|
|
3572
|
+
|
|
3573
|
+
const span = document.createElement('span');
|
|
3574
|
+
span.className = 'warning';
|
|
3575
|
+
|
|
3576
|
+
body.append(findLabel, findInputWrapper, replaceLabel, replaceInput, optionsContainer, span);
|
|
3577
|
+
|
|
3578
|
+
const footer = document.createElement('div');
|
|
3579
|
+
footer.style.display = 'flex';
|
|
3580
|
+
footer.style.gap = '8px';
|
|
3581
|
+
footer.style.justifyContent = 'flex-end';
|
|
3582
|
+
footer.style.width = '100%';
|
|
3583
|
+
|
|
3584
|
+
const prevBtn = document.createElement('button');
|
|
3585
|
+
prevBtn.type = 'button';
|
|
3586
|
+
prevBtn.className = 'submit';
|
|
3587
|
+
prevBtn.innerText = 'Previous';
|
|
3588
|
+
|
|
3589
|
+
const nextBtn = document.createElement('button');
|
|
3590
|
+
nextBtn.type = 'button';
|
|
3591
|
+
nextBtn.className = 'submit';
|
|
3592
|
+
nextBtn.innerText = 'Next';
|
|
3593
|
+
|
|
3594
|
+
const replaceBtn = document.createElement('button');
|
|
3595
|
+
replaceBtn.type = 'button';
|
|
3596
|
+
replaceBtn.className = 'submit';
|
|
3597
|
+
replaceBtn.innerText = 'Replace';
|
|
3598
|
+
|
|
3599
|
+
const replaceAllBtn = document.createElement('button');
|
|
3600
|
+
replaceAllBtn.type = 'button';
|
|
3601
|
+
replaceAllBtn.className = 'submit';
|
|
3602
|
+
replaceAllBtn.innerText = 'Replace All';
|
|
3603
|
+
|
|
3604
|
+
footer.append(prevBtn, nextBtn, replaceBtn, replaceAllBtn);
|
|
3605
|
+
|
|
3606
|
+
const toggleTab = (mode) => {
|
|
3607
|
+
if (mode === 'find') {
|
|
3608
|
+
replaceLabel.style.display = 'none';
|
|
3609
|
+
replaceInput.style.display = 'none';
|
|
3610
|
+
replaceBtn.style.display = 'none';
|
|
3611
|
+
replaceAllBtn.style.display = 'none';
|
|
3612
|
+
|
|
3613
|
+
findTab.classList.add('active');
|
|
3614
|
+
replaceTab.classList.remove('active');
|
|
3615
|
+
} else {
|
|
3616
|
+
replaceLabel.style.display = 'block';
|
|
3617
|
+
replaceInput.style.display = 'block';
|
|
3618
|
+
replaceBtn.style.display = 'flex';
|
|
3619
|
+
replaceAllBtn.style.display = 'flex';
|
|
3620
|
+
|
|
3621
|
+
replaceTab.classList.add('active');
|
|
3622
|
+
findTab.classList.remove('active');
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
findTab.onclick = () => toggleTab('find');
|
|
3627
|
+
replaceTab.onclick = () => toggleTab('replace');
|
|
3628
|
+
|
|
3629
|
+
openDraggableModal({
|
|
3630
|
+
title: headerContainer,
|
|
3631
|
+
bodyNode: body,
|
|
3632
|
+
footerNode: footer,
|
|
3633
|
+
onClose: () => {
|
|
3634
|
+
core.state.findAndReplaceModalOpen = false
|
|
3635
|
+
}
|
|
3636
|
+
}, core, options);
|
|
3637
|
+
|
|
3638
|
+
if (event.key === 'f')
|
|
3639
|
+
toggleTab('find');
|
|
3640
|
+
else
|
|
3641
|
+
toggleTab('replace');
|
|
3642
|
+
|
|
3643
|
+
|
|
3644
|
+
findInput.focus();
|
|
3645
|
+
|
|
3646
|
+
const find = (direction) => {
|
|
3647
|
+
const searchText = findInput.value;
|
|
3648
|
+
const matchCase = matchCaseCheckbox.checked;
|
|
3649
|
+
const useRegex = useRegexCheckbox.checked;
|
|
3650
|
+
const wholeWord = wholeWordCheckbox.checked;
|
|
3651
|
+
span.innerText = "";
|
|
3652
|
+
countSpan.innerText = "";
|
|
3653
|
+
|
|
3654
|
+
if (!searchText) {
|
|
3655
|
+
span.innerText = "Please enter text to find";
|
|
3656
|
+
return;
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
let textContent = editor.textContent;
|
|
3660
|
+
let matchIndex = -1;
|
|
3661
|
+
let matchLength = 0;
|
|
3662
|
+
|
|
3663
|
+
let searchPattern = searchText;
|
|
3664
|
+
|
|
3665
|
+
if (!useRegex) {
|
|
3666
|
+
const escaped = searchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3667
|
+
if (wholeWord) {
|
|
3668
|
+
searchPattern = `\\b${escaped}\\b`;
|
|
3669
|
+
} else {
|
|
3670
|
+
searchPattern = escaped;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
const selection = iframeWindow.getSelection();
|
|
3675
|
+
|
|
3676
|
+
try {
|
|
3677
|
+
const flags = matchCase ? 'g' : 'gi';
|
|
3678
|
+
const regex = new RegExp(searchPattern, flags);
|
|
3679
|
+
const matches = [...textContent.matchAll(regex)];
|
|
3680
|
+
|
|
3681
|
+
if (matches.length > 0) {
|
|
3682
|
+
let startIndex = 0;
|
|
3683
|
+
if (selection.rangeCount > 0) {
|
|
3684
|
+
const range = selection.getRangeAt(0);
|
|
3685
|
+
if (direction === 'next') {
|
|
3686
|
+
startIndex = getCharIndex(editor, range.endContainer, range.endOffset);
|
|
3687
|
+
} else {
|
|
3688
|
+
startIndex = getCharIndex(editor, range.startContainer, range.startOffset);
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
let match;
|
|
3693
|
+
if (direction === 'next') {
|
|
3694
|
+
match = matches.find(m => m.index >= startIndex);
|
|
3695
|
+
if (!match) match = matches[0];
|
|
3696
|
+
} else {
|
|
3697
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
3698
|
+
if (matches[i].index < startIndex) {
|
|
3699
|
+
match = matches[i];
|
|
3700
|
+
break;
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
if (!match) match = matches[matches.length - 1];
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
if (match) {
|
|
3707
|
+
matchIndex = match.index;
|
|
3708
|
+
matchLength = match[0].length;
|
|
3709
|
+
countSpan.innerText = `${matches.indexOf(match) + 1} of ${matches.length}`;
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
} catch (error) {
|
|
3713
|
+
span.innerText = "Invalid regular expression";
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
if (matchIndex !== -1) {
|
|
3718
|
+
const startNodeInfo = getNodeAndOffsetFromIndex(editor, matchIndex);
|
|
3719
|
+
const endNodeInfo = getNodeAndOffsetFromIndex(editor, matchIndex + matchLength);
|
|
3720
|
+
|
|
3721
|
+
if (startNodeInfo && endNodeInfo) {
|
|
3722
|
+
const newRange = iframeWindow.createRange();
|
|
3723
|
+
newRange.setStart(startNodeInfo.node, startNodeInfo.offset);
|
|
3724
|
+
newRange.setEnd(endNodeInfo.node, endNodeInfo.offset);
|
|
3725
|
+
selection.removeAllRanges();
|
|
3726
|
+
selection.addRange(newRange);
|
|
3727
|
+
|
|
3728
|
+
startNodeInfo.node.parentElement.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
3729
|
+
}
|
|
3730
|
+
} else {
|
|
3731
|
+
countSpan.innerText = "0 of 0";
|
|
3732
|
+
}
|
|
3733
|
+
};
|
|
3734
|
+
|
|
3735
|
+
const replace = () => {
|
|
3736
|
+
const searchText = findInput.value;
|
|
3737
|
+
const replaceText = replaceInput.value;
|
|
3738
|
+
const useRegex = useRegexCheckbox.checked;
|
|
3739
|
+
const matchCase = matchCaseCheckbox.checked;
|
|
3740
|
+
const wholeWord = wholeWordCheckbox.checked;
|
|
3741
|
+
|
|
3742
|
+
if (!searchText) return;
|
|
3743
|
+
|
|
3744
|
+
const selection = iframeWindow.getSelection();
|
|
3745
|
+
if (selection.rangeCount > 0) {
|
|
3746
|
+
const range = selection.getRangeAt(0);
|
|
3747
|
+
const selectedText = range.toString();
|
|
3748
|
+
let shouldReplace = false;
|
|
3749
|
+
let newText = replaceText;
|
|
3750
|
+
|
|
3751
|
+
let searchPattern = searchText;
|
|
3752
|
+
let isRegexSearch = useRegex;
|
|
3753
|
+
|
|
3754
|
+
if (!useRegex && wholeWord) {
|
|
3755
|
+
const escaped = searchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3756
|
+
searchPattern = `\\b${escaped}\\b`;
|
|
3757
|
+
isRegexSearch = true;
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
if (isRegexSearch) {
|
|
3761
|
+
try {
|
|
3762
|
+
const regex = new RegExp(searchPattern, matchCase ? '' : 'i');
|
|
3763
|
+
const match = selectedText.match(regex);
|
|
3764
|
+
if (match && match[0] === selectedText) {
|
|
3765
|
+
shouldReplace = true;
|
|
3766
|
+
newText = selectedText.replace(regex, replaceText);
|
|
3767
|
+
}
|
|
3768
|
+
} catch (e) { }
|
|
3769
|
+
} else if (selectedText === searchText || (!matchCase && selectedText.toLowerCase() === searchText.toLowerCase())) {
|
|
3770
|
+
shouldReplace = true;
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
if (shouldReplace) {
|
|
3774
|
+
iframeWindow.execCommand('insertText', false, newText);
|
|
3775
|
+
find('next');
|
|
3776
|
+
} else {
|
|
3777
|
+
find('next');
|
|
3778
|
+
}
|
|
3779
|
+
} else {
|
|
3780
|
+
find('next');
|
|
3781
|
+
}
|
|
3782
|
+
};
|
|
3783
|
+
|
|
3784
|
+
const replaceAll = () => {
|
|
3785
|
+
const searchText = findInput.value;
|
|
3786
|
+
const replaceText = replaceInput.value;
|
|
3787
|
+
const matchCase = matchCaseCheckbox.checked;
|
|
3788
|
+
const useRegex = useRegexCheckbox.checked;
|
|
3789
|
+
const wholeWord = wholeWordCheckbox.checked;
|
|
3790
|
+
|
|
3791
|
+
if (!searchText) return;
|
|
3792
|
+
|
|
3793
|
+
let count = 0;
|
|
3794
|
+
const selection = iframeWindow.getSelection();
|
|
3795
|
+
|
|
3796
|
+
const range = iframeWindow.createRange();
|
|
3797
|
+
const startNode = getNodeAndOffsetFromIndex(editor, 0);
|
|
3798
|
+
if (startNode) {
|
|
3799
|
+
range.setStart(startNode.node, 0);
|
|
3800
|
+
range.collapse(true);
|
|
3801
|
+
selection.removeAllRanges();
|
|
3802
|
+
selection.addRange(range);
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
while (true) {
|
|
3806
|
+
const currentRange = selection.getRangeAt(0);
|
|
3807
|
+
const searchStart = getCharIndex(editor, currentRange.endContainer, currentRange.endOffset);
|
|
3808
|
+
|
|
3809
|
+
let currentContent = editor.textContent;
|
|
3810
|
+
let matchIndex = -1;
|
|
3811
|
+
let matchLength = 0;
|
|
3812
|
+
let newText = replaceText;
|
|
3813
|
+
|
|
3814
|
+
let searchPattern = searchText;
|
|
3815
|
+
let isRegexSearch = useRegex;
|
|
3816
|
+
|
|
3817
|
+
if (!useRegex && wholeWord) {
|
|
3818
|
+
const escaped = searchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3819
|
+
searchPattern = `\\b${escaped}\\b`;
|
|
3820
|
+
isRegexSearch = true;
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
if (isRegexSearch) {
|
|
3824
|
+
try {
|
|
3825
|
+
const regex = new RegExp(searchPattern, matchCase ? 'g' : 'gi');
|
|
3826
|
+
regex.lastIndex = searchStart;
|
|
3827
|
+
const match = regex.exec(currentContent);
|
|
3828
|
+
if (match) {
|
|
3829
|
+
matchIndex = match.index;
|
|
3830
|
+
matchLength = match[0].length;
|
|
3831
|
+
newText = match[0].replace(new RegExp(searchPattern, matchCase ? '' : 'i'), replaceText);
|
|
3832
|
+
}
|
|
3833
|
+
} catch (e) { break; }
|
|
3834
|
+
} else {
|
|
3835
|
+
let search = searchText;
|
|
3836
|
+
if (!matchCase) {
|
|
3837
|
+
currentContent = currentContent.toLowerCase();
|
|
3838
|
+
search = search.toLowerCase();
|
|
3839
|
+
}
|
|
3840
|
+
matchIndex = currentContent.indexOf(search, searchStart);
|
|
3841
|
+
matchLength = search.length;
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
if (matchIndex === -1) break;
|
|
3845
|
+
|
|
3846
|
+
const startNodeInfo = getNodeAndOffsetFromIndex(editor, matchIndex);
|
|
3847
|
+
const endNodeInfo = getNodeAndOffsetFromIndex(editor, matchIndex + matchLength);
|
|
3848
|
+
|
|
3849
|
+
if (startNodeInfo && endNodeInfo) {
|
|
3850
|
+
const newRange = iframeWindow.createRange();
|
|
3851
|
+
newRange.setStart(startNodeInfo.node, startNodeInfo.offset);
|
|
3852
|
+
newRange.setEnd(endNodeInfo.node, endNodeInfo.offset);
|
|
3853
|
+
selection.removeAllRanges();
|
|
3854
|
+
selection.addRange(newRange);
|
|
3855
|
+
|
|
3856
|
+
iframeWindow.execCommand('insertText', false, newText);
|
|
3857
|
+
count++;
|
|
3858
|
+
} else {
|
|
3859
|
+
break;
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
span.innerText = `Replaced ${count} occurrences.`;
|
|
3863
|
+
};
|
|
3864
|
+
|
|
3865
|
+
prevBtn.onclick = () => find('prev');
|
|
3866
|
+
nextBtn.onclick = () => find('next');
|
|
3867
|
+
replaceBtn.onclick = replace;
|
|
3868
|
+
replaceAllBtn.onclick = replaceAll;
|
|
3869
|
+
|
|
3870
|
+
const onInputKeydown = (event) => {
|
|
3871
|
+
if (event.key === 'Enter') {
|
|
3872
|
+
event.preventDefault();
|
|
3873
|
+
find('next');
|
|
3874
|
+
}
|
|
3875
|
+
};
|
|
3876
|
+
findInput.addEventListener('keydown', onInputKeydown);
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
const isCaretAtStart = (range, li) => {
|
|
3880
|
+
const preRange = range.cloneRange();
|
|
3881
|
+
preRange.selectNodeContents(li);
|
|
3882
|
+
preRange.setEnd(range.startContainer, range.startOffset);
|
|
3883
|
+
|
|
3884
|
+
return preRange.toString().length === 0;
|
|
3885
|
+
};
|
|
3886
|
+
const handleBackspaceInList = (event, core) => {
|
|
3887
|
+
if (event.key !== 'Backspace') return;
|
|
3888
|
+
|
|
3889
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3890
|
+
if (!selection.rangeCount) return;
|
|
3891
|
+
const range = selection.getRangeAt(0);
|
|
3892
|
+
|
|
3893
|
+
if (!range.collapsed) return;
|
|
3894
|
+
|
|
3895
|
+
let element = range.startContainer;
|
|
3896
|
+
if (element.nodeType === Node.TEXT_NODE) element = element.parentElement;
|
|
3897
|
+
|
|
3898
|
+
const li = element.closest('li');
|
|
3899
|
+
if (!li) return;
|
|
3900
|
+
|
|
3901
|
+
if (!core.elements.editor.contains(li)) return;
|
|
3902
|
+
|
|
3903
|
+
const nestedList = li.querySelector('ul, ol');
|
|
3904
|
+
if (!nestedList) return;
|
|
3905
|
+
|
|
3906
|
+
const clone = li.cloneNode(true);
|
|
3907
|
+
const nestedInClone = clone.querySelector('ul, ol');
|
|
3908
|
+
if (nestedInClone) nestedInClone.remove();
|
|
3909
|
+
const textContent = clone.textContent.replace(/\u200B/g, '').trim();
|
|
3910
|
+
|
|
3911
|
+
if (textContent.length > 0 && !isCaretAtStart(range, li)) return;
|
|
3912
|
+
|
|
3913
|
+
event.preventDefault();
|
|
3914
|
+
|
|
3915
|
+
const parentList = li.parentElement;
|
|
3916
|
+
const fragment = document.createDocumentFragment();
|
|
3917
|
+
const children = Array.from(nestedList.children);
|
|
3918
|
+
|
|
3919
|
+
children.forEach(child => fragment.appendChild(child));
|
|
3920
|
+
|
|
3921
|
+
if (children.length > 0) {
|
|
3922
|
+
parentList.insertBefore(fragment, li);
|
|
3923
|
+
const firstChild = children[0];
|
|
3924
|
+
const newRange = document.createRange();
|
|
3925
|
+
newRange.setStart(firstChild, 0);
|
|
3926
|
+
newRange.collapse(true);
|
|
3927
|
+
selection.removeAllRanges();
|
|
3928
|
+
selection.addRange(newRange);
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
li.remove();
|
|
3932
|
+
core.updateCaretPosition();
|
|
3933
|
+
};
|
|
3934
|
+
|
|
3353
3935
|
export {
|
|
3354
3936
|
cleanHTML,
|
|
3355
3937
|
debounce,
|
|
@@ -3398,4 +3980,6 @@ export {
|
|
|
3398
3980
|
navigateToHeading,
|
|
3399
3981
|
updateOutlineItems,
|
|
3400
3982
|
highlightActiveOutline,
|
|
3983
|
+
findAndReplace,
|
|
3984
|
+
handleBackspaceInList,
|
|
3401
3985
|
}
|