@atlaskit/editor-plugin-block-controls
Version:
Block controls plugin for @atlaskit/editor-core
231 lines (225 loc) • 11.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.setCursorPositionAtMovedNode = exports.selectNode = exports.rootTaskListDepth = exports.rootListDepth = exports.newGetSelection = exports.isNodeWithCodeBlock = exports.isHandleCorrelatedToSelection = exports.getSelection = exports.getInlineNodePos = void 0;
var _selection2 = require("@atlaskit/editor-common/selection");
var _toolbarFlagCheck = require("@atlaskit/editor-common/toolbar-flag-check");
var _state = require("@atlaskit/editor-prosemirror/state");
var _utils = require("@atlaskit/editor-prosemirror/utils");
var _utils2 = require("@atlaskit/editor-tables/utils");
var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
var getInlineNodePos = exports.getInlineNodePos = function getInlineNodePos(doc, start, nodeSize) {
var $startPos = doc.resolve(start);
// To trigger the annotation floating toolbar for non-selectable node, we need to select inline nodes
// Find the first inline node in the node
var inlineNodePos = start;
var foundInlineNode = false;
var inlineNodeEndPos = 0;
doc.nodesBetween($startPos.pos, $startPos.pos + nodeSize, function (n, pos) {
if (n.isInline) {
inlineNodeEndPos = pos + n.nodeSize;
}
if (n.isInline && !foundInlineNode) {
inlineNodePos = pos;
foundInlineNode = true;
}
return true;
});
return {
inlineNodePos: inlineNodePos,
inlineNodeEndPos: inlineNodeEndPos
};
};
var isNodeWithCodeBlock = exports.isNodeWithCodeBlock = function isNodeWithCodeBlock(tr, start, nodeSize) {
var $startPos = tr.doc.resolve(start);
var hasCodeBlock = false;
tr.doc.nodesBetween($startPos.pos, $startPos.pos + nodeSize, function (n) {
if (['codeBlock'].includes(n.type.name)) {
hasCodeBlock = true;
}
});
return hasCodeBlock;
};
var isNodeWithMediaOrExtension = function isNodeWithMediaOrExtension(doc, start, nodeSize) {
var $startPos = doc.resolve(start);
var hasMediaOrExtension = false;
doc.nodesBetween($startPos.pos, $startPos.pos + nodeSize, function (n) {
if (['media', 'extension'].includes(n.type.name)) {
hasMediaOrExtension = true;
}
});
return hasMediaOrExtension;
};
var oldGetSelection = function oldGetSelection(tr, start) {
var node = tr.doc.nodeAt(start);
var isNodeSelection = node && _state.NodeSelection.isSelectable(node);
var nodeSize = node ? node.nodeSize : 1;
var $startPos = tr.doc.resolve(start);
var nodeName = node === null || node === void 0 ? void 0 : node.type.name;
var isBlockQuoteWithMediaOrExtension = nodeName === 'blockquote' && isNodeWithMediaOrExtension(tr.doc, start, nodeSize);
var isListWithMediaOrExtension = nodeName === 'bulletList' && isNodeWithMediaOrExtension(tr.doc, start, nodeSize) || nodeName === 'orderedList' && isNodeWithMediaOrExtension(tr.doc, start, nodeSize);
if (isNodeSelection && nodeName !== 'blockquote' || isListWithMediaOrExtension || isBlockQuoteWithMediaOrExtension ||
// decisionList/layoutColumn node is not selectable, but we want to select the whole node not just text
['decisionList', 'layoutColumn'].includes(nodeName || '')) {
return new _state.NodeSelection($startPos);
} else if (nodeName === 'mediaGroup' && (node === null || node === void 0 ? void 0 : node.childCount) === 1) {
var $mediaStartPos = tr.doc.resolve(start + 1);
return new _state.NodeSelection($mediaStartPos);
} else if (
// Even though mediaGroup is not selectable,
// we need a quick way to make all child media nodes appear as selected without the need for a custom selection
nodeName === 'mediaGroup') {
return new _state.NodeSelection($startPos);
} else if (nodeName === 'taskList') {
return _state.TextSelection.create(tr.doc, start, start + nodeSize);
} else {
var _getInlineNodePos = getInlineNodePos(tr.doc, start, nodeSize),
inlineNodePos = _getInlineNodePos.inlineNodePos,
inlineNodeEndPos = _getInlineNodePos.inlineNodeEndPos;
return new _state.TextSelection(tr.doc.resolve(inlineNodePos), tr.doc.resolve(inlineNodeEndPos));
}
};
/**
* Gets the appropriate selection for the node at the given start position.
*
* @param doc The ProseMirror document.
* @param selectionEmpty Indicates if the current selection is empty.
* @param start The start position of the node.
* @returns The appropriate selection for the node.
*/
var newGetSelection = exports.newGetSelection = function newGetSelection(doc, selectionEmpty, start) {
var node = doc.nodeAt(start);
var isNodeSelection = node && _state.NodeSelection.isSelectable(node);
var nodeSize = node ? node.nodeSize : 1;
var nodeName = node === null || node === void 0 ? void 0 : node.type.name;
if ((0, _experiments.editorExperiment)('platform_editor_block_menu', true)) {
// if mediaGroup only has a single child, we want to select the child
if (nodeName === 'mediaGroup' && (node === null || node === void 0 ? void 0 : node.childCount) === 1) {
var $mediaStartPos = doc.resolve(start + 1);
return new _state.NodeSelection($mediaStartPos);
}
return new _state.NodeSelection(doc.resolve(start));
}
// this is a fix for empty paragraph selection - put first to avoid any extra work
if (nodeName === 'paragraph' && selectionEmpty && (node === null || node === void 0 ? void 0 : node.childCount) === 0) {
return false;
}
var isBlockQuoteWithMediaOrExtension = nodeName === 'blockquote' && isNodeWithMediaOrExtension(doc, start, nodeSize);
var isListWithMediaOrExtension = nodeName === 'bulletList' && isNodeWithMediaOrExtension(doc, start, nodeSize) || nodeName === 'orderedList' && isNodeWithMediaOrExtension(doc, start, nodeSize);
if (isNodeSelection && nodeName !== 'blockquote' || isListWithMediaOrExtension || isBlockQuoteWithMediaOrExtension ||
// decisionList/layoutColumn node is not selectable, but we want to select the whole node not just text
['decisionList', 'layoutColumn'].includes(nodeName || '') || nodeName === 'mediaGroup' && typeof (node === null || node === void 0 ? void 0 : node.childCount) === 'number' && (node === null || node === void 0 ? void 0 : node.childCount) > 1) {
return new _state.NodeSelection(doc.resolve(start));
}
// if mediaGroup only has a single child, we want to select the child
if (nodeName === 'mediaGroup') {
var _$mediaStartPos = doc.resolve(start + 1);
return new _state.NodeSelection(_$mediaStartPos);
}
if (nodeName === 'taskList') {
return _state.TextSelection.create(doc, start, start + nodeSize);
}
var _getInlineNodePos2 = getInlineNodePos(doc, start, nodeSize),
inlineNodePos = _getInlineNodePos2.inlineNodePos,
inlineNodeEndPos = _getInlineNodePos2.inlineNodeEndPos;
return new _state.TextSelection(doc.resolve(inlineNodePos), doc.resolve(inlineNodeEndPos));
};
var getSelection = exports.getSelection = function getSelection(tr, start, api) {
if ((0, _toolbarFlagCheck.areToolbarFlagsEnabled)(Boolean(api === null || api === void 0 ? void 0 : api.toolbar)) || (0, _experiments.editorExperiment)('platform_editor_block_menu', true)) {
return newGetSelection(tr.doc, tr.selection.empty, start);
}
return oldGetSelection(tr, start);
};
var selectNode = exports.selectNode = function selectNode(tr, start, nodeType, api) {
// For table, we need to do cell selection instead of node selection
if (nodeType === 'table') {
tr = (0, _utils2.selectTableClosestToPos)(tr, tr.doc.resolve(start + 1));
return tr;
}
var selection = getSelection(tr, start, api);
if (selection) {
tr.setSelection(selection);
}
return tr;
};
var setCursorPositionAtMovedNode = exports.setCursorPositionAtMovedNode = function setCursorPositionAtMovedNode(tr, start, api) {
var node = tr.doc.nodeAt(start);
var isNodeSelection = node && _state.NodeSelection.isSelectable(node);
var nodeSize = node ? node.nodeSize : 1;
var selection;
// decisionList node is not selectable, but we want to select the whole node not just text
// blockQuote is selectable, but we want to set cursor at the inline end Pos instead of the gap cursor as this causes jittering post drop
if (isNodeSelection && node.type.name !== 'blockquote' || (node === null || node === void 0 ? void 0 : node.type.name) === 'decisionList') {
selection = new _selection2.GapCursorSelection(tr.doc.resolve(start + node.nodeSize), _selection2.Side.RIGHT);
tr.setSelection(selection);
return tr;
}
// this is a fix for empty paragraph selection - can safely use start position as the paragraph is empty
if ((node === null || node === void 0 ? void 0 : node.type.name) === 'paragraph' && (node === null || node === void 0 ? void 0 : node.childCount) === 0 && (0, _toolbarFlagCheck.areToolbarFlagsEnabled)(Boolean(api === null || api === void 0 ? void 0 : api.toolbar))) {
var _selection = new _state.TextSelection(tr.doc.resolve(start));
tr.setSelection(_selection);
return tr;
}
var _getInlineNodePos3 = getInlineNodePos(tr.doc, start, nodeSize),
inlineNodeEndPos = _getInlineNodePos3.inlineNodeEndPos;
selection = new _state.TextSelection(tr.doc.resolve(inlineNodeEndPos));
tr.setSelection(selection);
return tr;
};
/**
* Checks if handle position is with the selection or corresponds to a (partially) selected node
* @param state
* @param selection
* @param handlePos
* @returns
*/
var isHandleCorrelatedToSelection = exports.isHandleCorrelatedToSelection = function isHandleCorrelatedToSelection(state, selection, handlePos) {
if (selection.empty) {
return false;
}
var nodeStart;
var $selectionFrom = selection.$from;
nodeStart = $selectionFrom.before($selectionFrom.sharedDepth(selection.to) + 1);
if (nodeStart === $selectionFrom.pos) {
nodeStart = $selectionFrom.depth ? $selectionFrom.before() : $selectionFrom.pos;
}
var $resolvedNodePos = state.doc.resolve(nodeStart);
if (['tableRow', 'tableCell', 'tableHeader'].includes($resolvedNodePos.node().type.name)) {
var parentNodeFindRes = (0, _utils.findParentNodeOfType)(state.schema.nodes['table'])(selection);
var tablePos = parentNodeFindRes === null || parentNodeFindRes === void 0 ? void 0 : parentNodeFindRes.pos;
nodeStart = typeof tablePos === 'undefined' ? nodeStart : tablePos;
} else if (['listItem'].includes($resolvedNodePos.node().type.name)) {
nodeStart = $resolvedNodePos.before(rootListDepth($resolvedNodePos));
} else if (['taskList'].includes($resolvedNodePos.node().type.name)) {
var listdepth = rootTaskListDepth($resolvedNodePos);
nodeStart = $resolvedNodePos.before(listdepth);
} else if (['blockquote'].includes($resolvedNodePos.node().type.name)) {
nodeStart = $resolvedNodePos.before();
}
return Boolean(handlePos < selection.$to.pos && handlePos >= nodeStart);
};
var rootListDepth = exports.rootListDepth = function rootListDepth(itemPos) {
var depth;
for (var i = itemPos.depth; i > 1; i -= 2) {
var node = itemPos.node(i);
if (node.type.name === 'listItem') {
depth = i - 1;
} else {
break;
}
}
return depth;
};
var rootTaskListDepth = exports.rootTaskListDepth = function rootTaskListDepth(taskListPos) {
var depth;
for (var i = taskListPos.depth; i > 0; i--) {
var node = taskListPos.node(i);
if (node.type.name === 'taskList' || node.type.name === 'taskItem') {
depth = i;
} else {
break;
}
}
return depth;
};