From 554706659ee87cde68674c499eda1d2833e9f8b0 Mon Sep 17 00:00:00 2001 From: Silviu Bogan Date: Thu, 25 Jun 2020 12:45:33 +0300 Subject: [PATCH] Converting list to paragraph, list splitting w/ 1 issue --- src/TextBlock/TextBlockEdit.jsx | 7 +- src/TextBlock/decorators/withList.js | 150 +++++++++++++++--- .../keyDownHandlers/listsKeyDownHandlers.js | 4 +- src/editor/components/BlockButton.jsx | 41 ++++- 4 files changed, 171 insertions(+), 31 deletions(-) diff --git a/src/TextBlock/TextBlockEdit.jsx b/src/TextBlock/TextBlockEdit.jsx index 123708c0..113bc74a 100644 --- a/src/TextBlock/TextBlockEdit.jsx +++ b/src/TextBlock/TextBlockEdit.jsx @@ -82,7 +82,10 @@ const TextBlockEdit = (props) => { return withHandleBreak(index, onAddBlock, onChangeBlock, onSelectBlock); }, [index, onAddBlock, onChangeBlock, onSelectBlock]); - const configureWithList = useMemo(() => withList(), []); + const configureWithList = useMemo( + () => withList({ onChangeBlock, onAddBlock, onSelectBlock, index }), + [index, onAddBlock, onChangeBlock, onSelectBlock], + ); const configuredOnKeyDownList = useMemo(() => onKeyDownList(), []); @@ -106,7 +109,7 @@ const TextBlockEdit = (props) => { properties={properties} onAddBlock={onAddBlock} // TODO: uncomment this piece of code (it was commented just for testing purposes): - decorators={[configureWithList/* , configuredWithHandleBreak */]} + decorators={[configureWithList /* , configuredWithHandleBreak */]} onSelectBlock={onSelectBlock} value={value} data={data} diff --git a/src/TextBlock/decorators/withList.js b/src/TextBlock/decorators/withList.js index 8a1b2d7d..0eb4c9ec 100644 --- a/src/TextBlock/decorators/withList.js +++ b/src/TextBlock/decorators/withList.js @@ -1,5 +1,14 @@ import { Editor, Path, Point, Range, Transforms, Text, Node } from 'slate'; import { castArray } from 'lodash'; +import { + simulateBackspaceAtEndOfEditor, + createDefaultFragment, + createAndSelectNewSlateBlock, + splitEditorInTwoFragments, + replaceAllContentInEditorWith, +} from '../utils'; + +// TODO: 2 x Enter in the middle of a list should split the list in 2 parts, and just that or also insert a new empty block in the middle? /** * See {@link Range.isCollapsed}. @@ -126,14 +135,49 @@ const withResetBlockType = (options) => (editor) => { return editor; }; +const thereIsNoListItemBelowSelection = (editor) => { + let sel = editor.selection; + if (Range.isExpanded(sel)) { + Transforms.collapse(editor, { edge: 'start' }); + } + // path of paragraph (TODO: what if there is no paragraph, but a nested list?) + let pg = Path.parent(sel.anchor.path); + // Path of list-item + let p = Path.parent(pg); + // Path of numbered/bulleted list + let pp = Path.parent(p); + + let listItems = Node.children(editor, pp); + + for (let [node, path] of listItems) { + if (Path.isAfter(path, p)) { + return false; + } + } + + return true; +}; + const withList = ({ typeUl = 'bulleted-list', typeOl = 'numbered-list', typeLi = 'list-item', typeP = 'paragraph', + onChangeBlock, + onAddBlock, + onSelectBlock, + index, } = {}) => (editor) => { const { insertBreak } = editor; + const createAndSelectNewBlockAfter = (blockValue) => { + return createAndSelectNewSlateBlock(blockValue, index, { + onChangeBlock, + onAddBlock, + onSelectBlock, + }); + }; + /** * Add a new list item if selection is in a LIST_ITEM > typeP. */ @@ -146,13 +190,11 @@ const withList = ({ ); // if the selection is inside a paragraph if (paragraphNode.type === typeP) { - const [listItemNode, listItemPath] = Editor.parent( - editor, - paragraphPath, - ); + const listItemEntry = Editor.parent(editor, paragraphPath); + const [listItemNode, listItemPath] = listItemEntry; // if the paragraph is inside a list item - if (listItemNode.type === typeLi) { + if (listItemEntry && listItemNode.type === typeLi) { // if selection is expanded, delete it if (!Range.isCollapsed(editor.selection)) { Transforms.delete(editor); @@ -167,6 +209,8 @@ const withList = ({ const nextParagraphPath = Path.next(paragraphPath); const nextListItemPath = Path.next(listItemPath); + // console.log('isStart', isStart); + /** * If cursor on start of paragraph, if the paragraph is empty, remove the paragraph (and the list item), then break the block! * if it is not empty, insert a new empty list item. @@ -175,6 +219,7 @@ const withList = ({ if (isBlockTextEmpty(paragraphNode)) { console.log('remove list item and split here'); } else { + console.log('inserting new list item'); Transforms.insertNodes( editor, { @@ -186,6 +231,8 @@ const withList = ({ } } + console.log('isEnd', isEnd); + /** * If not end, split nodes, wrap a list item on the new paragraph and move it to the next list item */ @@ -204,18 +251,59 @@ const withList = ({ to: nextListItemPath, }); } else { - /** - * If end, insert a list item after and select it - */ - Transforms.insertNodes( - editor, - { - type: typeLi, - children: [{ type: typeP, children: [{ text: '' }] }], - }, - { at: nextListItemPath }, - ); - Transforms.select(editor, nextListItemPath); + if (isBlockTextEmpty(paragraphNode)) { + if (thereIsNoListItemBelowSelection(editor)) { + simulateBackspaceAtEndOfEditor(editor); + const bottomBlockValue = createDefaultFragment(); + createAndSelectNewBlockAfter(bottomBlockValue); + } else { + console.log('should split the list in two Volto blocks!'); + let [upBlock, bottomBlock] = splitEditorInTwoFragments(editor); + + let [listNode, listPath] = Editor.parent(editor, listItemPath); + + let theType = listNode.type; + + let newUpBlock = [ + { + type: theType, + children: upBlock[0].children.slice( + 0, + upBlock[0].children.length - 1, + ), + }, + ]; + + let newBottomBlock = [ + { + type: theType, + children: bottomBlock[0].children.slice( + 1, + bottomBlock[0].children.length, + ), + }, + ]; + + console.log('newUpBlock', newUpBlock); + console.log('newBottomBlock', newBottomBlock); + + replaceAllContentInEditorWith(editor, newUpBlock); + createAndSelectNewBlockAfter(newBottomBlock); + } + } else { + /** + * If end, insert a list item after and select it + */ + Transforms.insertNodes( + editor, + { + type: typeLi, + children: [{ type: typeP, children: [{ text: '' }] }], + }, + { at: nextListItemPath }, + ); + Transforms.select(editor, nextListItemPath); + } } /** @@ -231,21 +319,31 @@ const withList = ({ return; } } + } else if (editor.selection && isRangeAtRoot(editor.selection)) { + const paragraphEntry = Editor.parent(editor, editor.selection); + + if (paragraphEntry) { + // const [paragraphNode, paragraphPath] = paragraphEntry; + const [upBlock, bottomBlock] = splitEditorInTwoFragments(editor); + replaceAllContentInEditorWith(editor, upBlock); + createAndSelectNewBlockAfter(bottomBlock); + } + return; } insertBreak(); }; - const onResetListType = () => { - unwrapNodesByType(editor, typeLi, { split: true }); - unwrapNodesByType(editor, [typeUl, typeOl], { split: true }); - }; + // const onResetListType = () => { + // unwrapNodesByType(editor, typeLi, { split: true }); + // unwrapNodesByType(editor, [typeUl, typeOl], { split: true }); + // }; - editor = withResetBlockType({ - types: [typeLi], - defaultType: typeP, - onUnwrap: onResetListType, - })(editor); + // editor = withResetBlockType({ + // types: [typeLi], + // defaultType: typeP, + // onUnwrap: onResetListType, + // })(editor); return editor; }; diff --git a/src/TextBlock/keyDownHandlers/listsKeyDownHandlers.js b/src/TextBlock/keyDownHandlers/listsKeyDownHandlers.js index a3c93a84..15d1031e 100644 --- a/src/TextBlock/keyDownHandlers/listsKeyDownHandlers.js +++ b/src/TextBlock/keyDownHandlers/listsKeyDownHandlers.js @@ -192,7 +192,7 @@ export const onKeyDownList = ({ [ListHotkey.ENTER, ListHotkey.DELETE_BACKWARD].includes(e.key) && isBlockTextEmpty(paragraphNode); - if (shiftTab || deleteOnEmptyBlock) { + if (!isFirstChild(listItemPath) && (shiftTab || deleteOnEmptyBlock)) { const moved = moveUp(editor, listNode, listPath, listItemPath, options); if (moved) e.preventDefault(); } @@ -205,3 +205,5 @@ export const onKeyDownList = ({ } } }; + +export default onKeyDownList; diff --git a/src/editor/components/BlockButton.jsx b/src/editor/components/BlockButton.jsx index 1b404d2d..17675816 100644 --- a/src/editor/components/BlockButton.jsx +++ b/src/editor/components/BlockButton.jsx @@ -4,7 +4,7 @@ import { useSlate } from 'slate-react'; import { isBlockActive, toggleBlock } from '../utils'; import Button from './Button'; -import { Editor, Transforms } from 'slate'; +import { Editor, Transforms, Node, Text, Range } from 'slate'; import { castArray } from 'lodash'; // TODO: put all these functions into an utils.js file @@ -20,6 +20,7 @@ const unwrapNodesByType = (editor, types, options = {}) => { const unwrapList = ( editor, + willWrapAgain, { typeUl = 'bulleted-list', typeOl = 'numbered-list', @@ -28,6 +29,41 @@ const unwrapList = ( ) => { unwrapNodesByType(editor, typeLi); unwrapNodesByType(editor, [typeUl, typeOl], { split: true }); + + if (!willWrapAgain) { + let output = []; + let count = 0; + let children = Node.children(editor, []); + for (let [node, path] of children) { + // node is a paragraph + if (count === 0) { + output = output.concat(...node.children); + } else { + output = output.concat({ text: ' ' }, ...node.children); + } + ++count; + } + if (count === 0) { + output.push({ text: '' }); + } + + Editor.withoutNormalizing(editor, () => { + for (let i = 0; i < count; ++i) { + Transforms.removeNodes(editor, [0]); + } + // console.log('output', JSON.stringify(output, null, 2)); + Transforms.insertNodes(editor, [{ type: 'paragraph', children: output }]); + }); + + // Transforms.mergeNodes(editor, { + // at: { + // anchor: Editor.start(editor, []), + // focus: Editor.end(editor, []), + // }, + // }); + + // console.log('editor.children', JSON.stringify(editor.children, null, 2)); + } }; /** @@ -66,8 +102,9 @@ const toggleList = ( }, ) => { const isActive = isNodeInSelection(editor, typeList); + const willWrapAgain = !isActive; - unwrapList(editor, { typeUl, typeOl, typeLi }); + unwrapList(editor, willWrapAgain, { typeUl, typeOl, typeLi }); Transforms.setNodes(editor, { type: typeP,