diff --git a/src/TextBlock/TextBlockEdit.jsx b/src/TextBlock/TextBlockEdit.jsx
index 5ebe9351..26ef1769 100644
--- a/src/TextBlock/TextBlockEdit.jsx
+++ b/src/TextBlock/TextBlockEdit.jsx
@@ -6,7 +6,7 @@ import React, { useMemo } from 'react';
import { Editor, Transforms, Range, Node, Point } from 'slate';
import SlateEditor from './../editor';
import {
- fixSelection,
+ // fixSelection,
isCursorAtBlockEnd,
isCursorAtBlockStart,
} from './../editor/utils';
@@ -18,6 +18,121 @@ import ShortcutListing from './ShortcutListing';
import { LISTTYPES } from './constants';
import { withHandleBreak } from './decorators';
+function getPreviousBlock(index, properties) {
+ if (index === 0) return;
+
+ // join this block with previous block, if previous block is slate
+ const blocksFieldname = getBlocksFieldname(properties);
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
+
+ const blocks_layout = properties[blocksLayoutFieldname];
+ const prevBlockId = blocks_layout.items[index - 1];
+ const prevBlock = properties[blocksFieldname][prevBlockId];
+ return prevBlock;
+}
+
+function isCursorInList(editor) {
+ const [listItemWithSelection, listItemWithSelectionPath] = Editor.above(
+ editor,
+ {
+ match: (n) => n.type === 'list-item',
+ },
+ );
+
+ // whether the selection is inside a list item
+ const listItemCase =
+ Range.isCollapsed(editor.selection) && listItemWithSelection;
+
+ return [listItemCase, listItemWithSelectionPath, listItemCase];
+}
+
+function handleBackspaceInList(editor, prevBlock) {
+ const [listItemWithSelection, listItemWithSelectionPath] = Editor.above(
+ editor,
+ {
+ match: (n) => n.type === 'list-item',
+ },
+ );
+
+ // whether the selection is inside a list item
+ const listItemCase =
+ Range.isCollapsed(editor.selection) && listItemWithSelection;
+
+ // if the selection is collapsed and at node and offset 0
+ // or collapsed inside a list item
+ if (isCursorAtBlockStart(editor) || listItemCase) {
+ // are we in a list-item and is cursor at the beginning of the list item?
+ if (listItemCase && editor.selection.anchor.offset === 0) {
+ if (
+ Node.parent(editor, listItemWithSelectionPath).children.indexOf(
+ listItemWithSelection,
+ ) === 0
+ ) {
+ // the cursor is inside the first list-item
+ event.stopPropagation();
+ event.preventDefault();
+ return false; // TODO: join with previous
element, if exists
+ }
+ // else handle by deleting the list-item
+ Transforms.liftNodes(editor);
+ Transforms.mergeNodes(editor);
+ Transforms.mergeNodes(editor, { at: [1] });
+ //console.log('editor.children', editor.children);
+
+ event.stopPropagation();
+ event.preventDefault();
+
+ return;
+ }
+ }
+ return true;
+}
+
+function handleBackspaceInText(editor, prevBlock) {
+ // To work around current architecture limitations, read the value
+ // from previous block. Replace it in the current editor (over
+ // which we have control), join with current block value, then use
+ // this result for previous block, delete current block
+
+ const prev = prevBlock.value;
+
+ // collapse the selection to its start point
+ Transforms.collapse(editor, { edge: 'start' });
+
+ // TODO: do we really want to insert this text here?
+
+ // insert a space before the left edge of the selection
+ editor.apply({
+ type: 'insert_text',
+ path: [0, 0],
+ offset: 0,
+ text: ' ',
+ });
+
+ // collapse the selection to its start point
+ Transforms.collapse(editor, { edge: 'start' });
+
+ // insert the contents of the previous editor into the current editor
+ Transforms.insertNodes(editor, prev, {
+ at: Editor.start(editor, []),
+ });
+
+ // not needed currently: delete the useless space inserted above
+ //Editor.deleteBackward(editor, { unit: 'character' });
+
+ // merge the contents separated by the collapsed selection
+ Transforms.mergeNodes(editor);
+}
+
+function blockIsEmpty(editor) {
+ const value = editor.children;
+ // TODO: this is very optimistic, we might have void nodes that are
+ // meaningful. We should test if only one child, with empty text
+ if (plaintext_serialize(value || []).length === 0) {
+ return true;
+ }
+}
+
const TextBlockEdit = (props) => {
const {
data,
@@ -104,124 +219,52 @@ const TextBlockEdit = (props) => {
},
Backspace: ({ editor, event }) => {
- const { value } = data;
-
- // can be undefined
- const [listItemWithSelection, listItemWithSelectionPath] = Editor.above(
- editor,
- {
- match: (n) => n.type === 'list-item',
- },
- );
-
- // whether the selection is inside a list item
- const listItemCase =
- Range.isCollapsed(editor.selection) && listItemWithSelection;
-
- // if the selection is collapsed and at node and offset 0
- // or collapsed inside a list item
- if (isCursorAtBlockStart(editor) || listItemCase) {
- // TODO: this is very optimistic, we might have void nodes that are
- // meaningful. We should test if only one child, with empty text
- if (plaintext_serialize(value || []).length === 0) {
- event.preventDefault();
- return onDeleteBlock(block, true);
- }
-
- // are we in a list-item and is cursor at the beginning of the list item?
- if (listItemCase && editor.selection.anchor.offset === 0) {
- if (
- Node.parent(editor, listItemWithSelectionPath).children.indexOf(
- listItemWithSelection,
- ) === 0
- ) {
- // the cursor is inside the first list-item
- event.stopPropagation();
- event.preventDefault();
- return false; // TODO: join with previous element, if exists
- }
- // else handle by deleting the list-item
- Transforms.liftNodes(editor);
- Transforms.mergeNodes(editor);
- Transforms.mergeNodes(editor, { at: [1] });
- //console.log('editor.children', editor.children);
-
- event.stopPropagation();
- event.preventDefault();
-
- return;
- }
-
- // join this block with previous block, if previous block is slate
- const blocksFieldname = getBlocksFieldname(properties);
- const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
-
- const blocks_layout = properties[blocksLayoutFieldname];
- const prevBlockId = blocks_layout.items[index - 1];
- const prevBlock = properties[blocksFieldname][prevBlockId];
-
- if (prevBlock['@type'] !== 'slate') {
- return;
- }
-
- // To work around current architecture limitations, read the value
- // from previous block. Replace it in the current editor (over
- // which we have control), join with current block value, then use
- // this result for previous block, delete current block
-
- event.stopPropagation();
+ if (blockIsEmpty(editor)) {
event.preventDefault();
+ return onDeleteBlock(block, true);
+ }
- const prev = prevBlock.value;
+ const [prevBlock = {}, prevBlockId] = getPreviousBlock(
+ index,
+ properties,
+ );
- // collapse the selection to its start point
- Transforms.collapse(editor, { edge: 'start' });
+ if (prevBlock['@type'] !== 'slate') return;
- // TODO: do we really want to insert this text here?
+ const isAtBlockStart = isCursorAtBlockStart(editor);
- // insert a space before the left edge of the selection
- editor.apply({
- type: 'insert_text',
- path: [0, 0],
- offset: 0,
- text: ' ',
- });
+ if (!isAtBlockStart) return;
- // collapse the selection to its start point
- Transforms.collapse(editor, { edge: 'start' });
+ event.stopPropagation();
+ event.preventDefault();
- // insert the contents of the previous editor into the current editor
- Transforms.insertNodes(editor, prev, {
- at: Editor.start(editor, []),
- });
+ if (isCursorInList(editor)) {
+ handleBackspaceInList(editor);
+ } else {
+ handleBackspaceInText(editor);
+ }
- // not needed currently: delete the useless space inserted above
- //Editor.deleteBackward(editor, { unit: 'character' });
-
- // merge the contents separated by the collapsed selection
- Transforms.mergeNodes(editor);
-
- const selection = JSON.parse(JSON.stringify(editor.selection));
- const combined = JSON.parse(JSON.stringify(editor.children));
-
- // TODO: don't remove undo history, etc
- // TODO: after Enter, the current filled-with-previous-block
- // block is visible for a fraction of second
-
- // setTimeout is needed to ensure setState has been successfully
- // executed in Form.jsx. See
- // https://github.com/plone/volto/issues/1519
- onDeleteBlock(block, true);
- setTimeout(() => {
- onChangeBlock(prevBlockId, {
- '@type': 'slate',
- value: combined,
- selection,
- plaintext: plaintext_serialize(combined || []),
- });
+ const selection = JSON.parse(JSON.stringify(editor.selection));
+ const combined = JSON.parse(JSON.stringify(editor.children));
+
+ // TODO: don't remove undo history, etc
+ // TODO: after Enter, the current filled-with-previous-block
+ // block is visible for a fraction of second
+
+ // setTimeout ensures setState has been successfully
+ // executed in Form.jsx. See
+ // https://github.com/plone/volto/issues/1519
+ setTimeout(() => {
+ onChangeBlock(prevBlockId, {
+ '@type': 'slate',
+ value: combined,
+ selection,
+ plaintext: plaintext_serialize(combined || []),
});
- }
- return true;
+ setTimeout(() => onDeleteBlock(block, true));
+ });
+
+ return;
},
...settings.slate?.keyDownHandlers,
@@ -229,12 +272,11 @@ const TextBlockEdit = (props) => {
}, [
block,
blockNode,
- data,
index,
- onChangeBlock,
- onDeleteBlock,
onFocusNextBlock,
onFocusPreviousBlock,
+ onDeleteBlock,
+ onChangeBlock,
properties,
]);