UNPKG

@portabletext/editor

Version:

Portable Text Editor made in React

295 lines (294 loc) 13 kB
import { isKeySegment, isPortableTextTextBlock, isPortableTextSpan } from "@sanity/types"; import { getSelectedBlocks, getFocusSpan, isSelectionCollapsed, createGuards, getSelectionStartPoint, getSelectionEndPoint, getFocusTextBlock, isSelectionExpanded, getSelectionStartBlock, getSelectionEndBlock } from "./selector.is-overlapping-selection.js"; import { isTextBlock } from "./parse-blocks.js"; import { isKeyedSegment, isEmptyTextBlock, getBlockStartPoint, getBlockEndPoint, isEqualSelectionPoints } from "./util.slice-blocks.js"; const getSelectedSpans = (snapshot) => { if (!snapshot.context.selection) return []; const selectedSpans = [], startPoint = snapshot.context.selection.backward ? snapshot.context.selection.focus : snapshot.context.selection.anchor, endPoint = snapshot.context.selection.backward ? snapshot.context.selection.anchor : snapshot.context.selection.focus, startBlockKey = isKeySegment(startPoint.path[0]) ? startPoint.path[0]._key : void 0, endBlockKey = isKeySegment(endPoint.path[0]) ? endPoint.path[0]._key : void 0; if (!startBlockKey || !endBlockKey) return selectedSpans; const startSpanKey = isKeySegment(startPoint.path[2]) ? startPoint.path[2]._key : void 0, endSpanKey = isKeySegment(endPoint.path[2]) ? endPoint.path[2]._key : void 0; let startBlockFound = !1; for (const block of snapshot.context.value) if (block._key === startBlockKey && (startBlockFound = !0), !!isPortableTextTextBlock(block)) { if (block._key === startBlockKey) { for (const child of block.children) if (isPortableTextSpan(child)) { if (startSpanKey && child._key === startSpanKey) { if (startPoint.offset < child.text.length && selectedSpans.push({ node: child, path: [{ _key: block._key }, "children", { _key: child._key }] }), startSpanKey === endSpanKey) break; continue; } if (endSpanKey && child._key === endSpanKey) { endPoint.offset > 0 && selectedSpans.push({ node: child, path: [{ _key: block._key }, "children", { _key: child._key }] }); break; } selectedSpans.length > 0 && selectedSpans.push({ node: child, path: [{ _key: block._key }, "children", { _key: child._key }] }); } if (startBlockKey === endBlockKey) break; continue; } if (block._key === endBlockKey) { for (const child of block.children) if (isPortableTextSpan(child)) { if (endSpanKey && child._key === endSpanKey) { endPoint.offset > 0 && selectedSpans.push({ node: child, path: [{ _key: block._key }, "children", { _key: child._key }] }); break; } selectedSpans.push({ node: child, path: [{ _key: block._key }, "children", { _key: child._key }] }); } break; } if (startBlockFound) for (const child of block.children) isPortableTextSpan(child) && selectedSpans.push({ node: child, path: [{ _key: block._key }, "children", { _key: child._key }] }); } return selectedSpans; }, getActiveAnnotations = (snapshot) => { if (!snapshot.context.selection) return []; const selectedBlocks = getSelectedBlocks(snapshot), selectedSpans = getSelectedSpans(snapshot), focusSpan = getFocusSpan(snapshot); if (selectedSpans.length === 0 || !focusSpan) return []; if (selectedSpans.length === 1 && isSelectionCollapsed(snapshot)) { if (snapshot.context.selection.focus.offset === 0) return []; if (snapshot.context.selection.focus.offset === focusSpan.node.text.length) return []; } return selectedBlocks.flatMap((block) => isPortableTextTextBlock(block.node) ? block.node.markDefs ?? [] : []).filter((markDef) => selectedSpans.some((span) => span.node.marks?.includes(markDef._key))); }, getActiveListItem = (snapshot) => { if (!snapshot.context.selection) return; const guards = createGuards(snapshot.context), selectedTextBlocks = getSelectedBlocks(snapshot).map((block) => block.node).filter(guards.isTextBlock), firstTextBlock = selectedTextBlocks.at(0); if (!firstTextBlock) return; const firstListItem = firstTextBlock.listItem; if (firstListItem && selectedTextBlocks.every((block) => block.listItem === firstListItem)) return firstListItem; }, getActiveStyle = (snapshot) => { if (!snapshot.context.selection) return; const guards = createGuards(snapshot.context), selectedTextBlocks = getSelectedBlocks(snapshot).map((block) => block.node).filter(guards.isTextBlock), firstTextBlock = selectedTextBlocks.at(0); if (!firstTextBlock) return; const firstStyle = firstTextBlock.style; if (firstStyle && selectedTextBlocks.every((block) => block.style === firstStyle)) return firstStyle; }, getSelectedTextBlocks = (snapshot) => { if (!snapshot.context.selection) return []; const selectedTextBlocks = [], startKey = snapshot.context.selection.backward ? isKeyedSegment(snapshot.context.selection.focus.path[0]) ? snapshot.context.selection.focus.path[0]._key : void 0 : isKeyedSegment(snapshot.context.selection.anchor.path[0]) ? snapshot.context.selection.anchor.path[0]._key : void 0, endKey = snapshot.context.selection.backward ? isKeyedSegment(snapshot.context.selection.anchor.path[0]) ? snapshot.context.selection.anchor.path[0]._key : void 0 : isKeyedSegment(snapshot.context.selection.focus.path[0]) ? snapshot.context.selection.focus.path[0]._key : void 0; if (!startKey || !endKey) return selectedTextBlocks; for (const block of snapshot.context.value) { if (block._key === startKey) { if (isTextBlock(snapshot.context.schema, block) && selectedTextBlocks.push({ node: block, path: [{ _key: block._key }] }), startKey === endKey) break; continue; } if (block._key === endKey) { isTextBlock(snapshot.context.schema, block) && selectedTextBlocks.push({ node: block, path: [{ _key: block._key }] }); break; } selectedTextBlocks.length > 0 && isTextBlock(snapshot.context.schema, block) && selectedTextBlocks.push({ node: block, path: [{ _key: block._key }] }); } return selectedTextBlocks; }, getTrimmedSelection = (snapshot) => { if (!snapshot.context.selection) return snapshot.context.selection; const startPoint = getSelectionStartPoint(snapshot), endPoint = getSelectionEndPoint(snapshot); if (!startPoint || !endPoint) return snapshot.context.selection; const startBlockKey = isKeyedSegment(startPoint.path[0]) ? startPoint.path[0]._key : null, startChildKey = isKeyedSegment(startPoint.path[2]) ? startPoint.path[2]._key : null, endBlockKey = isKeyedSegment(endPoint.path[0]) ? endPoint.path[0]._key : null, endChildKey = isKeyedSegment(endPoint.path[2]) ? endPoint.path[2]._key : null; if (!startBlockKey || !endBlockKey) return snapshot.context.selection; let startBlockFound = !1, adjustedStartPoint, trimStartPoint = !1, adjustedEndPoint, trimEndPoint = !1, previousPotentialEndpoint; for (const block of snapshot.context.value) if (!(block._key === startBlockKey && (startBlockFound = !0, isPortableTextTextBlock(block) && isEmptyTextBlock(block))) && startBlockFound && isPortableTextTextBlock(block)) { if (block._key === endBlockKey && isEmptyTextBlock(block)) break; for (const child of block.children) { if (child._key === endChildKey && (!isPortableTextSpan(child) || endPoint.offset === 0)) { adjustedEndPoint = previousPotentialEndpoint ? { path: [{ _key: previousPotentialEndpoint.blockKey }, "children", { _key: previousPotentialEndpoint.span._key }], offset: previousPotentialEndpoint.span.text.length } : void 0, trimEndPoint = !0; break; } if (trimStartPoint) { const lonelySpan = isPortableTextSpan(child) && block.children.length === 1; (isPortableTextSpan(child) && child.text.length > 0 || lonelySpan) && (adjustedStartPoint = { path: [{ _key: block._key }, "children", { _key: child._key }], offset: 0 }, previousPotentialEndpoint = { blockKey: block._key, span: child }, trimStartPoint = !1); continue; } if (child._key === startChildKey) { if (!isPortableTextSpan(child)) { trimStartPoint = !0; continue; } if (startPoint.offset === child.text.length) { trimStartPoint = !0, previousPotentialEndpoint = child.text.length > 0 ? { blockKey: block._key, span: child } : previousPotentialEndpoint; continue; } } previousPotentialEndpoint = isPortableTextSpan(child) && child.text.length > 0 ? { blockKey: block._key, span: child } : previousPotentialEndpoint; } if (block._key === endBlockKey) break; } const trimmedSelection = snapshot.context.selection.backward ? { anchor: trimEndPoint && adjustedEndPoint ? adjustedEndPoint : endPoint, focus: adjustedStartPoint ?? startPoint, backward: !0 } : { anchor: adjustedStartPoint ?? startPoint, focus: trimEndPoint && adjustedEndPoint ? adjustedEndPoint : endPoint }; if (isSelectionCollapsed({ context: { ...snapshot.context, selection: trimmedSelection } })) { const focusTextBlock = getFocusTextBlock({ context: { ...snapshot.context, selection: trimmedSelection } }); if (focusTextBlock && !isEmptyTextBlock(focusTextBlock.node)) return null; } return trimmedSelection; }; function isActiveAnnotation(annotation) { return (snapshot) => { if (!snapshot.context.selection) return !1; const selectedBlocks = getSelectedBlocks(snapshot), focusSpan = getFocusSpan(snapshot), selectedSpans = isSelectionExpanded(snapshot) ? getSelectedSpans(snapshot) : focusSpan ? [focusSpan] : []; if (selectedSpans.length === 0 || selectedSpans.some((span) => !span.node.marks || span.node.marks?.length === 0)) return !1; const selectionMarkDefs = selectedBlocks.flatMap((block) => isPortableTextTextBlock(block.node) ? block.node.markDefs ?? [] : []); return selectedSpans.every((span) => (span.node.marks?.flatMap((mark) => { const markDef = selectionMarkDefs.find((markDef2) => markDef2._key === mark); return markDef ? [markDef._type] : []; }) ?? []).includes(annotation)); }; } function isActiveDecorator(decorator) { return (snapshot) => { if (isSelectionExpanded(snapshot)) { const selectedSpans = getSelectedSpans(snapshot); return selectedSpans.length > 0 && selectedSpans.every((span) => span.node.marks?.includes(decorator)); } return snapshot.context.activeDecorators.includes(decorator); }; } function isActiveListItem(listItem) { return (snapshot) => getActiveListItem(snapshot) === listItem; } function isActiveStyle(style) { return (snapshot) => getActiveStyle(snapshot) === style; } const isSelectingEntireBlocks = (snapshot) => { if (!snapshot.context.selection) return !1; const startPoint = snapshot.context.selection.backward ? snapshot.context.selection.focus : snapshot.context.selection.anchor, endPoint = snapshot.context.selection.backward ? snapshot.context.selection.anchor : snapshot.context.selection.focus, startBlock = getSelectionStartBlock(snapshot), endBlock = getSelectionEndBlock(snapshot); if (!startBlock || !endBlock) return !1; const startBlockStartPoint = getBlockStartPoint(startBlock), endBlockEndPoint = getBlockEndPoint(endBlock); return isEqualSelectionPoints(startBlockStartPoint, startPoint) && isEqualSelectionPoints(endBlockEndPoint, endPoint); }; export { getActiveAnnotations, getActiveListItem, getActiveStyle, getSelectedSpans, getSelectedTextBlocks, getTrimmedSelection, isActiveAnnotation, isActiveDecorator, isActiveListItem, isActiveStyle, isSelectingEntireBlocks }; //# sourceMappingURL=selector.is-selecting-entire-blocks.js.map