UNPKG

@portabletext/editor

Version:

Portable Text Editor made in React

567 lines (566 loc) 17.3 kB
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