leksy-editor 2.3.2 → 2.4.1
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 +4 -4
- package/constant.js +42 -1
- package/index.js +4 -2
- package/package.json +1 -1
- package/plugin.js +25 -18
- package/utilities.js +289 -7
package/README.md
CHANGED
|
@@ -177,10 +177,10 @@ Similarly, Pexels and Tenor can be integrated using `pexels` and `tenor` plugins
|
|
|
177
177
|
|
|
178
178
|
| Function | Description |
|
|
179
179
|
|----------|-------------|
|
|
180
|
-
| `setContents(html)` | Sets the editor's content. |
|
|
181
|
-
| `getContents()` | Returns the
|
|
182
|
-
| `onChange(
|
|
183
|
-
| `onBlur(
|
|
180
|
+
| `setContents(tabs || html)` | Sets the editor's content. |
|
|
181
|
+
| `getContents()` | Returns the array of tabs containing HTML content of the editor. |
|
|
182
|
+
| `onChange(tabs)` | Triggered when content changes. |
|
|
183
|
+
| `onBlur(tabs)` | Triggered when the editor loses focus. |
|
|
184
184
|
| `onAttachment(files)` | Fires when files are attached. |
|
|
185
185
|
| `manipulateImage()` | Custom function for manipulating images. |
|
|
186
186
|
| `handleFilePicker()` | Custom function for file upload, useful for Android and iOS file upload. |
|
package/constant.js
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
const WHILE_LISTED_TAG = "br|p|div|pre|blockquote|h|h1|h2|h3|h4|h5|h6|ol|ul|li|hr|figure|figcaption|img|iframe|audio|video|source|table|thead|tbody|tr|th|td|a|b|strong|var|i|em|u|ins|s|span|strike|del|sub|sup|code|svg|path|details|summary|font|article|section|main|header|footer|aside|mark|cite|abbr|dfn|nav|caption|picture|dl|dt|dd|time|label|kbd|samp|wbr";
|
|
2
2
|
const WHILE_LISTED_ATTRIBUTES = "align|alt|bgcolor|border|cellpadding|cellspacing|color|colspan|contenteditable|dir|download|face|height|href|lang|name|rowspan|size|src|style|target|title|valign|width|background|media|sizes|srcset|spellcheck";
|
|
3
|
+
const BLOCKED_STYLES = [
|
|
4
|
+
"position",
|
|
5
|
+
"inset",
|
|
6
|
+
"top",
|
|
7
|
+
"right",
|
|
8
|
+
"bottom",
|
|
9
|
+
"left",
|
|
10
|
+
"z-index",
|
|
11
|
+
"float",
|
|
12
|
+
"clear",
|
|
13
|
+
"overflow",
|
|
14
|
+
"overflow-x",
|
|
15
|
+
"overflow-y",
|
|
16
|
+
"transform",
|
|
17
|
+
"transform-origin",
|
|
18
|
+
"perspective",
|
|
19
|
+
"transition",
|
|
20
|
+
"transition-property",
|
|
21
|
+
"transition-duration",
|
|
22
|
+
"transition-timing-function",
|
|
23
|
+
"transition-delay",
|
|
24
|
+
"animation",
|
|
25
|
+
"animation-name",
|
|
26
|
+
"animation-duration",
|
|
27
|
+
"animation-timing-function",
|
|
28
|
+
"animation-delay",
|
|
29
|
+
"animation-iteration-count",
|
|
30
|
+
"animation-direction",
|
|
31
|
+
"animation-fill-mode",
|
|
32
|
+
"animation-play-state",
|
|
33
|
+
"flex-basis",
|
|
34
|
+
"flex-grow",
|
|
35
|
+
"flex-shrink",
|
|
36
|
+
"order",
|
|
37
|
+
"place-self",
|
|
38
|
+
"grid-column",
|
|
39
|
+
"grid-row",
|
|
40
|
+
].join("|")
|
|
3
41
|
|
|
4
42
|
const REGEX = {
|
|
5
43
|
TEXT_URL: /(https?:\/\/[^\s]+|www\.[^\s]+)$/i,
|
|
@@ -10,6 +48,7 @@ const REGEX = {
|
|
|
10
48
|
MOBILE: /^\+?[0-9\s()-]+$/,
|
|
11
49
|
WHILE_LISTED_TAG: new RegExp('^(' + WHILE_LISTED_TAG.replace(/\|/g, '\\b|\\b') + ')$', 'i'),
|
|
12
50
|
WHILE_LISTED_ATTRIBUTES: new RegExp('^(' + WHILE_LISTED_ATTRIBUTES.replace(/\|/g, '\\b|\\b') + ')$', 'i'),
|
|
51
|
+
BLOCKED_STYLES: new RegExp('^(' + BLOCKED_STYLES.replace(/\|/g, '\\b|\\b') + ')$', 'i'),
|
|
13
52
|
}
|
|
14
53
|
|
|
15
54
|
const ERRORS = {
|
|
@@ -893,6 +932,7 @@ const UNORDERED_LIST_OPTIONS = {
|
|
|
893
932
|
|
|
894
933
|
const RESIZE_MARGIN = 10;
|
|
895
934
|
|
|
935
|
+
const BLOCK_FORMAT_TAGS = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P'];
|
|
896
936
|
const LIST_STYLES_BY_LEVEL = ['1', 'a', 'i', 'A'];
|
|
897
937
|
const UNORDERED_LIST_STYLES_BY_LEVEL = ['disc', 'circle', 'square'];
|
|
898
938
|
|
|
@@ -919,8 +959,9 @@ export {
|
|
|
919
959
|
CSS_VARIABLES,
|
|
920
960
|
TAB_CATEGORIES,
|
|
921
961
|
RESIZE_MARGIN,
|
|
962
|
+
BLOCK_FORMAT_TAGS,
|
|
922
963
|
LIST_STYLES_BY_LEVEL,
|
|
923
964
|
UNORDERED_LIST_STYLES_BY_LEVEL,
|
|
924
965
|
UNORDERED_LIST_OPTIONS,
|
|
925
966
|
ORDERED_LIST_OPTIONS,
|
|
926
|
-
}
|
|
967
|
+
}
|
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
|
|
|
@@ -675,6 +675,7 @@ class LeksyEditor {
|
|
|
675
675
|
contentEditableDiv.className = 'content-editable';
|
|
676
676
|
|
|
677
677
|
contentEditableDiv.oninput = (e) => {
|
|
678
|
+
cleanupListSelectionDeletion(core);
|
|
678
679
|
core.onChange(e.target.innerHTML)
|
|
679
680
|
}
|
|
680
681
|
contentEditableDiv.onbeforeinput = (e) => {
|
|
@@ -858,6 +859,7 @@ class LeksyEditor {
|
|
|
858
859
|
}
|
|
859
860
|
|
|
860
861
|
handleBackspaceInList(event, core);
|
|
862
|
+
trackListSelectionDeletion(event, core);
|
|
861
863
|
|
|
862
864
|
if (!isLinkCreated) core.elements.lastCreatedLink = null
|
|
863
865
|
|
|
@@ -1015,4 +1017,4 @@ export default LeksyEditor
|
|
|
1015
1017
|
export {
|
|
1016
1018
|
convertHtmlToText,
|
|
1017
1019
|
isHTMLEmpty,
|
|
1018
|
-
}
|
|
1020
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leksy-editor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
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,11 +1,26 @@
|
|
|
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
|
|
|
9
|
+
const removeBlockedStyles = (node) => {
|
|
10
|
+
if (!node?.style) return;
|
|
11
|
+
const styleProperties = Array.from(node.style);
|
|
12
|
+
|
|
13
|
+
styleProperties.forEach((propertyName) => {
|
|
14
|
+
if (REGEX.BLOCKED_STYLES.test(propertyName)) {
|
|
15
|
+
node.style.removeProperty(propertyName);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (!node.getAttribute('style')?.trim()) {
|
|
20
|
+
node.removeAttribute('style');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
9
24
|
const traverseAndClean = (element, options, core) => {
|
|
10
25
|
if (!element?.childNodes) return;
|
|
11
26
|
|
|
@@ -51,6 +66,7 @@ const traverseAndClean = (element, options, core) => {
|
|
|
51
66
|
node.style.removeProperty('background-color');
|
|
52
67
|
node.style.removeProperty('color');
|
|
53
68
|
}
|
|
69
|
+
removeBlockedStyles(node);
|
|
54
70
|
attributesToRemove.forEach(attr => node.removeAttribute(attr));
|
|
55
71
|
|
|
56
72
|
// Process child nodes
|
|
@@ -2854,6 +2870,98 @@ const isInsideTableCell = (node) => {
|
|
|
2854
2870
|
return element?.closest('td, th');
|
|
2855
2871
|
};
|
|
2856
2872
|
|
|
2873
|
+
const formatListItemContent = (li, tag) => {
|
|
2874
|
+
const formattedBlock = document.createElement(tag);
|
|
2875
|
+
const childNodes = Array.from(li.childNodes);
|
|
2876
|
+
|
|
2877
|
+
childNodes.forEach(child => {
|
|
2878
|
+
if (child.nodeType === Node.ELEMENT_NODE && ['UL', 'OL'].includes(child.tagName)) return;
|
|
2879
|
+
formattedBlock.appendChild(child);
|
|
2880
|
+
});
|
|
2881
|
+
|
|
2882
|
+
if (!formattedBlock.childNodes.length) formattedBlock.innerHTML = '<br>';
|
|
2883
|
+
|
|
2884
|
+
const firstNestedList = Array.from(li.children).find(child => ['UL', 'OL'].includes(child.tagName));
|
|
2885
|
+
li.insertBefore(formattedBlock, firstNestedList || null);
|
|
2886
|
+
|
|
2887
|
+
return formattedBlock;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
const applyFormatBlockInListItem = (core, tag) => {
|
|
2891
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
2892
|
+
const node = selection?.rangeCount ? selection.getRangeAt(0).startContainer : null;
|
|
2893
|
+
const element = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement;
|
|
2894
|
+
const li = element?.closest('li');
|
|
2895
|
+
|
|
2896
|
+
if (!li || !core.elements.editor.contains(li)) return false;
|
|
2897
|
+
|
|
2898
|
+
const currentBlock = element.closest(BLOCK_FORMAT_TAGS.join(','));
|
|
2899
|
+
const isCurrentBlockInLi = currentBlock && currentBlock.closest('li') === li;
|
|
2900
|
+
let formattedBlock;
|
|
2901
|
+
|
|
2902
|
+
const placeCaretAtEnd = (core, element) => {
|
|
2903
|
+
const range = document.createRange();
|
|
2904
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
2905
|
+
|
|
2906
|
+
range.selectNodeContents(element);
|
|
2907
|
+
range.collapse(false);
|
|
2908
|
+
selection.removeAllRanges();
|
|
2909
|
+
selection.addRange(range);
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
if (tag === 'p') {
|
|
2913
|
+
if (isCurrentBlockInLi) {
|
|
2914
|
+
const fragment = document.createDocumentFragment();
|
|
2915
|
+
|
|
2916
|
+
while (currentBlock.firstChild) fragment.appendChild(currentBlock.firstChild);
|
|
2917
|
+
|
|
2918
|
+
li.insertBefore(fragment, currentBlock);
|
|
2919
|
+
currentBlock.remove();
|
|
2920
|
+
}
|
|
2921
|
+
placeCaretAtEnd(core, li);
|
|
2922
|
+
return true;
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
if (isCurrentBlockInLi) {
|
|
2926
|
+
formattedBlock = document.createElement(tag);
|
|
2927
|
+
formattedBlock.innerHTML = currentBlock.innerHTML || '<br>';
|
|
2928
|
+
currentBlock.replaceWith(formattedBlock);
|
|
2929
|
+
} else {
|
|
2930
|
+
formattedBlock = formatListItemContent(li, tag);
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
placeCaretAtEnd(core, formattedBlock);
|
|
2934
|
+
return true;
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
const normalizeHeadingWrappedLists = (core) => {
|
|
2938
|
+
core.elements.editor.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
|
|
2939
|
+
const lists = Array.from(heading.children).filter(child => ['UL', 'OL'].includes(child.tagName));
|
|
2940
|
+
if (!lists.length) return;
|
|
2941
|
+
|
|
2942
|
+
const headingTag = heading.tagName.toLowerCase();
|
|
2943
|
+
const fragment = document.createDocumentFragment();
|
|
2944
|
+
|
|
2945
|
+
lists.forEach(list => {
|
|
2946
|
+
list.querySelectorAll('li').forEach(li => {
|
|
2947
|
+
const hasBlock = Array.from(li.children).some(child => BLOCK_FORMAT_TAGS.includes(child.tagName));
|
|
2948
|
+
if (!hasBlock) formatListItemContent(li, headingTag);
|
|
2949
|
+
});
|
|
2950
|
+
fragment.appendChild(list);
|
|
2951
|
+
});
|
|
2952
|
+
|
|
2953
|
+
heading.parentNode.insertBefore(fragment, heading);
|
|
2954
|
+
|
|
2955
|
+
if (heading.textContent.replace(/\u200B/g, '').trim() || heading.querySelector('img, br')) {
|
|
2956
|
+
const paragraph = document.createElement('p');
|
|
2957
|
+
paragraph.innerHTML = heading.innerHTML;
|
|
2958
|
+
heading.replaceWith(paragraph);
|
|
2959
|
+
} else {
|
|
2960
|
+
heading.remove();
|
|
2961
|
+
}
|
|
2962
|
+
});
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2857
2965
|
const makeUnorderedList = (event, core) => {
|
|
2858
2966
|
if (event.key !== ' ') return;
|
|
2859
2967
|
|
|
@@ -3185,7 +3293,7 @@ const indentListItem = (li, core) => {
|
|
|
3185
3293
|
const selection = core.elements.iframeWindow.getSelection();
|
|
3186
3294
|
const range = document.createRange();
|
|
3187
3295
|
range.selectNodeContents(li);
|
|
3188
|
-
range.collapse(
|
|
3296
|
+
range.collapse(true);
|
|
3189
3297
|
selection.removeAllRanges();
|
|
3190
3298
|
selection.addRange(range);
|
|
3191
3299
|
core.elements.editor.focus();
|
|
@@ -3225,7 +3333,109 @@ const outdentListItem = (li, core) => {
|
|
|
3225
3333
|
const isEmptyLi = (li) => {
|
|
3226
3334
|
const text = li.textContent.replace(/\u200B/g, '').trim();
|
|
3227
3335
|
const hasOnlySublist = li.children.length === 1 && li.querySelector('ul, ol');
|
|
3228
|
-
|
|
3336
|
+
const hasMeaningfulElement = li.querySelector('img, iframe, audio, video, table, hr, figure');
|
|
3337
|
+
return text === '' && !hasOnlySublist && !hasMeaningfulElement;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
const trackListSelectionDeletion = (event, core) => {
|
|
3341
|
+
if (event.defaultPrevented) {
|
|
3342
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
if (!['Backspace', 'Delete'].includes(event.key)) {
|
|
3347
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3352
|
+
if (!selection?.rangeCount) return;
|
|
3353
|
+
|
|
3354
|
+
const range = selection.getRangeAt(0);
|
|
3355
|
+
if (range.collapsed) {
|
|
3356
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
const getElementFromNode = (node) => {
|
|
3361
|
+
if (!node) return null;
|
|
3362
|
+
return node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
const startElement = getElementFromNode(range.startContainer);
|
|
3366
|
+
const endElement = getElementFromNode(range.endContainer);
|
|
3367
|
+
const startLi = startElement?.closest('li');
|
|
3368
|
+
const endLi = endElement?.closest('li');
|
|
3369
|
+
|
|
3370
|
+
if (!startLi || !endLi || startLi === endLi) {
|
|
3371
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
const listItems = [];
|
|
3376
|
+
|
|
3377
|
+
core.elements.editor.querySelectorAll('li').forEach(li => {
|
|
3378
|
+
if (range.intersectsNode(li)) listItems.push(li);
|
|
3379
|
+
});
|
|
3380
|
+
|
|
3381
|
+
if (listItems.length < 2) {
|
|
3382
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
core.state.pendingListSelectionDeletion = {
|
|
3387
|
+
listItems,
|
|
3388
|
+
lists: Array.from(new Set(listItems.map(li => li.parentElement).filter(Boolean))),
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3392
|
+
const cleanupListSelectionDeletion = (core) => {
|
|
3393
|
+
const pendingDeletion = core.state.pendingListSelectionDeletion;
|
|
3394
|
+
if (!pendingDeletion) return false;
|
|
3395
|
+
|
|
3396
|
+
delete core.state.pendingListSelectionDeletion;
|
|
3397
|
+
|
|
3398
|
+
let didChange = false;
|
|
3399
|
+
const lists = new Set(pendingDeletion.lists);
|
|
3400
|
+
|
|
3401
|
+
pendingDeletion.listItems.forEach(li => {
|
|
3402
|
+
if (!li.isConnected || !core.elements.editor.contains(li)) return;
|
|
3403
|
+
if (!isEmptyLi(li)) return;
|
|
3404
|
+
|
|
3405
|
+
const parentList = li.parentElement;
|
|
3406
|
+
if (parentList) lists.add(parentList);
|
|
3407
|
+
li.remove();
|
|
3408
|
+
didChange = true;
|
|
3409
|
+
});
|
|
3410
|
+
|
|
3411
|
+
Array.from(lists).reverse().forEach(list => {
|
|
3412
|
+
if (!list?.isConnected || !core.elements.editor.contains(list)) return;
|
|
3413
|
+
if (list.querySelector('li')) return;
|
|
3414
|
+
|
|
3415
|
+
list.remove();
|
|
3416
|
+
didChange = true;
|
|
3417
|
+
});
|
|
3418
|
+
|
|
3419
|
+
if (didChange) {
|
|
3420
|
+
if (!core.elements.editor.textContent.replace(/\u200B/g, '').trim() && !core.elements.editor.querySelector('img, iframe, audio, video, table, hr, figure')) {
|
|
3421
|
+
core.elements.editor.innerHTML = '<div><br></div>';
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3425
|
+
if (!selection?.rangeCount || !core.elements.editor.contains(selection.anchorNode)) {
|
|
3426
|
+
const range = document.createRange();
|
|
3427
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3428
|
+
|
|
3429
|
+
range.selectNodeContents(core.elements.editor);
|
|
3430
|
+
range.collapse(false);
|
|
3431
|
+
selection.removeAllRanges();
|
|
3432
|
+
selection.addRange(range);
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
core.updateCaretPosition();
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
return didChange;
|
|
3229
3439
|
}
|
|
3230
3440
|
|
|
3231
3441
|
const makeSublist = (event, core) => {
|
|
@@ -3235,6 +3445,7 @@ const makeSublist = (event, core) => {
|
|
|
3235
3445
|
if (!selection.rangeCount) return;
|
|
3236
3446
|
|
|
3237
3447
|
const range = selection.getRangeAt(0);
|
|
3448
|
+
const isAtStart = range.startOffset === 0;
|
|
3238
3449
|
|
|
3239
3450
|
let container = range.commonAncestorContainer;
|
|
3240
3451
|
|
|
@@ -3258,7 +3469,7 @@ const makeSublist = (event, core) => {
|
|
|
3258
3469
|
|
|
3259
3470
|
if (!event.shiftKey) {
|
|
3260
3471
|
const hasContent = selectedLis.some(li => !isEmptyLi(li));
|
|
3261
|
-
if (hasContent) return;
|
|
3472
|
+
if (hasContent && !isAtStart) return;
|
|
3262
3473
|
}
|
|
3263
3474
|
|
|
3264
3475
|
event.preventDefault();
|
|
@@ -3403,7 +3614,74 @@ const insertTOC = (core) => {
|
|
|
3403
3614
|
});
|
|
3404
3615
|
|
|
3405
3616
|
tocContainer.appendChild(ul);
|
|
3406
|
-
|
|
3617
|
+
|
|
3618
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3619
|
+
|
|
3620
|
+
if (!selection.rangeCount) return;
|
|
3621
|
+
|
|
3622
|
+
const range = selection.getRangeAt(0);
|
|
3623
|
+
|
|
3624
|
+
if (!range.collapsed) {
|
|
3625
|
+
// Replace selected content with TOC
|
|
3626
|
+
range.deleteContents();
|
|
3627
|
+
range.insertNode(tocContainer);
|
|
3628
|
+
|
|
3629
|
+
// Remove empty list items
|
|
3630
|
+
editor.querySelectorAll('li').forEach(li => {
|
|
3631
|
+
const text = li.textContent.replace(/\u00A0/g, '').trim();
|
|
3632
|
+
|
|
3633
|
+
if (
|
|
3634
|
+
!text &&
|
|
3635
|
+
!li.querySelector(
|
|
3636
|
+
'img, video, table, ul, ol, iframe, blockquote'
|
|
3637
|
+
)
|
|
3638
|
+
) {
|
|
3639
|
+
li.remove();
|
|
3640
|
+
}
|
|
3641
|
+
});
|
|
3642
|
+
|
|
3643
|
+
// Place cursor after TOC
|
|
3644
|
+
const p = document.createElement('p');
|
|
3645
|
+
p.innerHTML = '<br>';
|
|
3646
|
+
tocContainer.after(p);
|
|
3647
|
+
|
|
3648
|
+
const newRange = document.createRange();
|
|
3649
|
+
newRange.setStart(p, 0);
|
|
3650
|
+
newRange.collapse(true);
|
|
3651
|
+
|
|
3652
|
+
selection.removeAllRanges();
|
|
3653
|
+
selection.addRange(newRange);
|
|
3654
|
+
} else {
|
|
3655
|
+
// No selection -> insert after current block
|
|
3656
|
+
let node = range.startContainer;
|
|
3657
|
+
|
|
3658
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
3659
|
+
node = node.parentElement;
|
|
3660
|
+
}
|
|
3661
|
+
|
|
3662
|
+
const block = node.closest(
|
|
3663
|
+
'p, div, h1, h2, h3, h4, h5, h6, blockquote, pre, li'
|
|
3664
|
+
);
|
|
3665
|
+
|
|
3666
|
+
if (block) {
|
|
3667
|
+
block.insertAdjacentElement('afterend', tocContainer);
|
|
3668
|
+
} else {
|
|
3669
|
+
editor.appendChild(tocContainer);
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
// Place cursor after TOC
|
|
3673
|
+
const p = document.createElement('p');
|
|
3674
|
+
p.innerHTML = '<br>';
|
|
3675
|
+
tocContainer.after(p);
|
|
3676
|
+
|
|
3677
|
+
const newRange = document.createRange();
|
|
3678
|
+
newRange.setStart(p, 0);
|
|
3679
|
+
newRange.collapse(true);
|
|
3680
|
+
|
|
3681
|
+
selection.removeAllRanges();
|
|
3682
|
+
selection.addRange(newRange);
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3407
3685
|
core.updateCaretPosition();
|
|
3408
3686
|
};
|
|
3409
3687
|
|
|
@@ -4053,6 +4331,8 @@ export {
|
|
|
4053
4331
|
updateHeight,
|
|
4054
4332
|
getTableGrid,
|
|
4055
4333
|
getCellPosition,
|
|
4334
|
+
applyFormatBlockInListItem,
|
|
4335
|
+
normalizeHeadingWrappedLists,
|
|
4056
4336
|
makeUnorderedList,
|
|
4057
4337
|
makeOrderedList,
|
|
4058
4338
|
showTooltip,
|
|
@@ -4070,4 +4350,6 @@ export {
|
|
|
4070
4350
|
highlightActiveOutline,
|
|
4071
4351
|
findAndReplace,
|
|
4072
4352
|
handleBackspaceInList,
|
|
4073
|
-
|
|
4353
|
+
trackListSelectionDeletion,
|
|
4354
|
+
cleanupListSelectionDeletion,
|
|
4355
|
+
}
|