@atlaskit/editor-plugin-selection
Version:
Selection plugin for @atlaskit/editor-core
121 lines (120 loc) • 5.49 kB
JavaScript
import { getFragmentsFromSelection, getLocalIdsFromSelection } from '@atlaskit/editor-common/selection';
import { nodeToJSON } from '@atlaskit/editor-json-transformer';
import { Fragment } from '@atlaskit/editor-prosemirror/model';
import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
import { fg } from '@atlaskit/platform-feature-flags';
const listDepth = 3;
const selectionCoversAllListItems = ($from, $to) => {
// Block level lists
const listParents = ['bulletList', 'orderedList'];
if ($from.depth >= listDepth && $to.depth >= listDepth && $from.depth === $to.depth) {
var _greatGrandparentFrom, _greatGrandparentFrom2;
// Get grandparents (from)
const grandparentFrom = $from.node($from.depth - 1);
const greatGrandparentFrom = $from.node($from.depth - 2);
// Get grandparents (to)
const grandparentTo = $to.node($from.depth - 1);
const greatGrandparentTo = $to.node($from.depth - 2);
if (greatGrandparentTo.eq(greatGrandparentFrom) && listParents.includes(greatGrandparentFrom.type.name) && // Selection covers entire list
(_greatGrandparentFrom = greatGrandparentFrom.firstChild) !== null && _greatGrandparentFrom !== void 0 && _greatGrandparentFrom.eq(grandparentFrom) && (_greatGrandparentFrom2 = greatGrandparentFrom.lastChild) !== null && _greatGrandparentFrom2 !== void 0 && _greatGrandparentFrom2.eq(grandparentTo)) {
return true;
}
}
return false;
};
/**
* Get the slice of the document corresponding to the selection.
* This is similar to the prosemirror `selection.content()` - but
* does not include the parents (unless the result is inline)
*
* @param selection The selection to get the slice for.
* @returns The slice of the document corresponding to the selection.
*/
export const getSliceFromSelection = selection => {
const {
from,
to
} = selection;
if (from === to) {
return Fragment.empty;
}
let frag = Fragment.empty;
const sortedRanges = [...selection.ranges.slice()].sort((a, b) => a.$from.pos - b.$from.pos);
for (const range of sortedRanges) {
const {
$from,
$to
} = range;
const to = $to.pos;
const depth =
// If we're in a text selection, and share the parent node across the anchor->head
// make the depth the parent node
selection instanceof TextSelection && $from.parent.eq($to.parent) ? Math.max(0, $from.sharedDepth(to) - 1) : $from.sharedDepth(to);
let finalDepth = depth;
// For block-level lists (non-nested) specifically use the selection
if (selectionCoversAllListItems($from, $to)) {
finalDepth = $from.depth - listDepth;
}
const start = $from.start(finalDepth);
const node = $from.node(finalDepth);
const content = node.content.cut($from.pos - start, $to.pos - start);
frag = frag.append(content);
}
return frag;
};
export const getSelectionFragment = api => () => {
var _api$selection$shared, _api$selection$shared2;
const selection = api === null || api === void 0 ? void 0 : (_api$selection$shared = api.selection.sharedState) === null || _api$selection$shared === void 0 ? void 0 : (_api$selection$shared2 = _api$selection$shared.currentState()) === null || _api$selection$shared2 === void 0 ? void 0 : _api$selection$shared2.selection;
if (fg('platform_editor_renderer_selection_context')) {
return getFragmentsFromSelection(selection);
} else {
var _api$core$sharedState;
const schema = api === null || api === void 0 ? void 0 : (_api$core$sharedState = api.core.sharedState.currentState()) === null || _api$core$sharedState === void 0 ? void 0 : _api$core$sharedState.schema;
if (!selection || !schema || selection.empty) {
return null;
}
const slice = getSliceFromSelection(selection);
const content = slice.content;
const fragment = [];
content.forEach(node => {
fragment.push(nodeToJSON(node));
});
return fragment;
}
};
export const getSelectionLocalIds = api => () => {
var _api$selection$shared3, _api$selection$shared4, _selection;
let selection = api === null || api === void 0 ? void 0 : (_api$selection$shared3 = api.selection.sharedState) === null || _api$selection$shared3 === void 0 ? void 0 : (_api$selection$shared4 = _api$selection$shared3.currentState()) === null || _api$selection$shared4 === void 0 ? void 0 : _api$selection$shared4.selection;
if ((_selection = selection) !== null && _selection !== void 0 && _selection.empty) {
// If we have an empty selection the current state might not be correct
// We have a hack here to retrieve the current selection - but not dispatch a transaction
api === null || api === void 0 ? void 0 : api.core.actions.execute(({
tr
}) => {
selection = tr.selection;
return null;
});
}
if (fg('platform_editor_renderer_selection_context')) {
return getLocalIdsFromSelection(selection);
} else {
if (!selection) {
return null;
}
if (selection instanceof NodeSelection) {
return [selection.node.attrs.localId];
} else if (selection.empty) {
return [selection.$from.parent.attrs.localId];
}
const content = getSliceFromSelection(selection).content;
const ids = [];
content.forEach(node => {
var _node$attrs;
const localId = (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.localId;
if (localId) {
ids.push(localId);
}
});
return ids;
}
};