@atlaskit/editor-plugin-paste
Version:
Paste plugin for @atlaskit/editor-core
139 lines (136 loc) • 6.24 kB
JavaScript
import { createToggleBlockMarkOnRangeNext } from '@atlaskit/editor-common/commands';
import { isListNode } from '@atlaskit/editor-common/utils';
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
import { Selection } from '@atlaskit/editor-prosemirror/state';
import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { isCursorSelectionAtTextStartOrEnd, isEmptyNode, isSelectionInsidePanel } from '../index';
import { insertSliceAtNodeEdge, insertSliceInsideOfPanelNodeSelected, insertSliceIntoEmptyNode, insertSliceIntoRangeSelectionInsideList } from './lists';
export function insertSliceForLists(_ref) {
var _slice$content$firstC;
var tr = _ref.tr,
slice = _ref.slice,
schema = _ref.schema;
var selection = tr.selection,
_tr$selection = tr.selection,
$to = _tr$selection.$to,
$from = _tr$selection.$from;
var _ref2 = selection,
$cursor = _ref2.$cursor;
var panelNode = isSelectionInsidePanel(selection);
var selectionIsInsideList = $from.blockRange($to, isListNode);
if (!$cursor && selectionIsInsideList) {
return insertSliceIntoRangeSelectionInsideList({
tr: tr,
slice: slice
});
}
// if inside an empty panel, try and insert content inside it rather than replace it
if (panelNode && isEmptyNode(panelNode) && $from.node() === $to.node()) {
return insertSliceInsideOfPanelNodeSelected(panelNode)({
tr: tr,
slice: slice,
schema: schema
});
}
if (!$cursor || selectionIsInsideList) {
return tr.replaceSelection(slice);
}
if (isEmptyNode(tr.doc.resolve($cursor.pos).node())) {
return insertSliceIntoEmptyNode({
tr: tr,
slice: slice
});
}
// When pasting a single list item into an action or decision, we skip the special "insert at node edge"
// logic so that prosemirror pastes the list's content into the action/decision, rather than
// pasting a whole list node directly after the action/decision item. (But we still preserve the
// existing "insert at" node edge" behaviour if dealing with a list with more than one item, so that
// it still inserts whole list node after the action/decision item).
var pastingIntoActionOrDecision = Boolean(findParentNodeOfType([schema.nodes.taskList, schema.nodes.decisionList])(selection));
var oneListItem = slice.content.childCount === 1 && isListNode(slice.content.firstChild) && ((_slice$content$firstC = slice.content.firstChild) === null || _slice$content$firstC === void 0 ? void 0 : _slice$content$firstC.childCount) === 1;
if (!(pastingIntoActionOrDecision && oneListItem) && isCursorSelectionAtTextStartOrEnd(selection)) {
return insertSliceAtNodeEdge({
tr: tr,
slice: slice
});
}
tr.replaceSelection(slice);
}
var stripFontSizeInsideBlockquoteLists = function stripFontSizeInsideBlockquoteLists(tr, blockquotePos) {
var _tr$doc$type$schema = tr.doc.type.schema,
fontSize = _tr$doc$type$schema.marks.fontSize,
_tr$doc$type$schema$n = _tr$doc$type$schema.nodes,
paragraph = _tr$doc$type$schema$n.paragraph,
listItem = _tr$doc$type$schema$n.listItem,
taskItem = _tr$doc$type$schema$n.taskItem,
blockTaskItem = _tr$doc$type$schema$n.blockTaskItem;
var listItemParentNodeTypes = [listItem, taskItem, blockTaskItem];
if (!fontSize || !expValEquals('platform_editor_small_font_size', 'isEnabled', true)) {
return;
}
var containingBlockquote = tr.doc.nodeAt(blockquotePos);
if (!containingBlockquote) {
return;
}
var from = blockquotePos + 1;
var to = blockquotePos + containingBlockquote.nodeSize - 1;
createToggleBlockMarkOnRangeNext(fontSize, function () {
return false;
}, function (_schema, node, parent) {
return node.type === paragraph && !!parent && listItemParentNodeTypes.some(function (nodeType) {
return nodeType === parent.type;
});
})(from, to, tr);
};
export function insertSliceInsideBlockquote(_ref3) {
var tr = _ref3.tr,
slice = _ref3.slice;
//insert blockquote explicitly and set the selection in blockquote since replaceSelection will only insert the list
var schema = tr.doc.type.schema;
tr.replaceSelection(new Slice(Fragment.from(schema.nodes.blockquote.createAndFill()), 0, 0));
updateSelectionAfterReplace({
tr: tr
});
tr.replaceSelection(slice);
if (expValEquals('platform_editor_small_font_size', 'isEnabled', true)) {
var insertedBlockquotePos = findParentNodeOfType(schema.nodes.blockquote)(tr.selection);
if (!insertedBlockquotePos) {
return;
}
stripFontSizeInsideBlockquoteLists(tr, insertedBlockquotePos.pos);
}
}
export function updateSelectionAfterReplace(_ref4) {
var tr = _ref4.tr;
// ProseMirror doesn't give a proper way to tell us where something was inserted.
// However, we can know "how" it inserted something.
//
// So, instead of weird depth calculations, we can use the step produced by the transform.
// For instance:
// The `replaceStep.to and replaceStep.from`, tell us the real position
// where the content will be insert.
// Then, we can use the `tr.mapping.map` to the updated position after the replace operation
var replaceStep = tr.steps[0];
if (!(replaceStep instanceof ReplaceStep)) {
return tr;
}
var nextPosition = tr.mapping.map(replaceStep.to);
// The findFrom will make search for both: TextSelection and NodeSelections.
var nextSelection = Selection.findFrom(tr.doc.resolve(Math.min(nextPosition, tr.doc.content.size)), -1);
if (nextSelection) {
tr.setSelection(nextSelection);
}
}
export function insertSliceForTaskInsideList(_ref5) {
var tr = _ref5.tr,
slice = _ref5.slice;
var schema = tr.doc.type.schema;
//To avoid the list being replaced with the tasklist, enclose the slice within a taskItem.
var selectionBeforeReplace = tr.selection.from;
tr.replaceSelection(new Slice(Fragment.from(schema.nodes.taskItem.createAndFill()), 0, 0));
var nextSelection = Selection.near(tr.doc.resolve(selectionBeforeReplace + 1));
tr.setSelection(nextSelection);
tr.replaceSelection(slice);
}