@atlaskit/editor-plugin-paste
Version:
Paste plugin for @atlaskit/editor-core
128 lines (123 loc) • 5.12 kB
JavaScript
import { isEmptyParagraph } from '@atlaskit/editor-common/utils';
import { Fragment } from '@atlaskit/editor-prosemirror/model';
import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
import { Transform } from '@atlaskit/editor-prosemirror/transform';
import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
export function insertSliceIntoEmptyNode({
tr,
slice
}) {
tr.replaceSelection(slice);
}
export function insertSliceAtNodeEdge({
tr,
slice
}) {
const {
selection
} = tr;
const {
$cursor
} = selection;
if (!$cursor) {
return;
}
const position = !$cursor.nodeBefore ? $cursor.before() : $cursor.after();
tr.replaceRange(position, position, slice);
const startSlicePosition = tr.doc.resolve(Math.min(position + slice.content.size - slice.openEnd, tr.doc.content.size));
const direction = -1;
tr.setSelection(TextSelection.near(startSlicePosition, direction));
}
export function insertSliceIntoRangeSelectionInsideList({
tr,
slice
}) {
const {
selection: {
$to,
$from,
to,
from
}
} = tr;
// when the selection is inside of the same list item
// we can use a normal replace
if ($from.sameParent($to) || $from.depth === $to.depth) {
return tr.replaceSelection(slice);
}
// if pasting a list inside another list, ensure no empty list items get added
const newRange = $from.blockRange($to);
if (!newRange) {
return;
}
const startPos = from;
const endPos = $to.nodeAfter ? to : to + 2;
const newSlice = tr.doc.slice(endPos, newRange.end);
tr.deleteRange(startPos, newRange.end);
const mapped = tr.mapping.map(startPos);
tr.replaceRange(mapped, mapped, slice);
if (newSlice.size <= 0) {
return;
}
const newSelection = TextSelection.near(tr.doc.resolve(tr.mapping.map(mapped)), -1);
// @ts-ignore - [unblock prosemirror bump] assigning to readonly prop
newSlice.openEnd = newSlice.openStart;
tr.replaceRange(newSelection.from, newSelection.from, newSlice);
tr.setSelection(TextSelection.near(tr.doc.resolve(newSelection.from), -1));
}
export function insertSliceInsideOfPanelNodeSelected(panelNode) {
return ({
tr,
slice,
schema
}) => {
const {
selection,
selection: {
$to,
$from
}
} = tr;
const {
from: panelPosition
} = selection;
// if content of slice isn't valid for a panel node, insert the invalid node and following content after
if (panelNode && !panelNode.type.validContent(Fragment.from(slice.content))) {
var _parentNode$firstChil;
let insertPosition = $to.pos + 1;
/* Adapting above logic to handle MBE, as it currently assumes that slice can be safely inserted after the panel node, which is not the case for MBE
If insertPosition is in MBE and current slice contains invalid content for MBE, we need to insert the slice after the MBE node
*/
if (schema) {
const mbeParentOfPanel = findParentNodeOfType(schema.nodes.multiBodiedExtension)(selection);
if (mbeParentOfPanel && !mbeParentOfPanel.node.type.validContent(Fragment.from(slice.content))) {
insertPosition = mbeParentOfPanel.start + mbeParentOfPanel.node.nodeSize - 1;
}
}
tr.replaceRange(insertPosition, insertPosition, slice);
// need to delete the empty paragraph at the top of the panel
const parentNode = tr.doc.resolve($from.before()).node();
if (parentNode && parentNode.childCount > 1 && ((_parentNode$firstChil = parentNode.firstChild) === null || _parentNode$firstChil === void 0 ? void 0 : _parentNode$firstChil.type.name) === 'paragraph' && isEmptyParagraph(parentNode.firstChild)) {
const startPosDelete = tr.doc.resolve($from.before()).posAtIndex(0);
const endPosDelete = tr.doc.resolve($from.before()).posAtIndex(1);
const SIZE_OF_EMPTY_PARAGRAPH = 2; // {startPos}<p>{startPos + 1}</p>{endPos}
if (endPosDelete - startPosDelete === SIZE_OF_EMPTY_PARAGRAPH) {
tr.delete(startPosDelete, endPosDelete);
}
}
tr.setSelection(TextSelection.near(tr.doc.resolve(insertPosition + slice.content.size - slice.openStart - slice.openEnd + 1)));
return;
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const temporaryDoc = new Transform(tr.doc.type.createAndFill());
temporaryDoc.replaceRange(0, temporaryDoc.doc.content.size, slice);
const sliceWithoutInvalidListSurrounding = temporaryDoc.doc.slice(0);
const newPanel = panelNode.copy(sliceWithoutInvalidListSurrounding.content);
const panelNodeSelected = selection instanceof NodeSelection ? selection.node : null;
const replaceFrom = panelNodeSelected ? panelPosition : tr.doc.resolve(panelPosition).start();
const replaceTo = panelNodeSelected ? panelPosition + panelNodeSelected.nodeSize : replaceFrom;
tr.replaceRangeWith(replaceFrom, replaceTo, newPanel);
tr.setSelection(TextSelection.near(tr.doc.resolve($from.pos + newPanel.content.size), -1));
};
}