diff --git a/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/events/keyDown.js b/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/events/keyDown.js index 7d75353a7105..0ce618dd1403 100644 --- a/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/events/keyDown.js +++ b/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/events/keyDown.js @@ -1,9 +1,11 @@ import isHotkey from 'is-hotkey'; +import { Editor, Transforms } from 'slate'; import keyDownEnter from './keyDownEnter'; import keyDownBackspace from './keyDownBackspace'; import isCursorInNonDefaultBlock from '../locations/isCursorInNonDefaultBlock'; import toggleBlock from './toggleBlock'; +import isCursorCollapsedAfterSoftBreak from '../locations/isCursorCollapsedAfterSoftBreak'; const HEADING_HOTKEYS = { 'mod+1': 'heading-one', @@ -25,6 +27,13 @@ function keyDown(event, editor) { } } + if (isHotkey('backspace', event) && isCursorCollapsedAfterSoftBreak(editor)) { + const [, path] = Editor.previous(editor); + Transforms.removeNodes(editor, { at: path }); + event.preventDefault(); + return false; + } + if (!isCursorInNonDefaultBlock(editor)) return; if (isHotkey('enter', event)) { diff --git a/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/locations/isCursorCollapsedAfterSoftBreak.js b/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/locations/isCursorCollapsedAfterSoftBreak.js new file mode 100644 index 000000000000..7355b0061c90 --- /dev/null +++ b/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/locations/isCursorCollapsedAfterSoftBreak.js @@ -0,0 +1,13 @@ +import { Editor, Range } from 'slate'; + +function isCursorCollapsedAfterSoftBreak(editor) { + const { selection } = editor; + if (!selection) return false; + if (Range.isExpanded(selection)) return false; + + const previous = Editor.previous(editor); + + return previous && previous[0].type == 'break'; +} + +export default isCursorCollapsedAfterSoftBreak; diff --git a/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/transforms/unwrapIfCursorAtStart.js b/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/transforms/unwrapIfCursorAtStart.js index 491b50f906f5..7e728a1a5d10 100644 --- a/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/transforms/unwrapIfCursorAtStart.js +++ b/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/blocks/transforms/unwrapIfCursorAtStart.js @@ -5,18 +5,31 @@ import lowestMatchedAncestor from '../../matchers/lowestMatchedAncestor'; function unwrapIfCursorAtStart(editor, mergeWithPrevious = false) { if (editor.selection.anchor.offset !== 0) return false; - const node = Editor.above(editor, lowestMatchedAncestor(editor, 'non-default')); + let [node, path] = Editor.above(editor, lowestMatchedAncestor(editor, 'non-default')); - if (node[1].length == 0) return false; + if (path.length == 0) return false; - const isHeading = `${node[0].type}`.startsWith('heading-'); + const isHeading = `${node.type}`.startsWith('heading-'); if (isHeading) { Transforms.setNodes(editor, { type: 'paragraph' }); return false; } + const isBlock = Editor.isBlock(editor, node); + const [parentBlock, parentBlockPath] = Editor.above( + editor, + lowestMatchedAncestor(editor, 'block'), + ); + if (!isBlock) { + if (!Editor.isStart(editor, path, parentBlockPath)) { + return false; + } + + [node, path] = [parentBlock, parentBlockPath]; + } + Editor.withoutNormalizing(editor, () => { - Transforms.unwrapNodes(editor, { match: n => n.type === node[0].type, split: true }); + Transforms.unwrapNodes(editor, { match: n => n.type === node.type, split: true }); if (mergeWithPrevious) { Transforms.mergeNodes(editor); diff --git a/packages/decap-cms-widget-markdown/src/serializers/remarkSlate.js b/packages/decap-cms-widget-markdown/src/serializers/remarkSlate.js index c4a44c8e93cb..cd631a440eb2 100644 --- a/packages/decap-cms-widget-markdown/src/serializers/remarkSlate.js +++ b/packages/decap-cms-widget-markdown/src/serializers/remarkSlate.js @@ -234,7 +234,6 @@ export default function remarkToSlate({ voidCodeBlock } = {}) { * Convert simple cases that only require a type and children, with no * additional properties. */ - case 'root': case 'paragraph': case 'blockquote': case 'tableRow': @@ -242,6 +241,15 @@ export default function remarkToSlate({ voidCodeBlock } = {}) { return createBlock(typeMap[node.type], nodes); } + /** + * Root element + * If the root node is empty, we need to add a paragraph node to it. + */ + case 'root': { + const children = isEmpty(nodes) ? [createBlock('paragraph')] : nodes; + return createBlock(typeMap[node.type], children); + } + /** * List Items *