diff --git a/playground/index.html b/playground/index.html
index 799a9f09..d2474ae4 100644
--- a/playground/index.html
+++ b/playground/index.html
@@ -78,7 +78,9 @@
* Wrapper of Editor
*/
holder: 'editorjs',
-
+ sanitizer: {
+ br: false,
+ },
/**
* Common Inline Toolbar settings
* - if true (or not specified), the order from 'tool' property will be used
@@ -98,6 +100,9 @@
class: List,
inlineToolbar: true,
shortcut: 'CMD+SHIFT+L',
+ sanitizer: {
+ br: false,
+ },
config: {
defaultStyle: 'checklist',
maxLevel: 4,
@@ -107,7 +112,7 @@
/**
* Example of the lacalisation dictionary
*/
- i18n: {
+ i18n: {
messages: {
"toolNames": {
"Ordered List": "Nummerierte Liste",
diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts
index acb7ccea..6848dd36 100644
--- a/src/ListTabulator/index.ts
+++ b/src/ListTabulator/index.ts
@@ -4,7 +4,7 @@ import type { ListConfig, ListData, ListDataStyle } from '../types/ListParams';
import type { ListItem } from '../types/ListParams';
import type { ItemElement, ItemChildWrapperElement } from '../types/Elements';
import { isHtmlElement } from '../utils/type-guards';
-import { getContenteditableSlice, getCaretNodeAndOffset, isCaretAtStartOfInput } from '@editorjs/caret';
+import { getContenteditableSlice, getCaretNodeAndOffset, isCaretAtStartOfInput, isCaretAtEndOfInput } from '@editorjs/caret';
import { DefaultListCssClasses } from '../ListRenderer';
import type { PasteEvent } from '../types';
import type { API, BlockAPI, PasteConfig } from '@editorjs/editorjs';
@@ -167,11 +167,18 @@ export default class ListTabulator {
(event) => {
switch (event.key) {
case 'Enter':
- this.enterPressed(event);
+ if (event.shiftKey) {
+ this.enterBreakPressed(event);
+ } else {
+ this.enterPressed(event);
+ }
break;
case 'Backspace':
this.backspace(event);
break;
+ case 'Delete':
+ this.delete(event);
+ break;
case 'Tab':
if (event.shiftKey) {
this.shiftTab(event);
@@ -401,8 +408,18 @@ export default class ListTabulator {
const subItemsWrapper = child.querySelector(`:scope > ${tagToSearch}`);
// get subitems.
const subItems = subItemsWrapper ? getPastedItems(subItemsWrapper) : [];
+
// get text content of the li element.
- const content = child.innerHTML ?? '';
+ let content = child.innerHTML.trim();
+
+ if (subItemsWrapper) {
+ // Get Copy of Child and remove any nested OL or UL tags from the content
+ const childCopy = child.cloneNode(true) as HTMLElement;
+
+ childCopy.querySelector(`:scope > ${tagToSearch}`)?.remove();
+
+ content = childCopy.innerHTML;
+ }
return {
content,
@@ -514,6 +531,50 @@ export default class ListTabulator {
}
}
+
+ /**
+ * Handles Enter break keypress
+ * @param event - keydown
+ */
+ private enterBreakPressed(event: KeyboardEvent): void {
+ const currentItem = this.currentItem;
+ /**
+ * Prevent editor.js behaviour
+ */
+ event.stopPropagation();
+
+ /**
+ * Prevent browser behaviour
+ */
+ event.preventDefault();
+
+ /**
+ * Prevent duplicated event in Chinese, Japanese and Korean languages
+ */
+ if (event.isComposing) {
+ return;
+ }
+ if (currentItem === null) {
+ return;
+ }
+
+ const isEmpty = this.renderer?.getItemContent(currentItem).trim().length === 0;
+
+ /**
+ * On Enter in the last empty item, get out of list
+ */
+ if (isEmpty) {
+
+ return;
+ } else {
+ /**
+ * If current item is not empty than split current item
+ */
+ this.insertLineBreakAtCaret(currentItem);
+ }
+ }
+
+
/**
* Handle backspace
* @param event - keydown
@@ -565,6 +626,45 @@ export default class ListTabulator {
this.mergeItemWithPrevious(currentItem);
}
+ /**
+ * Handle delete
+ * @param event - keydown
+ */
+ private delete(event: KeyboardEvent): void {
+ const currentItem = this.currentItem;
+
+ if (currentItem === null) {
+ return;
+ }
+
+ /**
+ * Caret is not at end of the item
+ * Then delete button should remove letter as usual
+ */
+ if (!isCaretAtEndOfInput(currentItem)) {
+ return;
+ }
+
+ /**
+ * If backspace is pressed with selection, it should be handled as usual
+ */
+ if (window.getSelection()?.isCollapsed === false) {
+ return;
+ }
+
+ /**
+ * Prevent Editor.js backspace handling
+ */
+ event.stopPropagation();
+
+ /**
+ * Prevent default backspace behaviour
+ */
+ event.preventDefault();
+
+ this.mergeItemWithCurrent(currentItem);
+ }
+
/**
* Reduce indentation for current item
* @param event - keydown
@@ -942,6 +1042,216 @@ export default class ListTabulator {
item.remove();
}
+ /**
+ * Method that is used for merging current item with current one
+ * Content of the current item would be appended to the current item
+ * Current item children would not change nesting level
+ * @param item - current item html element
+ */
+ private mergeItemWithCurrent(item: ItemElement): void {
+ console.log(item)
+ const nextItem = item.nextElementSibling;
+ const currentItemParentNode = item.parentNode;
+
+ /**
+ * Check that parent node of the current element exists
+ */
+ if (currentItemParentNode === null) {
+ return;
+ }
+ if (!isHtmlElement(currentItemParentNode)) {
+ return;
+ }
+
+ let parentItem = currentItemParentNode.closest(`.${DefaultListCssClasses.item}`);
+
+ if (parentItem === null) {
+ parentItem = item;
+ }
+
+ const nextParentItem = parentItem?.nextElementSibling;
+
+ if (nextParentItem === undefined) {
+ return;
+ }
+
+ /**
+ * Check that current item has any next siblings to be merged with
+ */
+ if (!nextItem && !nextParentItem) {
+ return;
+ }
+
+ /**
+ * Make sure previousItem is an HTMLElement
+ */
+ if (nextItem && !isHtmlElement(nextItem)) {
+ return;
+ }
+
+ /**
+ * Make sure previousItem is an HTMLElement
+ */
+ if (nextParentItem && !isHtmlElement(nextParentItem)) {
+ return;
+ }
+
+ /**
+ * Lets compute the item which will be merged with current item text
+ */
+ let targetItem: ItemElement | null;
+
+ /**
+ * If there is a next item then we get a deepest item in its sublists
+ *
+ * Otherwise we will use the parent item
+ */
+ if (nextItem) {
+ /**
+ * Get list of all levels children of the next item
+ */
+ const childrenOfNextItem = getChildItems(nextItem, false);
+
+ /**
+ * Target item would be deepest child of the next item or next item itself
+ */
+ if (childrenOfNextItem.length !== 0 && childrenOfNextItem.length !== 0) {
+ targetItem = childrenOfNextItem[childrenOfNextItem.length - 1];
+ } else {
+ targetItem = nextItem;
+ }
+ } else {
+ targetItem = nextParentItem;
+ }
+
+
+ /**
+ * Get the target item content element
+ */
+ if (!targetItem) {
+ return;
+ }
+
+ /**
+ * Set caret to the end of the target item
+ */
+ focusItem(item, false);
+
+ /**
+ * Get next item content
+ */
+ const nextItemContent = this.renderer.getItemContent(targetItem);
+
+ /**
+ * Get target item content element
+ */
+ const targetItemContentElement = getItemContentElement(item);
+
+ /**
+ * Set a new place for caret
+ */
+ if (targetItemContentElement === null) {
+ return;
+ }
+
+ /**
+ * Update target item content by merging with current item html content
+ */
+ targetItemContentElement.insertAdjacentHTML('beforeend', nextItemContent);
+ /**
+ * Get child list of the currentItem
+ */
+ const nextItemChildrenList = getChildItems(targetItem);
+ const currentItemChildrenList = getChildItems(parentItem);
+
+ /**
+ * If item has no children, just remove item
+ * Else children of the item should be prepended to the target item child list
+ */
+ if (nextItemChildrenList.length === 0) {
+ /**
+ * Remove current item element
+ */
+ targetItem.remove();
+
+ /**
+ * If target item has empty child wrapper after merge, we need to remove child wrapper
+ * This case could be reached if the only child item of the target was merged with target
+ */
+ removeChildWrapperIfEmpty(targetItem);
+
+ return;
+ }
+
+ /**
+ * Get target for child list of the currentItem
+ * Note that previous item and parent item could not be null at the same time
+ * This case is checked before
+ */
+ const targetForChildItems = nextItem ? nextItem : parentItem!;
+
+ const targetChildWrapper = getItemChildWrapper(targetForChildItems) ?? this.renderer.renderWrapper(false);
+
+ /**
+ * Add child current item children to the target childWrapper
+ */
+ if (nextItem) {
+ nextItemChildrenList.forEach((childItem) => {
+ targetChildWrapper.appendChild(childItem);
+ });
+ } else {
+ nextItemChildrenList.forEach((childItem) => {
+ targetChildWrapper.append(childItem);
+ });
+ }
+
+ /**
+ * If we created new wrapper, then append childWrapper to the target item
+ */
+ if (getItemChildWrapper(targetForChildItems) === null) {
+ item.appendChild(targetChildWrapper);
+ }
+
+ /**
+ * Remove current item element
+ */
+ targetItem.remove();
+ }
+
+ private insertLineBreakAtCaret(currentItem: ItemElement): void {
+ const [currentNode, offset] = getCaretNodeAndOffset();
+
+ if (currentNode === null) {
+ return;
+ }
+
+ const currentItemContent = getItemContentElement(currentItem);
+
+ if (currentItemContent === null) {
+ return;
+ }
+
+ const br = document.createElement('br');
+
+ const range = document.createRange();
+ const selection = window.getSelection();
+
+ range.setStart(currentNode, offset);
+ range.setEnd(currentNode, offset);
+ range.insertNode(br);
+
+ // Posun kurzor za
+ range.setStartAfter(br);
+ range.setEndAfter(br);
+ const zwsp = document.createTextNode("\u200B");
+ br.after(zwsp);
+ range.setStartAfter(zwsp);
+ range.setEndAfter(zwsp);
+
+ selection?.removeAllRanges();
+ selection?.addRange(range);
+ }
+
/**
* Add indentation to current item
* @param event - keydown
diff --git a/src/index.ts b/src/index.ts
index 96435b35..91bf436e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -265,7 +265,6 @@ export default class EditorjsList {
*/
public save(): ListData {
this.data = this.list!.save();
-
return this.data;
}