@portabletext/editor
Version:
Portable Text Editor made in React
456 lines (455 loc) • 18.4 kB
JavaScript
import { isSpan, sliceBlocks, spanSelectionPointToBlockOffset, getBlockStartPoint, getBlockEndPoint, blockOffsetToSpanSelectionPoint, isEqualSelectionPoints, reverseSelection } from "./util.slice-blocks.js";
import { isPortableTextTextBlock, isPortableTextListBlock, isPortableTextSpan, isKeySegment } from "@sanity/types";
const isSelectionCollapsed = (snapshot) => snapshot.context.selection ? JSON.stringify(snapshot.context.selection.anchor.path) === JSON.stringify(snapshot.context.selection.focus.path) && snapshot.context.selection?.anchor.offset === snapshot.context.selection?.focus.offset : !1;
function createGuards({
schema
}) {
function isListBlock(block) {
return isPortableTextListBlock(block) && block._type === schema.block.name;
}
function isTextBlock(block) {
return isPortableTextTextBlock(block) && block._type === schema.block.name;
}
return {
isListBlock,
isTextBlock
};
}
const getFocusBlock = (snapshot) => {
const key = snapshot.context.selection && isKeySegment(snapshot.context.selection.focus.path[0]) ? snapshot.context.selection.focus.path[0]._key : void 0, node = key ? snapshot.context.value.find((block) => block._key === key) : void 0;
return node && key ? {
node,
path: [{
_key: key
}]
} : void 0;
}, getFocusListBlock = (snapshot) => {
const guards = createGuards(snapshot.context), focusBlock = getFocusBlock(snapshot);
return focusBlock && guards.isListBlock(focusBlock.node) ? {
node: focusBlock.node,
path: focusBlock.path
} : void 0;
}, getFocusTextBlock = (snapshot) => {
const focusBlock = getFocusBlock(snapshot);
return focusBlock && isPortableTextTextBlock(focusBlock.node) ? {
node: focusBlock.node,
path: focusBlock.path
} : void 0;
}, getFocusBlockObject = (snapshot) => {
const focusBlock = getFocusBlock(snapshot);
return focusBlock && !isPortableTextTextBlock(focusBlock.node) ? {
node: focusBlock.node,
path: focusBlock.path
} : void 0;
}, getFocusChild = (snapshot) => {
const focusBlock = getFocusTextBlock(snapshot);
if (!focusBlock)
return;
const key = snapshot.context.selection && isKeySegment(snapshot.context.selection.focus.path[2]) ? snapshot.context.selection.focus.path[2]._key : void 0, node = key ? focusBlock.node.children.find((span) => span._key === key) : void 0;
return node && key ? {
node,
path: [...focusBlock.path, "children", {
_key: key
}]
} : void 0;
}, getFocusSpan = (snapshot) => {
const focusChild = getFocusChild(snapshot);
return focusChild && isPortableTextSpan(focusChild.node) ? {
node: focusChild.node,
path: focusChild.path
} : void 0;
}, getFirstBlock = (snapshot) => {
const node = snapshot.context.value[0];
return node ? {
node,
path: [{
_key: node._key
}]
} : void 0;
}, getLastBlock = (snapshot) => {
const node = snapshot.context.value[snapshot.context.value.length - 1] ? snapshot.context.value[snapshot.context.value.length - 1] : void 0;
return node ? {
node,
path: [{
_key: node._key
}]
} : void 0;
}, getSelectedBlocks = (snapshot) => {
if (!snapshot.context.selection)
return [];
const selectedBlocks = [], startKey = snapshot.context.selection.backward ? isKeySegment(snapshot.context.selection.focus.path[0]) ? snapshot.context.selection.focus.path[0]._key : void 0 : isKeySegment(snapshot.context.selection.anchor.path[0]) ? snapshot.context.selection.anchor.path[0]._key : void 0, endKey = snapshot.context.selection.backward ? isKeySegment(snapshot.context.selection.anchor.path[0]) ? snapshot.context.selection.anchor.path[0]._key : void 0 : isKeySegment(snapshot.context.selection.focus.path[0]) ? snapshot.context.selection.focus.path[0]._key : void 0;
if (!startKey || !endKey)
return selectedBlocks;
for (const block of snapshot.context.value) {
if (block._key === startKey) {
if (selectedBlocks.push({
node: block,
path: [{
_key: block._key
}]
}), startKey === endKey)
break;
continue;
}
if (block._key === endKey) {
selectedBlocks.push({
node: block,
path: [{
_key: block._key
}]
});
break;
}
selectedBlocks.length > 0 && selectedBlocks.push({
node: block,
path: [{
_key: block._key
}]
});
}
return selectedBlocks;
}, getSelectionStartBlock = (snapshot) => {
if (!snapshot.context.selection)
return;
const key = snapshot.context.selection.backward ? isKeySegment(snapshot.context.selection.focus.path[0]) ? snapshot.context.selection.focus.path[0]._key : void 0 : isKeySegment(snapshot.context.selection.anchor.path[0]) ? snapshot.context.selection.anchor.path[0]._key : void 0, node = key ? snapshot.context.value.find((block) => block._key === key) : void 0;
return node && key ? {
node,
path: [{
_key: key
}]
} : void 0;
}, getSelectionEndBlock = (snapshot) => {
if (!snapshot.context.selection)
return;
const key = snapshot.context.selection.backward ? isKeySegment(snapshot.context.selection.anchor.path[0]) ? snapshot.context.selection.anchor.path[0]._key : void 0 : isKeySegment(snapshot.context.selection.focus.path[0]) ? snapshot.context.selection.focus.path[0]._key : void 0, node = key ? snapshot.context.value.find((block) => block._key === key) : void 0;
return node && key ? {
node,
path: [{
_key: key
}]
} : void 0;
}, getPreviousBlock = (snapshot) => {
let previousBlock;
const selectionStartBlock = getSelectionStartBlock(snapshot);
if (!selectionStartBlock)
return;
let foundSelectionStartBlock = !1;
for (const block of snapshot.context.value) {
if (block._key === selectionStartBlock.node._key) {
foundSelectionStartBlock = !0;
break;
}
previousBlock = {
node: block,
path: [{
_key: block._key
}]
};
}
if (foundSelectionStartBlock && previousBlock)
return previousBlock;
}, getNextBlock = (snapshot) => {
let nextBlock;
const selectionEndBlock = getSelectionEndBlock(snapshot);
if (!selectionEndBlock)
return;
let foundSelectionEndBlock = !1;
for (const block of snapshot.context.value) {
if (block._key === selectionEndBlock.node._key) {
foundSelectionEndBlock = !0;
continue;
}
if (foundSelectionEndBlock) {
nextBlock = {
node: block,
path: [{
_key: block._key
}]
};
break;
}
}
if (foundSelectionEndBlock && nextBlock)
return nextBlock;
}, getSelectionEndPoint = (snapshot) => {
if (snapshot.context.selection)
return snapshot.context.selection.backward ? snapshot.context.selection.anchor : snapshot.context.selection.focus;
}, getSelectionStartPoint = (snapshot) => {
if (snapshot.context.selection)
return snapshot.context.selection.backward ? snapshot.context.selection.focus : snapshot.context.selection.anchor;
}, getNextInlineObject = (snapshot) => {
const focusTextBlock = getFocusTextBlock(snapshot), selectionEndPoint = getSelectionEndPoint(snapshot), selectionEndPointChildKey = selectionEndPoint && isKeySegment(selectionEndPoint.path[2]) ? selectionEndPoint.path[2]._key : void 0;
if (!focusTextBlock || !selectionEndPointChildKey)
return;
let endPointChildFound = !1, inlineObject;
for (const child of focusTextBlock.node.children) {
if (child._key === selectionEndPointChildKey) {
endPointChildFound = !0;
continue;
}
if (!isSpan(snapshot.context, child) && endPointChildFound) {
inlineObject = {
node: child,
path: [...focusTextBlock.path, "children", {
_key: child._key
}]
};
break;
}
}
return inlineObject;
}, getPreviousInlineObject = (snapshot) => {
const focusTextBlock = getFocusTextBlock(snapshot), selectionStartPoint = getSelectionStartPoint(snapshot), selectionStartPointChildKey = selectionStartPoint && isKeySegment(selectionStartPoint.path[2]) ? selectionStartPoint.path[2]._key : void 0;
if (!focusTextBlock || !selectionStartPointChildKey)
return;
let inlineObject;
for (const child of focusTextBlock.node.children) {
if (child._key === selectionStartPointChildKey)
break;
isSpan(snapshot.context, child) || (inlineObject = {
node: child,
path: [...focusTextBlock.path, "children", {
_key: child._key
}]
});
}
return inlineObject;
}, getSelectedSlice = (snapshot) => sliceBlocks({
blocks: snapshot.context.value,
selection: snapshot.context.selection
}), getSelectionText = (snapshot) => getSelectedSlice(snapshot).reduce((text, block) => isPortableTextTextBlock(block) ? text + block.children.reduce((text2, child) => isPortableTextSpan(child) ? text2 + child.text : text2, "") : text, ""), isSelectionExpanded = (snapshot) => !isSelectionCollapsed(snapshot), getCaretWordSelection = (snapshot) => {
if (!snapshot.context.selection || !isSelectionCollapsed(snapshot))
return null;
const focusTextBlock = getFocusTextBlock(snapshot), selectionStartPoint = getSelectionStartPoint(snapshot), selectionStartOffset = selectionStartPoint ? spanSelectionPointToBlockOffset({
value: snapshot.context.value,
selectionPoint: selectionStartPoint
}) : void 0;
if (!focusTextBlock || !selectionStartPoint || !selectionStartOffset)
return null;
const previousInlineObject = getPreviousInlineObject(snapshot), blockStartPoint = getBlockStartPoint(focusTextBlock), textDirectlyBefore = getSelectionText({
context: {
...snapshot.context,
selection: {
anchor: previousInlineObject ? {
path: previousInlineObject.path,
offset: 0
} : blockStartPoint,
focus: selectionStartPoint
}
}
}).split(/\s+/).at(-1), nextInlineObject = getNextInlineObject(snapshot), blockEndPoint = getBlockEndPoint(focusTextBlock), textDirectlyAfter = getSelectionText({
context: {
...snapshot.context,
selection: {
anchor: selectionStartPoint,
focus: nextInlineObject ? {
path: nextInlineObject.path,
offset: 0
} : blockEndPoint
}
}
}).split(/\s+/).at(0);
if ((textDirectlyBefore === void 0 || textDirectlyBefore === "") && (textDirectlyAfter === void 0 || textDirectlyAfter === ""))
return null;
const caretWordStartOffset = textDirectlyBefore ? {
...selectionStartOffset,
offset: selectionStartOffset.offset - textDirectlyBefore.length
} : selectionStartOffset, caretWordEndOffset = textDirectlyAfter ? {
...selectionStartOffset,
offset: selectionStartOffset.offset + textDirectlyAfter.length
} : selectionStartOffset, caretWordStartSelectionPoint = blockOffsetToSpanSelectionPoint({
value: snapshot.context.value,
blockOffset: caretWordStartOffset,
direction: "backward"
}), caretWordEndSelectionPoint = blockOffsetToSpanSelectionPoint({
value: snapshot.context.value,
blockOffset: caretWordEndOffset,
direction: "forward"
});
if (!caretWordStartSelectionPoint || !caretWordEndSelectionPoint)
return null;
const caretWordSelection = {
anchor: caretWordStartSelectionPoint,
focus: caretWordEndSelectionPoint
};
return isSelectionExpanded({
context: {
...snapshot.context,
selection: caretWordSelection
}
}) ? caretWordSelection : null;
};
function isAtTheEndOfBlock(block) {
return (snapshot) => {
if (!snapshot.context.selection || !isSelectionCollapsed(snapshot))
return !1;
const blockEndPoint = getBlockEndPoint(block);
return isEqualSelectionPoints(snapshot.context.selection.focus, blockEndPoint);
};
}
function isAtTheStartOfBlock(block) {
return (snapshot) => {
if (!snapshot.context.selection || !isSelectionCollapsed(snapshot))
return !1;
const blockStartPoint = getBlockStartPoint(block);
return isEqualSelectionPoints(snapshot.context.selection.focus, blockStartPoint);
};
}
function isPointAfterSelection(point) {
return (snapshot) => {
if (!snapshot.context.selection)
return !1;
const selection = snapshot.context.selection.backward ? reverseSelection(snapshot.context.selection) : snapshot.context.selection, pointBlockKey = isKeySegment(point.path[0]) ? point.path[0]._key : void 0, pointChildKey = isKeySegment(point.path[2]) ? point.path[2]._key : void 0, endBlockKey = isKeySegment(selection.focus.path[0]) ? selection.focus.path[0]._key : void 0, endChildKey = isKeySegment(selection.focus.path[2]) ? selection.focus.path[2]._key : void 0;
if (!pointBlockKey || !endBlockKey)
return !1;
let after = !1;
for (const block of snapshot.context.value) {
if (block._key === endBlockKey) {
if (block._key !== pointBlockKey) {
after = !0;
break;
}
if (!isPortableTextTextBlock(block) || !pointChildKey || !endChildKey)
break;
for (const child of block.children) {
if (child._key === endChildKey) {
if (child._key !== pointChildKey) {
after = !0;
break;
}
after = point.offset > selection.focus.offset;
break;
}
if (child._key === pointChildKey)
break;
}
}
if (block._key === pointBlockKey)
break;
}
return after;
};
}
function isPointBeforeSelection(point) {
return (snapshot) => {
if (!snapshot.context.selection)
return !1;
const selection = snapshot.context.selection.backward ? reverseSelection(snapshot.context.selection) : snapshot.context.selection, pointBlockKey = isKeySegment(point.path[0]) ? point.path[0]._key : void 0, pointChildKey = isKeySegment(point.path[2]) ? point.path[2]._key : void 0, startBlockKey = isKeySegment(selection.anchor.path[0]) ? selection.anchor.path[0]._key : void 0, startChildKey = isKeySegment(selection.anchor.path[2]) ? selection.anchor.path[2]._key : void 0;
if (!pointBlockKey || !startBlockKey)
return !1;
let before = !1;
for (const block of snapshot.context.value) {
if (block._key === pointBlockKey) {
if (block._key !== startBlockKey) {
before = !0;
break;
}
if (!isPortableTextTextBlock(block) || !pointChildKey || !startChildKey)
break;
for (const child of block.children) {
if (child._key === pointChildKey) {
if (child._key !== startChildKey) {
before = !0;
break;
}
before = point.offset < selection.anchor.offset;
break;
}
if (child._key === startChildKey)
break;
}
}
if (block._key === startBlockKey)
break;
}
return before;
};
}
function isOverlappingSelection(selection) {
return (snapshot) => {
if (!selection || !snapshot.context.selection)
return !1;
const selectionStartPoint = getSelectionStartPoint({
context: {
...snapshot.context,
selection
}
}), selectionEndPoint = getSelectionEndPoint({
context: {
...snapshot.context,
selection
}
}), originalSelectionStartPoint = getSelectionStartPoint(snapshot), originalSelectionEndPoint = getSelectionEndPoint(snapshot);
if (!selectionStartPoint || !selectionEndPoint || !originalSelectionStartPoint || !originalSelectionEndPoint)
return !1;
const startPointBeforeSelection = isPointBeforeSelection(selectionStartPoint)(snapshot), startPointAfterSelection = isPointAfterSelection(selectionStartPoint)(snapshot), endPointBeforeSelection = isPointBeforeSelection(selectionEndPoint)(snapshot), endPointAfterSelection = isPointAfterSelection(selectionEndPoint)(snapshot), originalStartPointBeforeStartPoint = isPointBeforeSelection(originalSelectionStartPoint)({
...snapshot,
context: {
...snapshot.context,
selection: {
anchor: selectionStartPoint,
focus: selectionStartPoint
}
}
}), originalStartPointAfterStartPoint = isPointAfterSelection(originalSelectionStartPoint)({
...snapshot,
context: {
...snapshot.context,
selection: {
anchor: selectionStartPoint,
focus: selectionStartPoint
}
}
}), originalEndPointBeforeEndPoint = isPointBeforeSelection(originalSelectionEndPoint)({
...snapshot,
context: {
...snapshot.context,
selection: {
anchor: selectionEndPoint,
focus: selectionEndPoint
}
}
}), originalEndPointAfterEndPoint = isPointAfterSelection(originalSelectionEndPoint)({
...snapshot,
context: {
...snapshot.context,
selection: {
anchor: selectionEndPoint,
focus: selectionEndPoint
}
}
}), endPointEqualToOriginalStartPoint = isEqualSelectionPoints(selectionEndPoint, originalSelectionStartPoint), startPointEqualToOriginalEndPoint = isEqualSelectionPoints(selectionStartPoint, originalSelectionEndPoint);
return endPointBeforeSelection && !endPointEqualToOriginalStartPoint || startPointAfterSelection && !startPointEqualToOriginalEndPoint ? !1 : !originalStartPointBeforeStartPoint && originalStartPointAfterStartPoint && !originalEndPointBeforeEndPoint && originalEndPointAfterEndPoint ? !endPointEqualToOriginalStartPoint : originalStartPointBeforeStartPoint && !originalStartPointAfterStartPoint && originalEndPointBeforeEndPoint && !originalEndPointAfterEndPoint ? !startPointEqualToOriginalEndPoint : !startPointAfterSelection || !startPointBeforeSelection || !endPointAfterSelection || !endPointBeforeSelection;
};
}
export {
createGuards,
getCaretWordSelection,
getFirstBlock,
getFocusBlock,
getFocusBlockObject,
getFocusChild,
getFocusListBlock,
getFocusSpan,
getFocusTextBlock,
getLastBlock,
getNextBlock,
getNextInlineObject,
getPreviousBlock,
getPreviousInlineObject,
getSelectedBlocks,
getSelectedSlice,
getSelectionEndBlock,
getSelectionEndPoint,
getSelectionStartBlock,
getSelectionStartPoint,
getSelectionText,
isAtTheEndOfBlock,
isAtTheStartOfBlock,
isOverlappingSelection,
isPointAfterSelection,
isPointBeforeSelection,
isSelectionCollapsed,
isSelectionExpanded
};
//# sourceMappingURL=selector.is-overlapping-selection.js.map