UNPKG

@atlaskit/editor-plugin-list

Version:

List plugin for @atlaskit/editor-core

76 lines (71 loc) 3.95 kB
import { insertContentDeleteRange, isListNode } from '@atlaskit/editor-common/utils'; import { Fragment } from '@atlaskit/editor-prosemirror/model'; // Case for when a users selection is at the end of a paragraph, the paragraph // is followed by a list, and they delete forward export const joinListItemWithParagraph = ({ tr, $next, $head }) => { // For empty paragraphs before a list if ($head.parent.content.size < 1) { insertContentDeleteRange(tr, tr => tr.doc.resolve($head.pos), [], [[$head.pos - 1, $head.pos]]); return true; } const paragraphPosition = $head.pos; const list = tr.doc.nodeAt($next.pos - 1); const firstListItem = tr.doc.nodeAt($next.pos); if (!list || !firstListItem) { return false; } const firstChildNodeOfFirstListItem = firstListItem.firstChild; if (!firstChildNodeOfFirstListItem) { return false; } const lastChildOfFirstListItem = firstListItem.lastChild; const firstGrandchildOfFirstListItem = firstChildNodeOfFirstListItem.firstChild; const firstListItemHasOneChildWithNoNestedLists = hasSingleChild(firstListItem) && firstChildNodeOfFirstListItem.childCount < 2 && $next.nodeAfter; const firstListItemContainsParagraphAndNestedList = !hasSingleChild(firstListItem) && lastChildOfFirstListItem && isListNode(lastChildOfFirstListItem); const insertions = []; const deletions = []; // For lists that only have one list item with no children - need to remove remaining list if (hasSingleChild(list) && hasSingleChild(firstListItem) && $next.nodeAfter) { deletions.push([tr.mapping.map($next.pos - 1), tr.mapping.map($next.pos + $next.nodeAfter.nodeSize + 1)]); } // For first list items that have a paragraph and a list if (firstListItemContainsParagraphAndNestedList) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const firstListItemNestedList = Fragment.from(lastChildOfFirstListItem.content); insertions.push([firstListItemNestedList, tr.mapping.map($next.pos)]); } // For first list item has one child & no nested lists OR first list items that have a paragraph and a list if (firstListItemHasOneChildWithNoNestedLists || firstListItemContainsParagraphAndNestedList) { deletions.push([tr.mapping.map($next.pos), tr.mapping.map($next.pos + firstListItem.nodeSize - 1)]); const firstListItemText = Fragment.from(firstChildNodeOfFirstListItem.content); insertions.push([firstListItemText, paragraphPosition]); insertContentDeleteRange(tr, tr => tr.doc.resolve($head.pos), insertions, deletions); return true; } // For any first list items that have multiple children (eg. multiple paragraphs) if (firstListItem.childCount > 1) { insertions.push([Fragment.from(firstChildNodeOfFirstListItem.content), paragraphPosition]); deletions.push([tr.mapping.map($next.pos + 1), tr.mapping.map($next.pos + firstChildNodeOfFirstListItem.nodeSize + 1)]); insertContentDeleteRange(tr, tr => tr.doc.resolve($head.pos), insertions, deletions); return true; } // For any remaining first list items that have a single child (eg. single paragraph, multiple lines of text) if (firstGrandchildOfFirstListItem && firstGrandchildOfFirstListItem.type.name === 'hardBreak') { const nodeSizeOfGrandchild = firstGrandchildOfFirstListItem ? firstGrandchildOfFirstListItem.nodeSize : 0; deletions.push([tr.mapping.map($next.pos + 2), tr.mapping.map($next.pos + 2 + nodeSizeOfGrandchild)]); } else { insertions.push([Fragment.from(firstChildNodeOfFirstListItem.content), paragraphPosition]); const nodeSizeOfFirstChild = firstChildNodeOfFirstListItem.nodeSize; deletions.push([tr.mapping.map($next.pos), tr.mapping.map($next.pos + 2 + nodeSizeOfFirstChild)]); } insertContentDeleteRange(tr, tr => tr.doc.resolve($head.pos), insertions, deletions); return true; }; const hasSingleChild = node => { return node.childCount === 1; };