UNPKG

@atlaskit/editor-plugin-list

Version:

List plugin for @atlaskit/editor-core

131 lines 5.12 kB
import { normalizeListItemsSelection } from '@atlaskit/editor-common/lists'; import { GapCursorSelection } from '@atlaskit/editor-common/selection'; import { isListItemNode, isListNode } from '@atlaskit/editor-common/utils'; import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model'; import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import { findFirstParentListItemNode } from '../utils/find'; export const indentListItemsSelected = tr => { var _currentListItem$cont; const originalSelection = tr.selection; const normalizedSelection = normalizeListItemsSelection({ selection: originalSelection, doc: tr.doc }); const { $from, $to } = normalizedSelection; const range = calculateRange({ selection: normalizedSelection }); if (!range) { return false; } const listItemsSelected = { from: findFirstParentListItemNode($from), to: findFirstParentListItemNode($to) }; if (listItemsSelected.from === null || listItemsSelected.to === null) { return null; } const resolvedPos = tr.doc.resolve(listItemsSelected.from.pos); const listItemIndex = resolvedPos.index(); // @ts-ignore const positionListItemPosition = resolvedPos.posAtIndex(listItemIndex - 1); const currentListItemPosition = resolvedPos.posAtIndex(listItemIndex); const previousListItem = tr.doc.nodeAt(positionListItemPosition); const currentListItem = tr.doc.nodeAt(currentListItemPosition); const currentListItemContent = currentListItem === null || currentListItem === void 0 ? void 0 : (_currentListItem$cont = currentListItem.content) === null || _currentListItem$cont === void 0 ? void 0 : _currentListItem$cont.content; const hasLastItemExtension = currentListItemContent !== undefined && (currentListItemContent === null || currentListItemContent === void 0 ? void 0 : currentListItemContent.length) > 0 ? currentListItemContent[currentListItemContent.length - 1].type.name === 'extension' : false; if (!previousListItem || !isListItemNode(previousListItem)) { return null; } if (isListItemNode(previousListItem) && listItemIndex === 0) { return null; } const listItemSelectedCommonParent = range.parent; const previousNestedList = isListNode(previousListItem.lastChild) ? previousListItem.lastChild : null; const listNodeType = previousNestedList ? previousNestedList.type : listItemSelectedCommonParent.type; const nestedList = listItemsSelected.to.node.lastChild; const nestedItemsOffset = nestedList && isListNode(nestedList) ? nestedList.nodeSize : 0; const from = listItemsSelected.from.pos; const to = listItemsSelected.to.pos + listItemsSelected.to.node.nodeSize - nestedItemsOffset; const [sliceSelected, nestedListItemsLeftover] = createIndentedListItemsSlice({ tr, listNodeType, range, from, to, hasLastItemExtension }); const hasPreviousNestedList = Boolean(previousNestedList); const start = from - 1; tr.replaceRange(hasPreviousNestedList ? start - 1 : start, range.end, sliceSelected); const leftoverContentPosition = tr.mapping.map(to) - 2; if (nestedListItemsLeftover.openStart === 0) { tr.insert(leftoverContentPosition, nestedListItemsLeftover.content); } else { tr.replace(leftoverContentPosition - nestedListItemsLeftover.openStart, leftoverContentPosition - nestedListItemsLeftover.openStart, nestedListItemsLeftover); } const nextSelection = calculateNewSelection({ originalSelection, normalizedSelection, tr, hasPreviousNestedList }); tr.setSelection(nextSelection); }; const calculateRange = ({ selection }) => { const { $from, $to } = selection; const range = $from.blockRange($to, isListNode); if (!range) { return null; } return range; }; const calculateNewSelection = ({ tr, normalizedSelection, originalSelection, hasPreviousNestedList }) => { const offset = hasPreviousNestedList ? 2 : 0; const { $from, $to } = normalizedSelection; if (normalizedSelection instanceof GapCursorSelection) { const nextSelectionFrom = tr.doc.resolve($from.pos - offset); return new GapCursorSelection(nextSelectionFrom, normalizedSelection.side); } if (originalSelection instanceof NodeSelection) { return NodeSelection.create(tr.doc, $from.pos - offset); } const { $from: nextSelectionFrom } = Selection.near(tr.doc.resolve($from.pos - offset)); const { $to: nextSelectionTo } = Selection.near(tr.doc.resolve($to.pos - offset), -1); return new TextSelection(nextSelectionFrom, nextSelectionTo); }; const createIndentedListItemsSlice = ({ tr, from, to, listNodeType, range, hasLastItemExtension }) => { const listItemsSlice = tr.doc.slice(from, hasLastItemExtension ? to : to - 2); const listFragment = Fragment.from(listNodeType.create(null, listItemsSlice.content)); const nonSelectedListItemsSlice = tr.doc.slice(to, range.end - 2); const openStart = tr.doc.slice(from - 1, range.end).openStart; const slice = new Slice(listFragment, openStart, 0); return [slice, nonSelectedListItemsSlice]; };