UNPKG

@atlaskit/editor-plugin-list

Version:

List plugin for @atlaskit/editor-core

96 lines (92 loc) 3.7 kB
import { GapCursorSelection } from '@atlaskit/editor-common/selection'; import { autoJoinTr } from '@atlaskit/editor-common/utils'; import { Fragment, NodeRange, Slice } from '@atlaskit/editor-prosemirror/model'; import { canSplit, findWrapping, ReplaceAroundStep } from '@atlaskit/editor-prosemirror/transform'; import { isWrappingPossible } from '../utils/selection'; /** * Wraps the selection in a list with the given type. If this results in * two adjacent lists of the same type, those will be joined together. */ export function wrapInListAndJoin(nodeType, tr) { wrapInList(nodeType)(tr); autoJoinTr(tr, (before, after) => before.type === after.type && before.type === nodeType); } // eslint-disable-next-line @typescript-eslint/no-explicit-any /** * Wraps the selection in a list with the given type and attributes. * * Adapted from https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.js#L64-L89 */ export function wrapInList(listType, attrs) { return function (tr) { const { $from, $to } = tr.selection; let range; if (tr.selection instanceof GapCursorSelection && $from.nodeAfter && isWrappingPossible(listType, tr.selection)) { const nodeSize = $from.nodeAfter.nodeSize || 1; range = $from.blockRange($from.doc.resolve($from.pos + nodeSize)); } else { range = $from.blockRange($to); } let doJoin = false; let outerRange = range; if (!range) { return false; } // This is at the top of an existing list item if (range.depth >= 2 && // @ts-ignore - missing type for compatibleContent $from.node(range.depth - 1).type.compatibleContent(listType) && range.startIndex === 0) { // Don't do anything if this is the top of the list if ($from.index(range.depth - 1) === 0) { return false; } const $insert = tr.doc.resolve(range.start - 2); outerRange = new NodeRange($insert, $insert, range.depth); if (range.endIndex < range.parent.childCount) { range = new NodeRange($from, tr.doc.resolve($to.end(range.depth)), range.depth); } doJoin = true; } // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const wrap = findWrapping(outerRange, listType, attrs, range); if (!wrap) { return false; } tr = doWrapInList(tr, range, wrap, doJoin, listType); return true; }; } /** * Internal function used by wrapInList * * Adapted from https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.js#L91-L112 */ function doWrapInList(tr, range, wrappers, joinBefore, listType) { let content = Fragment.empty; for (let i = wrappers.length - 1; i >= 0; i--) { content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content)); } tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end, new Slice(content, 0, 0), wrappers.length, true)); let found = 0; for (let i = 0; i < wrappers.length; i++) { if (wrappers[i].type === listType) { found = i + 1; } } const splitDepth = wrappers.length - found; let splitPos = range.start + wrappers.length - (joinBefore ? 2 : 0); const parent = range.parent; for (let i = range.startIndex, e = range.endIndex, first = true; i < e; i++, first = false) { if (!first && canSplit(tr.doc, splitPos, splitDepth)) { // eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace -- Ignored via go/ees017 (to be fixed) tr.split(splitPos, splitDepth); splitPos += 2 * splitDepth; } splitPos += parent.child(i).nodeSize; } return tr; }