@portabletext/editor
Version:
Portable Text Editor made in React
567 lines (566 loc) • 17.3 kB
JavaScript
import { isTextBlock, isSpan } from "@portabletext/schema";
function isTypedObject(object) {
return isRecord(object) && typeof object._type == "string";
}
function isRecord(value) {
return !!value && (typeof value == "object" || typeof value == "function");
}
function getBlockEndPoint({
context,
block
}) {
if (isTextBlock(context, block.node)) {
const lastChild = block.node.children[block.node.children.length - 1];
if (lastChild)
return {
path: [...block.path, "children", {
_key: lastChild._key
}],
offset: isSpan(context, lastChild) ? lastChild.text.length : 0
};
}
return {
path: block.path,
offset: 0
};
}
function getBlockStartPoint({
context,
block
}) {
if (isTextBlock(context, block.node)) {
const firstChild = block.node.children[0];
return {
path: [...block.path, "children", {
_key: firstChild?._key ?? ""
}],
offset: 0
};
}
return {
path: block.path,
offset: 0
};
}
function isKeyedSegment(segment) {
return typeof segment == "object" && segment !== null && "_key" in segment;
}
function isEqualPathSegments(segA, segB) {
return segA === segB ? !0 : segA === void 0 || segB === void 0 ? !1 : (typeof segA == "string" || typeof segA == "number") && (typeof segB == "string" || typeof segB == "number") ? segA === segB : isKeyedSegment(segA) && isKeyedSegment(segB) ? segA._key === segB._key : Array.isArray(segA) && Array.isArray(segB) ? segA[0] === segB[0] && segA[1] === segB[1] : !1;
}
function isEqualPaths(a, b) {
if (a.length !== b.length)
return !1;
for (let i = 0; i < a.length; i++)
if (!isEqualPathSegments(a[i], b[i]))
return !1;
return !0;
}
function isSelectionCollapsed(selection) {
return selection ? isEqualPaths(selection.anchor.path, selection.focus.path) && selection.anchor.offset === selection.focus.offset : !1;
}
function getBlockKeyFromSelectionPoint(point) {
const blockPathSegment = point.path.at(0);
if (isKeyedSegment(blockPathSegment))
return blockPathSegment._key;
}
function getChildKeyFromSelectionPoint(point) {
const childPathSegment = point.path.at(2);
if (isKeyedSegment(childPathSegment))
return childPathSegment._key;
}
function parseBlocks({
context,
blocks,
options
}) {
return Array.isArray(blocks) ? blocks.flatMap((block) => {
const parsedBlock = parseBlock({
context,
block,
options
});
return parsedBlock ? [parsedBlock] : [];
}) : [];
}
function parseBlock({
context,
block,
options
}) {
return parseTextBlock({
block,
context,
options
}) ?? parseBlockObject({
blockObject: block,
context,
options
});
}
function parseBlockObject({
blockObject,
context,
options
}) {
if (!isTypedObject(blockObject))
return;
const schemaType = context.schema.blockObjects.find(({
name
}) => name === blockObject._type);
if (schemaType)
return parseObject({
object: blockObject,
context: {
keyGenerator: context.keyGenerator,
schemaType
},
options
});
}
function isListBlock(context, block) {
return isTextBlock(context, block) && block.level !== void 0 && block.listItem !== void 0;
}
function parseTextBlock({
block,
context,
options
}) {
if (!isTypedObject(block))
return;
const customFields = {};
for (const key of Object.keys(block))
key === "_type" || key === "_key" || key === "children" || key === "markDefs" || key === "style" || key === "listItem" || key === "level" || (options.validateFields ? context.schema.block.fields?.some((field) => field.name === key) && (customFields[key] = block[key]) : customFields[key] = block[key]);
if (block._type !== context.schema.block.name)
return;
const _key = typeof block._key == "string" ? block._key : context.keyGenerator(), {
markDefs,
markDefKeyMap
} = parseMarkDefs({
context,
markDefs: block.markDefs,
options
}), parsedChildren = (Array.isArray(block.children) ? block.children : []).map((child) => parseChild({
child,
context,
markDefKeyMap,
options
})).filter((child) => child !== void 0), marks = parsedChildren.flatMap((child) => child.marks ?? []), children = parsedChildren.length > 0 ? parsedChildren : [{
_key: context.keyGenerator(),
_type: context.schema.span.name,
text: "",
marks: []
}], normalizedChildren = options.normalize ? (
// Ensure that inline objects re surrounded by spans
children.reduce((normalizedChildren2, child, index) => {
if (isSpan(context, child))
return [...normalizedChildren2, child];
const previousChild = normalizedChildren2.at(-1);
return !previousChild || !isSpan(context, previousChild) ? [...normalizedChildren2, {
_key: context.keyGenerator(),
_type: context.schema.span.name,
text: "",
marks: []
}, child, ...index === children.length - 1 ? [{
_key: context.keyGenerator(),
_type: context.schema.span.name,
text: "",
marks: []
}] : []] : [...normalizedChildren2, child];
}, [])
) : children, parsedBlock = {
_type: context.schema.block.name,
_key,
children: normalizedChildren,
...customFields
};
return typeof block.markDefs == "object" && block.markDefs !== null && (parsedBlock.markDefs = options.removeUnusedMarkDefs ? markDefs.filter((markDef) => marks.includes(markDef._key)) : markDefs), typeof block.style == "string" && context.schema.styles.find((style) => style.name === block.style) && (parsedBlock.style = block.style), typeof block.listItem == "string" && context.schema.lists.find((list) => list.name === block.listItem) && (parsedBlock.listItem = block.listItem), typeof block.level == "number" && (parsedBlock.level = block.level), parsedBlock;
}
function parseMarkDefs({
context,
markDefs,
options
}) {
const unparsedMarkDefs = Array.isArray(markDefs) ? markDefs : [], markDefKeyMap = /* @__PURE__ */ new Map();
return {
markDefs: unparsedMarkDefs.flatMap((markDef) => {
if (!isTypedObject(markDef))
return [];
const schemaType = context.schema.annotations.find(({
name
}) => name === markDef._type);
if (!schemaType)
return [];
if (typeof markDef._key != "string")
return [];
const parsedAnnotation = parseObject({
object: markDef,
context: {
schemaType,
keyGenerator: context.keyGenerator
},
options
});
return parsedAnnotation ? (markDefKeyMap.set(markDef._key, parsedAnnotation._key), [parsedAnnotation]) : [];
}),
markDefKeyMap
};
}
function parseChild({
child,
context,
markDefKeyMap,
options
}) {
return parseSpan({
span: child,
context,
markDefKeyMap,
options
}) ?? parseInlineObject({
inlineObject: child,
context,
options
});
}
function parseSpan({
span,
context,
markDefKeyMap,
options
}) {
if (!isRecord(span))
return;
const customFields = {};
for (const key of Object.keys(span))
key !== "_type" && key !== "_key" && key !== "text" && key !== "marks" && (customFields[key] = span[key]);
const marks = (Array.isArray(span.marks) ? span.marks : []).flatMap((mark) => {
if (typeof mark != "string")
return [];
const markDefKey = markDefKeyMap.get(mark);
return markDefKey !== void 0 ? [markDefKey] : context.schema.decorators.some((decorator) => decorator.name === mark) ? [mark] : [];
});
if (!(typeof span._type == "string" && span._type !== context.schema.span.name))
return typeof span._type != "string" ? typeof span.text == "string" ? {
_type: context.schema.span.name,
_key: typeof span._key == "string" ? span._key : context.keyGenerator(),
text: span.text,
marks,
...options.validateFields ? {} : customFields
} : void 0 : {
_type: context.schema.span.name,
_key: typeof span._key == "string" ? span._key : context.keyGenerator(),
text: typeof span.text == "string" ? span.text : "",
marks,
...options.validateFields ? {} : customFields
};
}
function parseInlineObject({
inlineObject,
context,
options
}) {
if (!isTypedObject(inlineObject))
return;
const schemaType = context.schema.inlineObjects.find(({
name
}) => name === inlineObject._type);
if (schemaType)
return parseObject({
object: inlineObject,
context: {
keyGenerator: context.keyGenerator,
schemaType
},
options
});
}
function parseAnnotation({
annotation,
context,
options
}) {
if (!isTypedObject(annotation))
return;
const schemaType = context.schema.annotations.find(({
name
}) => name === annotation._type);
if (schemaType)
return parseObject({
object: annotation,
context: {
keyGenerator: context.keyGenerator,
schemaType
},
options
});
}
function parseObject({
object,
context,
options
}) {
const {
_type,
_key,
...customFields
} = object, values = options.validateFields ? context.schemaType.fields.reduce((fieldValues, field) => {
const fieldValue = object[field.name];
return fieldValue !== void 0 && (fieldValues[field.name] = fieldValue), fieldValues;
}, {}) : customFields;
return {
_type: context.schemaType.name,
_key: typeof object._key == "string" ? object._key : context.keyGenerator(),
...values
};
}
function blockOffsetToSpanSelectionPoint({
context,
blockOffset,
direction
}) {
let offsetLeft = blockOffset.offset, selectionPoint, skippedInlineObject = !1;
for (const block of context.value)
if (block._key === blockOffset.path[0]._key && isTextBlock(context, block))
for (const child of block.children) {
if (direction === "forward") {
if (!isSpan(context, child))
continue;
if (offsetLeft <= child.text.length) {
selectionPoint = {
path: [...blockOffset.path, "children", {
_key: child._key
}],
offset: offsetLeft
};
break;
}
offsetLeft -= child.text.length;
continue;
}
if (!isSpan(context, child)) {
skippedInlineObject = !0;
continue;
}
if (offsetLeft === 0 && selectionPoint && !skippedInlineObject) {
skippedInlineObject && (selectionPoint = {
path: [...blockOffset.path, "children", {
_key: child._key
}],
offset: 0
});
break;
}
if (offsetLeft > child.text.length) {
offsetLeft -= child.text.length;
continue;
}
if (offsetLeft <= child.text.length && (selectionPoint = {
path: [...blockOffset.path, "children", {
_key: child._key
}],
offset: offsetLeft
}, offsetLeft -= child.text.length, offsetLeft !== 0))
break;
}
return selectionPoint;
}
function spanSelectionPointToBlockOffset({
context,
selectionPoint
}) {
let offset = 0;
const blockKey = getBlockKeyFromSelectionPoint(selectionPoint), spanKey = getChildKeyFromSelectionPoint(selectionPoint);
if (!(!blockKey || !spanKey)) {
for (const block of context.value)
if (block._key === blockKey && isTextBlock(context, block)) {
for (const child of block.children)
if (isSpan(context, child)) {
if (child._key === spanKey)
return {
path: [{
_key: block._key
}],
offset: offset + selectionPoint.offset
};
offset += child.text.length;
}
}
}
}
function getSelectionEndPoint(selection) {
return selection ? selection.backward ? selection.anchor : selection.focus : null;
}
function getSelectionStartPoint(selection) {
return selection ? selection.backward ? selection.focus : selection.anchor : null;
}
function isEqualSelectionPoints(a, b) {
return a.offset === b.offset && isEqualPaths(a.path, b.path);
}
const defaultKeyGenerator = () => randomKey(12), getByteHexTable = /* @__PURE__ */ (() => {
let table;
return () => {
if (table)
return table;
table = [];
for (let i = 0; i < 256; ++i)
table[i] = (i + 256).toString(16).slice(1);
return table;
};
})();
function whatwgRNG(length = 16) {
const rnds8 = new Uint8Array(length);
return crypto.getRandomValues(rnds8), rnds8;
}
function randomKey(length) {
const table = getByteHexTable();
return whatwgRNG(length).reduce((str, n) => str + table[n], "").slice(0, length);
}
function sliceBlocks({
context,
blocks
}) {
const slice = [];
if (!context.selection)
return slice;
let startBlock;
const middleBlocks = [];
let endBlock;
const startPoint = getSelectionStartPoint(context.selection), endPoint = getSelectionEndPoint(context.selection), startBlockKey = getBlockKeyFromSelectionPoint(startPoint), startChildKey = getChildKeyFromSelectionPoint(startPoint), endBlockKey = getBlockKeyFromSelectionPoint(endPoint), endChildKey = getChildKeyFromSelectionPoint(endPoint);
if (!startBlockKey || !endBlockKey)
return slice;
for (const block of blocks) {
if (!isTextBlock(context, block) && block._key === startBlockKey && block._key === endBlockKey) {
startBlock = block;
break;
}
if (block._key === startBlockKey) {
if (!isTextBlock(context, block)) {
startBlock = block;
continue;
}
if (startChildKey) {
for (const child of block.children) {
if (child._key === startChildKey) {
if (isSpan(context, child)) {
const text = child._key === endChildKey ? child.text.slice(startPoint.offset, endPoint.offset) : child.text.slice(startPoint.offset);
startBlock = {
...block,
children: [{
...child,
text
}]
};
} else
startBlock = {
...block,
children: [child]
};
if (block._key === endBlockKey && startChildKey === endChildKey)
break;
continue;
}
if (startBlock && isTextBlock(context, startBlock) && (endChildKey && child._key === endChildKey && isSpan(context, child) ? startBlock.children.push({
...child,
text: child.text.slice(0, endPoint.offset)
}) : startBlock.children.push(child), block._key === endBlockKey && endChildKey && child._key === endChildKey))
break;
}
if (startBlockKey === endBlockKey)
break;
continue;
}
if (startBlock = block, startBlockKey === endBlockKey)
break;
}
if (block._key === endBlockKey) {
if (!isTextBlock(context, block)) {
endBlock = block;
break;
}
if (endChildKey) {
endBlock = {
...block,
children: []
};
for (const child of block.children)
if (endBlock && isTextBlock(context, endBlock)) {
if (child._key === endChildKey && isSpan(context, child)) {
endBlock.children.push({
...child,
text: child.text.slice(0, endPoint.offset)
});
break;
}
if (endBlock.children.push(child), endChildKey && child._key === endChildKey)
break;
}
break;
}
endBlock = block;
break;
}
startBlock && middleBlocks.push(parseBlock({
context: {
schema: context.schema,
keyGenerator: context.keyGenerator ?? defaultKeyGenerator
},
block,
options: {
normalize: !1,
removeUnusedMarkDefs: !0,
validateFields: !1
}
}) ?? block);
}
const parsedStartBlock = startBlock ? parseBlock({
context: {
schema: context.schema,
keyGenerator: context.keyGenerator ?? defaultKeyGenerator
},
block: startBlock,
options: {
normalize: !1,
removeUnusedMarkDefs: !0,
validateFields: !1
}
}) : void 0, parsedEndBlock = endBlock ? parseBlock({
context: {
schema: context.schema,
keyGenerator: context.keyGenerator ?? defaultKeyGenerator
},
block: endBlock,
options: {
normalize: !1,
removeUnusedMarkDefs: !0,
validateFields: !1
}
}) : void 0;
return [...parsedStartBlock ? [parsedStartBlock] : [], ...middleBlocks, ...parsedEndBlock ? [parsedEndBlock] : []];
}
export {
blockOffsetToSpanSelectionPoint,
defaultKeyGenerator,
getBlockEndPoint,
getBlockKeyFromSelectionPoint,
getBlockStartPoint,
getChildKeyFromSelectionPoint,
getSelectionEndPoint,
getSelectionStartPoint,
isEqualPathSegments,
isEqualPaths,
isEqualSelectionPoints,
isKeyedSegment,
isListBlock,
isSelectionCollapsed,
isTypedObject,
parseAnnotation,
parseBlock,
parseBlocks,
parseInlineObject,
parseMarkDefs,
parseSpan,
sliceBlocks,
spanSelectionPointToBlockOffset
};
//# sourceMappingURL=util.slice-blocks.js.map