leksy-editor 2.4.0 → 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 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 current HTML content of the editor. |
182
- | `onChange(html)` | Triggered when content changes. |
183
- | `onBlur(html)` | Triggered when the editor loses focus. |
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
@@ -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
 
@@ -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.4.0",
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 applyOrderList = (core) => {
16
- core.elements.iframeWindow.execCommand('insertOrderedList');
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('insertOrderedList');
21
- if (isActive) changeToolbarStateByName(core, 'active', ['ordered_list'])
22
- else changeToolbarStateByName(core, 'inactive', ['ordered_list'])
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 applyUnorderedList = (core) => {
27
- core.elements.iframeWindow.execCommand('insertUnorderedList');
28
- core.elements.editor.focus();
29
- core.updateCaretPosition()
26
+ const applyOrderList = (core) => applyList(core, {
27
+ command: 'insertOrderedList',
28
+ activePlugin: 'ordered_list',
29
+ inactivePlugin: 'unordered_list'
30
+ })
30
31
 
31
- const isActive = core.elements.iframeWindow.queryCommandState('insertUnorderedList');
32
- if (isActive) changeToolbarStateByName(core, 'active', ['unordered_list'])
33
- else changeToolbarStateByName(core, 'inactive', ['unordered_list'])
34
- changeToolbarStateByName(core, 'inactive', ['ordered_list'])
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.elements.iframeWindow.execCommand('formatBlock', false, tag);
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
 
@@ -2870,6 +2870,98 @@ const isInsideTableCell = (node) => {
2870
2870
  return element?.closest('td, th');
2871
2871
  };
2872
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
+
2873
2965
  const makeUnorderedList = (event, core) => {
2874
2966
  if (event.key !== ' ') return;
2875
2967
 
@@ -3201,7 +3293,7 @@ const indentListItem = (li, core) => {
3201
3293
  const selection = core.elements.iframeWindow.getSelection();
3202
3294
  const range = document.createRange();
3203
3295
  range.selectNodeContents(li);
3204
- range.collapse(false);
3296
+ range.collapse(true);
3205
3297
  selection.removeAllRanges();
3206
3298
  selection.addRange(range);
3207
3299
  core.elements.editor.focus();
@@ -3241,7 +3333,109 @@ const outdentListItem = (li, core) => {
3241
3333
  const isEmptyLi = (li) => {
3242
3334
  const text = li.textContent.replace(/\u200B/g, '').trim();
3243
3335
  const hasOnlySublist = li.children.length === 1 && li.querySelector('ul, ol');
3244
- return text === '' && !hasOnlySublist;
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;
3245
3439
  }
3246
3440
 
3247
3441
  const makeSublist = (event, core) => {
@@ -3251,6 +3445,7 @@ const makeSublist = (event, core) => {
3251
3445
  if (!selection.rangeCount) return;
3252
3446
 
3253
3447
  const range = selection.getRangeAt(0);
3448
+ const isAtStart = range.startOffset === 0;
3254
3449
 
3255
3450
  let container = range.commonAncestorContainer;
3256
3451
 
@@ -3274,7 +3469,7 @@ const makeSublist = (event, core) => {
3274
3469
 
3275
3470
  if (!event.shiftKey) {
3276
3471
  const hasContent = selectedLis.some(li => !isEmptyLi(li));
3277
- if (hasContent) return;
3472
+ if (hasContent && !isAtStart) return;
3278
3473
  }
3279
3474
 
3280
3475
  event.preventDefault();
@@ -3419,7 +3614,74 @@ const insertTOC = (core) => {
3419
3614
  });
3420
3615
 
3421
3616
  tocContainer.appendChild(ul);
3422
- core.insertNode(tocContainer);
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
+
3423
3685
  core.updateCaretPosition();
3424
3686
  };
3425
3687
 
@@ -4069,6 +4331,8 @@ export {
4069
4331
  updateHeight,
4070
4332
  getTableGrid,
4071
4333
  getCellPosition,
4334
+ applyFormatBlockInListItem,
4335
+ normalizeHeadingWrappedLists,
4072
4336
  makeUnorderedList,
4073
4337
  makeOrderedList,
4074
4338
  showTooltip,
@@ -4086,4 +4350,6 @@ export {
4086
4350
  highlightActiveOutline,
4087
4351
  findAndReplace,
4088
4352
  handleBackspaceInList,
4353
+ trackListSelectionDeletion,
4354
+ cleanupListSelectionDeletion,
4089
4355
  }