leksy-editor 2.4.0 → 2.5.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 +5 -4
- package/constant.js +2 -0
- package/index.js +7 -3
- package/package.json +1 -1
- package/plugin.js +25 -18
- package/utilities.js +281 -11
package/README.md
CHANGED
|
@@ -110,6 +110,7 @@ const app = createApp({
|
|
|
110
110
|
| `showTabs` | Enables or disables the display of tabs. |
|
|
111
111
|
| `enableFindAndReplace` | To enabled find and replace feature|
|
|
112
112
|
| `customTooltip` | Customize tooltip according to your need, set using key-value pair|
|
|
113
|
+
| `customIcon` | Customize icon according to your need, set using key-value pair|
|
|
113
114
|
|
|
114
115
|
### CSS Customization
|
|
115
116
|
|
|
@@ -177,10 +178,10 @@ Similarly, Pexels and Tenor can be integrated using `pexels` and `tenor` plugins
|
|
|
177
178
|
|
|
178
179
|
| Function | Description |
|
|
179
180
|
|----------|-------------|
|
|
180
|
-
| `setContents(html)` | Sets the editor's content. |
|
|
181
|
-
| `getContents()` | Returns the
|
|
182
|
-
| `onChange(
|
|
183
|
-
| `onBlur(
|
|
181
|
+
| `setContents(tabs || html)` | Sets the editor's content. |
|
|
182
|
+
| `getContents()` | Returns the array of tabs containing HTML content of the editor. |
|
|
183
|
+
| `onChange(tabs)` | Triggered when content changes. |
|
|
184
|
+
| `onBlur(tabs)` | Triggered when the editor loses focus. |
|
|
184
185
|
| `onAttachment(files)` | Fires when files are attached. |
|
|
185
186
|
| `manipulateImage()` | Custom function for manipulating images. |
|
|
186
187
|
| `handleFilePicker()` | Custom function for file upload, useful for Android and iOS file upload. |
|
package/constant.js
CHANGED
|
@@ -932,6 +932,7 @@ const UNORDERED_LIST_OPTIONS = {
|
|
|
932
932
|
|
|
933
933
|
const RESIZE_MARGIN = 10;
|
|
934
934
|
|
|
935
|
+
const BLOCK_FORMAT_TAGS = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P'];
|
|
935
936
|
const LIST_STYLES_BY_LEVEL = ['1', 'a', 'i', 'A'];
|
|
936
937
|
const UNORDERED_LIST_STYLES_BY_LEVEL = ['disc', 'circle', 'square'];
|
|
937
938
|
|
|
@@ -958,6 +959,7 @@ export {
|
|
|
958
959
|
CSS_VARIABLES,
|
|
959
960
|
TAB_CATEGORIES,
|
|
960
961
|
RESIZE_MARGIN,
|
|
962
|
+
BLOCK_FORMAT_TAGS,
|
|
961
963
|
LIST_STYLES_BY_LEVEL,
|
|
962
964
|
UNORDERED_LIST_STYLES_BY_LEVEL,
|
|
963
965
|
UNORDERED_LIST_OPTIONS,
|
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, findAndReplace, handleBackspaceInList } 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, trackListSelectionDeletion, cleanupListSelectionDeletion } from './utilities';
|
|
5
5
|
import { initTabs, findTab, renderTabs, prepareTabs } from './tab';
|
|
6
6
|
import { v4 as uuidv4 } from 'uuid';
|
|
7
7
|
|
|
@@ -32,7 +32,9 @@ class LeksyEditor {
|
|
|
32
32
|
* @property {Boolean} disabled
|
|
33
33
|
* @property {Function} onFullScreen
|
|
34
34
|
* @property {Boolean} showTabs
|
|
35
|
-
* @property {Boolean} enableFindAndReplace
|
|
35
|
+
* @property {Boolean} enableFindAndReplace
|
|
36
|
+
* @property {Array<Object>} customTooltip
|
|
37
|
+
* @property {Array<Object>} customIcon
|
|
36
38
|
*/
|
|
37
39
|
/**
|
|
38
40
|
*
|
|
@@ -675,6 +677,7 @@ class LeksyEditor {
|
|
|
675
677
|
contentEditableDiv.className = 'content-editable';
|
|
676
678
|
|
|
677
679
|
contentEditableDiv.oninput = (e) => {
|
|
680
|
+
cleanupListSelectionDeletion(core);
|
|
678
681
|
core.onChange(e.target.innerHTML)
|
|
679
682
|
}
|
|
680
683
|
contentEditableDiv.onbeforeinput = (e) => {
|
|
@@ -858,6 +861,7 @@ class LeksyEditor {
|
|
|
858
861
|
}
|
|
859
862
|
|
|
860
863
|
handleBackspaceInList(event, core);
|
|
864
|
+
trackListSelectionDeletion(event, core);
|
|
861
865
|
|
|
862
866
|
if (!isLinkCreated) core.elements.lastCreatedLink = null
|
|
863
867
|
|
|
@@ -1015,4 +1019,4 @@ export default LeksyEditor
|
|
|
1015
1019
|
export {
|
|
1016
1020
|
convertHtmlToText,
|
|
1017
1021
|
isHTMLEmpty,
|
|
1018
|
-
}
|
|
1022
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leksy-editor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.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/plugin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EMOJI_CATEGORIES, FONT_SIZE_OPTIONS, FONTS, FORMAT_OPTIONS, MIMETYPE, REGEX, SPECIAL_CHARACTERS, SVG, UNORDERED_LIST_OPTIONS, ORDERED_LIST_OPTIONS, CLASSES } from "./constant"
|
|
2
2
|
import { giphy, pexels, tenor } from "./gallery";
|
|
3
|
-
import { formatLink, changeAllToolbarState, changeToolbarHtmlByName, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, constructEmbedUrl, extractSocialMediaId, isLinkValid, openModal, transformTextStyle, insertTOC, } from "./utilities";
|
|
3
|
+
import { formatLink, changeAllToolbarState, changeToolbarHtmlByName, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, constructEmbedUrl, extractSocialMediaId, isLinkValid, openModal, transformTextStyle, insertTOC, applyFormatBlockInListItem, normalizeHeadingWrappedLists, } from "./utilities";
|
|
4
4
|
|
|
5
5
|
const applyTextFormat = (core, command) => {
|
|
6
6
|
core.elements.editor.focus();
|
|
@@ -12,27 +12,28 @@ const applyTextFormat = (core, command) => {
|
|
|
12
12
|
else changeToolbarStateByName(core, 'inactive', [command])
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
core.elements.iframeWindow.execCommand(
|
|
15
|
+
const applyList = (core, { command, activePlugin, inactivePlugin }) => {
|
|
16
|
+
core.elements.iframeWindow.execCommand(command);
|
|
17
|
+
normalizeHeadingWrappedLists(core);
|
|
17
18
|
core.elements.editor.focus();
|
|
18
19
|
core.updateCaretPosition()
|
|
19
20
|
|
|
20
|
-
const isActive = core.elements.iframeWindow.queryCommandState(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
changeToolbarStateByName(core, 'inactive', ['unordered_list'])
|
|
21
|
+
const isActive = core.elements.iframeWindow.queryCommandState(command);
|
|
22
|
+
changeToolbarStateByName(core, isActive ? 'active' : 'inactive', [activePlugin])
|
|
23
|
+
changeToolbarStateByName(core, 'inactive', [inactivePlugin])
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const applyOrderList = (core) => applyList(core, {
|
|
27
|
+
command: 'insertOrderedList',
|
|
28
|
+
activePlugin: 'ordered_list',
|
|
29
|
+
inactivePlugin: 'unordered_list'
|
|
30
|
+
})
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
32
|
+
const applyUnorderedList = (core) => applyList(core, {
|
|
33
|
+
command: 'insertUnorderedList',
|
|
34
|
+
activePlugin: 'unordered_list',
|
|
35
|
+
inactivePlugin: 'ordered_list'
|
|
36
|
+
})
|
|
36
37
|
|
|
37
38
|
const PLUGINS = {
|
|
38
39
|
'undo': {
|
|
@@ -258,7 +259,11 @@ const PLUGINS = {
|
|
|
258
259
|
width: '110px',
|
|
259
260
|
click: (tag, core, options) => {
|
|
260
261
|
core.elements.editor.focus();
|
|
261
|
-
core
|
|
262
|
+
const isFormattedInList = applyFormatBlockInListItem(core, tag);
|
|
263
|
+
if (!isFormattedInList) {
|
|
264
|
+
core.elements.iframeWindow.execCommand('formatBlock', false, tag);
|
|
265
|
+
}
|
|
266
|
+
normalizeHeadingWrappedLists(core);
|
|
262
267
|
|
|
263
268
|
core.updateCaretPosition()
|
|
264
269
|
changeToolbarValueByName(core, 'format-block', tag)
|
|
@@ -276,6 +281,7 @@ const PLUGINS = {
|
|
|
276
281
|
const isActive = core.elements.iframeWindow.queryCommandState('insertOrderedList');
|
|
277
282
|
if (!isActive) {
|
|
278
283
|
core.elements.iframeWindow.execCommand('insertOrderedList');
|
|
284
|
+
normalizeHeadingWrappedLists(core);
|
|
279
285
|
}
|
|
280
286
|
const selection = core.elements.iframeWindow.getSelection();
|
|
281
287
|
if (selection.rangeCount > 0) {
|
|
@@ -308,6 +314,7 @@ const PLUGINS = {
|
|
|
308
314
|
const isActive = core.elements.iframeWindow.queryCommandState('insertUnorderedList');
|
|
309
315
|
if (!isActive) {
|
|
310
316
|
core.elements.iframeWindow.execCommand('insertUnorderedList');
|
|
317
|
+
normalizeHeadingWrappedLists(core);
|
|
311
318
|
}
|
|
312
319
|
const selection = core.elements.iframeWindow.getSelection();
|
|
313
320
|
if (selection.rangeCount > 0) {
|
|
@@ -1345,4 +1352,4 @@ export {
|
|
|
1345
1352
|
applyTextFormat,
|
|
1346
1353
|
applyOrderList,
|
|
1347
1354
|
applyUnorderedList
|
|
1348
|
-
}
|
|
1355
|
+
}
|
package/utilities.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CLASSES, FONT_SIZES, FONTS, FORMATS, GIPHY_POWERED_IMAGE, REGEX, RESIZER_PLUGINS, SOCIAL_MEDIA_BASEURLS, SOCIAL_MEDIA_PATTERNS, SVG, TABLE_PLUGINS, TAB_CATEGORIES, RESIZE_MARGIN, LIST_STYLES_BY_LEVEL, UNORDERED_LIST_STYLES_BY_LEVEL } from "./constant";
|
|
1
|
+
import { BLOCK_FORMAT_TAGS, CLASSES, FONT_SIZES, FONTS, FORMATS, GIPHY_POWERED_IMAGE, REGEX, RESIZER_PLUGINS, SOCIAL_MEDIA_BASEURLS, SOCIAL_MEDIA_PATTERNS, SVG, TABLE_PLUGINS, TAB_CATEGORIES, RESIZE_MARGIN, LIST_STYLES_BY_LEVEL, UNORDERED_LIST_STYLES_BY_LEVEL } from "./constant";
|
|
2
2
|
import { DiffDOM } from 'diff-dom';
|
|
3
|
-
import { applyUnorderedList, applyOrderList } from "./plugin";
|
|
4
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
4
|
import { findTab } from "./tab";
|
|
5
|
+
import { applyOrderList, applyUnorderedList } from "./plugin";
|
|
6
6
|
|
|
7
7
|
const dd = new DiffDOM();
|
|
8
8
|
|
|
@@ -204,6 +204,10 @@ const getTooltip = (plugin, options) => {
|
|
|
204
204
|
return options?.customTooltip?.[plugin.pluginId] ?? plugin.title
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
const getToolbarIcon = (plugin, options) => {
|
|
208
|
+
return options?.customIcon?.[plugin.pluginId] ?? plugin.icon
|
|
209
|
+
}
|
|
210
|
+
|
|
207
211
|
const makeToolbarButton = (_plugin, options, core) => {
|
|
208
212
|
const pluginButton = document.createElement('button');
|
|
209
213
|
pluginButton.type = "button"
|
|
@@ -217,7 +221,7 @@ const makeToolbarButton = (_plugin, options, core) => {
|
|
|
217
221
|
};
|
|
218
222
|
|
|
219
223
|
const pluginIcon = document.createElement('div');
|
|
220
|
-
pluginIcon.innerHTML = _plugin
|
|
224
|
+
pluginIcon.innerHTML = getToolbarIcon(_plugin, options);
|
|
221
225
|
pluginButton.appendChild(pluginIcon)
|
|
222
226
|
return pluginButton
|
|
223
227
|
}
|
|
@@ -236,7 +240,7 @@ const makeToolbarButtonSelect = (_plugin, options, core) => {
|
|
|
236
240
|
};
|
|
237
241
|
|
|
238
242
|
const pluginIcon = document.createElement('div');
|
|
239
|
-
pluginIcon.innerHTML = _plugin
|
|
243
|
+
pluginIcon.innerHTML = getToolbarIcon(_plugin, options);
|
|
240
244
|
pluginButton.appendChild(pluginIcon)
|
|
241
245
|
const pluginSelect = makeToolbarSelect({ ..._plugin, icon: '' }, options, core);
|
|
242
246
|
|
|
@@ -265,7 +269,7 @@ const makeToolbarColor = (_plugin, options, core) => {
|
|
|
265
269
|
};
|
|
266
270
|
|
|
267
271
|
const pluginIcon = document.createElement('div');
|
|
268
|
-
pluginIcon.innerHTML = _plugin
|
|
272
|
+
pluginIcon.innerHTML = getToolbarIcon(_plugin, options);
|
|
269
273
|
pluginIcon.onclick = () => pluginButton.click()
|
|
270
274
|
pluginContainer.append(pluginButton, pluginIcon)
|
|
271
275
|
return pluginContainer
|
|
@@ -348,7 +352,7 @@ const makeToolbarDropdown = (_plugin, options, core) => {
|
|
|
348
352
|
dropdownButton.setAttribute('data-title', getTooltip(_plugin, options))
|
|
349
353
|
|
|
350
354
|
const pluginIcon = document.createElement('div');
|
|
351
|
-
pluginIcon.innerHTML = _plugin
|
|
355
|
+
pluginIcon.innerHTML = getToolbarIcon(_plugin, options);
|
|
352
356
|
dropdownButton.appendChild(pluginIcon)
|
|
353
357
|
|
|
354
358
|
const dropdownContent = document.createElement('div');
|
|
@@ -597,7 +601,7 @@ const makeToolbarSelect = (_plugin, options, core) => {
|
|
|
597
601
|
pluginIcon.style.justifyContent = 'space-between'
|
|
598
602
|
|
|
599
603
|
const iconName = document.createElement('span');
|
|
600
|
-
iconName.innerHTML = _plugin
|
|
604
|
+
iconName.innerHTML = getToolbarIcon(_plugin, options);
|
|
601
605
|
|
|
602
606
|
const arrow = document.createElement('span');
|
|
603
607
|
if (_plugin.type == 'button-select') {
|
|
@@ -2870,6 +2874,98 @@ const isInsideTableCell = (node) => {
|
|
|
2870
2874
|
return element?.closest('td, th');
|
|
2871
2875
|
};
|
|
2872
2876
|
|
|
2877
|
+
const formatListItemContent = (li, tag) => {
|
|
2878
|
+
const formattedBlock = document.createElement(tag);
|
|
2879
|
+
const childNodes = Array.from(li.childNodes);
|
|
2880
|
+
|
|
2881
|
+
childNodes.forEach(child => {
|
|
2882
|
+
if (child.nodeType === Node.ELEMENT_NODE && ['UL', 'OL'].includes(child.tagName)) return;
|
|
2883
|
+
formattedBlock.appendChild(child);
|
|
2884
|
+
});
|
|
2885
|
+
|
|
2886
|
+
if (!formattedBlock.childNodes.length) formattedBlock.innerHTML = '<br>';
|
|
2887
|
+
|
|
2888
|
+
const firstNestedList = Array.from(li.children).find(child => ['UL', 'OL'].includes(child.tagName));
|
|
2889
|
+
li.insertBefore(formattedBlock, firstNestedList || null);
|
|
2890
|
+
|
|
2891
|
+
return formattedBlock;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
const applyFormatBlockInListItem = (core, tag) => {
|
|
2895
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
2896
|
+
const node = selection?.rangeCount ? selection.getRangeAt(0).startContainer : null;
|
|
2897
|
+
const element = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement;
|
|
2898
|
+
const li = element?.closest('li');
|
|
2899
|
+
|
|
2900
|
+
if (!li || !core.elements.editor.contains(li)) return false;
|
|
2901
|
+
|
|
2902
|
+
const currentBlock = element.closest(BLOCK_FORMAT_TAGS.join(','));
|
|
2903
|
+
const isCurrentBlockInLi = currentBlock && currentBlock.closest('li') === li;
|
|
2904
|
+
let formattedBlock;
|
|
2905
|
+
|
|
2906
|
+
const placeCaretAtEnd = (core, element) => {
|
|
2907
|
+
const range = document.createRange();
|
|
2908
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
2909
|
+
|
|
2910
|
+
range.selectNodeContents(element);
|
|
2911
|
+
range.collapse(false);
|
|
2912
|
+
selection.removeAllRanges();
|
|
2913
|
+
selection.addRange(range);
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
if (tag === 'p') {
|
|
2917
|
+
if (isCurrentBlockInLi) {
|
|
2918
|
+
const fragment = document.createDocumentFragment();
|
|
2919
|
+
|
|
2920
|
+
while (currentBlock.firstChild) fragment.appendChild(currentBlock.firstChild);
|
|
2921
|
+
|
|
2922
|
+
li.insertBefore(fragment, currentBlock);
|
|
2923
|
+
currentBlock.remove();
|
|
2924
|
+
}
|
|
2925
|
+
placeCaretAtEnd(core, li);
|
|
2926
|
+
return true;
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
if (isCurrentBlockInLi) {
|
|
2930
|
+
formattedBlock = document.createElement(tag);
|
|
2931
|
+
formattedBlock.innerHTML = currentBlock.innerHTML || '<br>';
|
|
2932
|
+
currentBlock.replaceWith(formattedBlock);
|
|
2933
|
+
} else {
|
|
2934
|
+
formattedBlock = formatListItemContent(li, tag);
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
placeCaretAtEnd(core, formattedBlock);
|
|
2938
|
+
return true;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
const normalizeHeadingWrappedLists = (core) => {
|
|
2942
|
+
core.elements.editor.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
|
|
2943
|
+
const lists = Array.from(heading.children).filter(child => ['UL', 'OL'].includes(child.tagName));
|
|
2944
|
+
if (!lists.length) return;
|
|
2945
|
+
|
|
2946
|
+
const headingTag = heading.tagName.toLowerCase();
|
|
2947
|
+
const fragment = document.createDocumentFragment();
|
|
2948
|
+
|
|
2949
|
+
lists.forEach(list => {
|
|
2950
|
+
list.querySelectorAll('li').forEach(li => {
|
|
2951
|
+
const hasBlock = Array.from(li.children).some(child => BLOCK_FORMAT_TAGS.includes(child.tagName));
|
|
2952
|
+
if (!hasBlock) formatListItemContent(li, headingTag);
|
|
2953
|
+
});
|
|
2954
|
+
fragment.appendChild(list);
|
|
2955
|
+
});
|
|
2956
|
+
|
|
2957
|
+
heading.parentNode.insertBefore(fragment, heading);
|
|
2958
|
+
|
|
2959
|
+
if (heading.textContent.replace(/\u200B/g, '').trim() || heading.querySelector('img, br')) {
|
|
2960
|
+
const paragraph = document.createElement('p');
|
|
2961
|
+
paragraph.innerHTML = heading.innerHTML;
|
|
2962
|
+
heading.replaceWith(paragraph);
|
|
2963
|
+
} else {
|
|
2964
|
+
heading.remove();
|
|
2965
|
+
}
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2873
2969
|
const makeUnorderedList = (event, core) => {
|
|
2874
2970
|
if (event.key !== ' ') return;
|
|
2875
2971
|
|
|
@@ -3201,7 +3297,7 @@ const indentListItem = (li, core) => {
|
|
|
3201
3297
|
const selection = core.elements.iframeWindow.getSelection();
|
|
3202
3298
|
const range = document.createRange();
|
|
3203
3299
|
range.selectNodeContents(li);
|
|
3204
|
-
range.collapse(
|
|
3300
|
+
range.collapse(true);
|
|
3205
3301
|
selection.removeAllRanges();
|
|
3206
3302
|
selection.addRange(range);
|
|
3207
3303
|
core.elements.editor.focus();
|
|
@@ -3241,7 +3337,109 @@ const outdentListItem = (li, core) => {
|
|
|
3241
3337
|
const isEmptyLi = (li) => {
|
|
3242
3338
|
const text = li.textContent.replace(/\u200B/g, '').trim();
|
|
3243
3339
|
const hasOnlySublist = li.children.length === 1 && li.querySelector('ul, ol');
|
|
3244
|
-
|
|
3340
|
+
const hasMeaningfulElement = li.querySelector('img, iframe, audio, video, table, hr, figure');
|
|
3341
|
+
return text === '' && !hasOnlySublist && !hasMeaningfulElement;
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
const trackListSelectionDeletion = (event, core) => {
|
|
3345
|
+
if (event.defaultPrevented) {
|
|
3346
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
if (!['Backspace', 'Delete'].includes(event.key)) {
|
|
3351
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3352
|
+
return;
|
|
3353
|
+
}
|
|
3354
|
+
|
|
3355
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3356
|
+
if (!selection?.rangeCount) return;
|
|
3357
|
+
|
|
3358
|
+
const range = selection.getRangeAt(0);
|
|
3359
|
+
if (range.collapsed) {
|
|
3360
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
const getElementFromNode = (node) => {
|
|
3365
|
+
if (!node) return null;
|
|
3366
|
+
return node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
const startElement = getElementFromNode(range.startContainer);
|
|
3370
|
+
const endElement = getElementFromNode(range.endContainer);
|
|
3371
|
+
const startLi = startElement?.closest('li');
|
|
3372
|
+
const endLi = endElement?.closest('li');
|
|
3373
|
+
|
|
3374
|
+
if (!startLi || !endLi || startLi === endLi) {
|
|
3375
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3376
|
+
return;
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
const listItems = [];
|
|
3380
|
+
|
|
3381
|
+
core.elements.editor.querySelectorAll('li').forEach(li => {
|
|
3382
|
+
if (range.intersectsNode(li)) listItems.push(li);
|
|
3383
|
+
});
|
|
3384
|
+
|
|
3385
|
+
if (listItems.length < 2) {
|
|
3386
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
core.state.pendingListSelectionDeletion = {
|
|
3391
|
+
listItems,
|
|
3392
|
+
lists: Array.from(new Set(listItems.map(li => li.parentElement).filter(Boolean))),
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
const cleanupListSelectionDeletion = (core) => {
|
|
3397
|
+
const pendingDeletion = core.state.pendingListSelectionDeletion;
|
|
3398
|
+
if (!pendingDeletion) return false;
|
|
3399
|
+
|
|
3400
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3401
|
+
|
|
3402
|
+
let didChange = false;
|
|
3403
|
+
const lists = new Set(pendingDeletion.lists);
|
|
3404
|
+
|
|
3405
|
+
pendingDeletion.listItems.forEach(li => {
|
|
3406
|
+
if (!li.isConnected || !core.elements.editor.contains(li)) return;
|
|
3407
|
+
if (!isEmptyLi(li)) return;
|
|
3408
|
+
|
|
3409
|
+
const parentList = li.parentElement;
|
|
3410
|
+
if (parentList) lists.add(parentList);
|
|
3411
|
+
li.remove();
|
|
3412
|
+
didChange = true;
|
|
3413
|
+
});
|
|
3414
|
+
|
|
3415
|
+
Array.from(lists).reverse().forEach(list => {
|
|
3416
|
+
if (!list?.isConnected || !core.elements.editor.contains(list)) return;
|
|
3417
|
+
if (list.querySelector('li')) return;
|
|
3418
|
+
|
|
3419
|
+
list.remove();
|
|
3420
|
+
didChange = true;
|
|
3421
|
+
});
|
|
3422
|
+
|
|
3423
|
+
if (didChange) {
|
|
3424
|
+
if (!core.elements.editor.textContent.replace(/\u200B/g, '').trim() && !core.elements.editor.querySelector('img, iframe, audio, video, table, hr, figure')) {
|
|
3425
|
+
core.elements.editor.innerHTML = '<div><br></div>';
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3429
|
+
if (!selection?.rangeCount || !core.elements.editor.contains(selection.anchorNode)) {
|
|
3430
|
+
const range = document.createRange();
|
|
3431
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3432
|
+
|
|
3433
|
+
range.selectNodeContents(core.elements.editor);
|
|
3434
|
+
range.collapse(false);
|
|
3435
|
+
selection.removeAllRanges();
|
|
3436
|
+
selection.addRange(range);
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
core.updateCaretPosition();
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
return didChange;
|
|
3245
3443
|
}
|
|
3246
3444
|
|
|
3247
3445
|
const makeSublist = (event, core) => {
|
|
@@ -3251,6 +3449,7 @@ const makeSublist = (event, core) => {
|
|
|
3251
3449
|
if (!selection.rangeCount) return;
|
|
3252
3450
|
|
|
3253
3451
|
const range = selection.getRangeAt(0);
|
|
3452
|
+
const isAtStart = range.startOffset === 0;
|
|
3254
3453
|
|
|
3255
3454
|
let container = range.commonAncestorContainer;
|
|
3256
3455
|
|
|
@@ -3274,7 +3473,7 @@ const makeSublist = (event, core) => {
|
|
|
3274
3473
|
|
|
3275
3474
|
if (!event.shiftKey) {
|
|
3276
3475
|
const hasContent = selectedLis.some(li => !isEmptyLi(li));
|
|
3277
|
-
if (hasContent) return;
|
|
3476
|
+
if (hasContent && !isAtStart) return;
|
|
3278
3477
|
}
|
|
3279
3478
|
|
|
3280
3479
|
event.preventDefault();
|
|
@@ -3419,7 +3618,74 @@ const insertTOC = (core) => {
|
|
|
3419
3618
|
});
|
|
3420
3619
|
|
|
3421
3620
|
tocContainer.appendChild(ul);
|
|
3422
|
-
|
|
3621
|
+
|
|
3622
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3623
|
+
|
|
3624
|
+
if (!selection.rangeCount) return;
|
|
3625
|
+
|
|
3626
|
+
const range = selection.getRangeAt(0);
|
|
3627
|
+
|
|
3628
|
+
if (!range.collapsed) {
|
|
3629
|
+
// Replace selected content with TOC
|
|
3630
|
+
range.deleteContents();
|
|
3631
|
+
range.insertNode(tocContainer);
|
|
3632
|
+
|
|
3633
|
+
// Remove empty list items
|
|
3634
|
+
editor.querySelectorAll('li').forEach(li => {
|
|
3635
|
+
const text = li.textContent.replace(/\u00A0/g, '').trim();
|
|
3636
|
+
|
|
3637
|
+
if (
|
|
3638
|
+
!text &&
|
|
3639
|
+
!li.querySelector(
|
|
3640
|
+
'img, video, table, ul, ol, iframe, blockquote'
|
|
3641
|
+
)
|
|
3642
|
+
) {
|
|
3643
|
+
li.remove();
|
|
3644
|
+
}
|
|
3645
|
+
});
|
|
3646
|
+
|
|
3647
|
+
// Place cursor after TOC
|
|
3648
|
+
const p = document.createElement('p');
|
|
3649
|
+
p.innerHTML = '<br>';
|
|
3650
|
+
tocContainer.after(p);
|
|
3651
|
+
|
|
3652
|
+
const newRange = document.createRange();
|
|
3653
|
+
newRange.setStart(p, 0);
|
|
3654
|
+
newRange.collapse(true);
|
|
3655
|
+
|
|
3656
|
+
selection.removeAllRanges();
|
|
3657
|
+
selection.addRange(newRange);
|
|
3658
|
+
} else {
|
|
3659
|
+
// No selection -> insert after current block
|
|
3660
|
+
let node = range.startContainer;
|
|
3661
|
+
|
|
3662
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
3663
|
+
node = node.parentElement;
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3666
|
+
const block = node.closest(
|
|
3667
|
+
'p, div, h1, h2, h3, h4, h5, h6, blockquote, pre, li'
|
|
3668
|
+
);
|
|
3669
|
+
|
|
3670
|
+
if (block) {
|
|
3671
|
+
block.insertAdjacentElement('afterend', tocContainer);
|
|
3672
|
+
} else {
|
|
3673
|
+
editor.appendChild(tocContainer);
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
// Place cursor after TOC
|
|
3677
|
+
const p = document.createElement('p');
|
|
3678
|
+
p.innerHTML = '<br>';
|
|
3679
|
+
tocContainer.after(p);
|
|
3680
|
+
|
|
3681
|
+
const newRange = document.createRange();
|
|
3682
|
+
newRange.setStart(p, 0);
|
|
3683
|
+
newRange.collapse(true);
|
|
3684
|
+
|
|
3685
|
+
selection.removeAllRanges();
|
|
3686
|
+
selection.addRange(newRange);
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3423
3689
|
core.updateCaretPosition();
|
|
3424
3690
|
};
|
|
3425
3691
|
|
|
@@ -4069,6 +4335,8 @@ export {
|
|
|
4069
4335
|
updateHeight,
|
|
4070
4336
|
getTableGrid,
|
|
4071
4337
|
getCellPosition,
|
|
4338
|
+
applyFormatBlockInListItem,
|
|
4339
|
+
normalizeHeadingWrappedLists,
|
|
4072
4340
|
makeUnorderedList,
|
|
4073
4341
|
makeOrderedList,
|
|
4074
4342
|
showTooltip,
|
|
@@ -4086,4 +4354,6 @@ export {
|
|
|
4086
4354
|
highlightActiveOutline,
|
|
4087
4355
|
findAndReplace,
|
|
4088
4356
|
handleBackspaceInList,
|
|
4357
|
+
trackListSelectionDeletion,
|
|
4358
|
+
cleanupListSelectionDeletion,
|
|
4089
4359
|
}
|