UNPKG

@portabletext/editor

Version:

Portable Text Editor made in React

716 lines (715 loc) 20.3 kB
import { isTextBlock, isSpan, getSubSchema } from "@portabletext/schema"; import { getAncestor, getNode, isKeyedSegment, isTypedObject, isRecord } from "./get-ancestor.js"; function getAncestorTextBlock(snapshot, path) { const result = getAncestor(snapshot, path, (node) => isTextBlock({ schema: snapshot.context.schema }, node)); if (result && isTextBlock({ schema: snapshot.context.schema }, result.node)) return { node: result.node, path: result.path }; } function blockOffsetToSpanSelectionPoint({ snapshot, blockOffset, direction }) { const blockEntry = getNode(snapshot, blockOffset.path); if (!blockEntry || !isTextBlock(snapshot.context, blockEntry.node)) return; const block = blockEntry.node, blockPath = blockEntry.path; let offsetLeft = blockOffset.offset, selectionPoint, skippedInlineObject = !1; for (const child of block.children) { if (direction === "forward") { if (!isSpan(snapshot.context, child)) continue; if (offsetLeft <= child.text.length) { selectionPoint = { path: [...blockPath, "children", { _key: child._key }], offset: offsetLeft }; break; } offsetLeft -= child.text.length; continue; } if (!isSpan(snapshot.context, child)) { skippedInlineObject = !0; continue; } if (offsetLeft === 0 && selectionPoint && !skippedInlineObject) { skippedInlineObject && (selectionPoint = { path: [...blockPath, "children", { _key: child._key }], offset: 0 }); break; } if (offsetLeft > child.text.length) { offsetLeft -= child.text.length; continue; } if (offsetLeft <= child.text.length && (selectionPoint = { path: [...blockPath, "children", { _key: child._key }], offset: offsetLeft }, offsetLeft -= child.text.length, offsetLeft !== 0)) break; } return selectionPoint; } function spanSelectionPointToBlockOffset({ snapshot, selectionPoint }) { const spanSegment = selectionPoint.path.at(-1); if (!isKeyedSegment(spanSegment)) return; const textBlock = getAncestorTextBlock(snapshot, selectionPoint.path); if (!textBlock) return; let offset = 0; for (const child of textBlock.node.children) if (isSpan(snapshot.context, child)) { if (child._key === spanSegment._key) return { path: textBlock.path, offset: offset + selectionPoint.offset }; offset += child.text.length; } } 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 isEqualSelectionPoints(a, b) { return a.offset === b.offset && isEqualPaths(a.path, b.path); } 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 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 isSelectionCollapsed(selection) { return selection ? isEqualPaths(selection.anchor.path, selection.focus.path) && selection.anchor.offset === selection.focus.offset : !1; } 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 parseBlocks({ schema, keyGenerator, blocks, options }) { return Array.isArray(blocks) ? blocks.flatMap((block) => { const parsedBlock = parseBlockInternal({ schema, keyGenerator, block, options }); return parsedBlock ? [parsedBlock] : []; }) : []; } function parseBlock({ schema, keyGenerator, block, options }) { return parseBlockInternal({ schema, keyGenerator, block, options }); } function parseSpan({ span, schema, keyGenerator, markDefKeyMap, options }) { return parseSpanInternal({ span, schema, keyGenerator, markDefKeyMap, options }); } function parseInlineObject({ inlineObject, schema, keyGenerator, options }) { return parseInlineObjectInternal({ inlineObject, schema, keyGenerator, options }); } function parseMarkDefs({ schema, keyGenerator, markDefs, options }) { return parseMarkDefsInternal({ schema, keyGenerator, markDefs, options }); } function parseBlockInternal({ schema, keyGenerator, block, options }) { return parseTextBlock({ block, schema, keyGenerator, options }) ?? parseBlockObject({ blockObject: block, schema, keyGenerator, options }); } function parseBlockObject({ blockObject, schema, keyGenerator, options }) { if (!isTypedObject(blockObject)) return; const schemaType = schema.blockObjects.find(({ name }) => name === blockObject._type); if (schemaType) return parseObject({ object: blockObject, schema, keyGenerator, schemaType, options }); } function isListBlock(context, block) { return isTextBlock(context, block) && block.level !== void 0 && block.listItem !== void 0; } function parseTextBlock({ block, schema, keyGenerator, options }) { if (!isTypedObject(block) || block._type !== schema.block.name) 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 ? (schema.block.fields ?? []).some((f) => f.name === key) && (customFields[key] = block[key]) : customFields[key] = block[key]); const _key = typeof block._key == "string" ? block._key : keyGenerator(), { markDefs, markDefKeyMap } = parseMarkDefsInternal({ schema, keyGenerator, markDefs: block.markDefs, options }), parsedChildren = (Array.isArray(block.children) ? block.children : []).map((child) => parseChildInternal({ child, schema, keyGenerator, markDefKeyMap, options })).filter((child) => child !== void 0), marks = parsedChildren.flatMap((child) => child.marks ?? []), children = parsedChildren.length > 0 ? parsedChildren : [{ _key: keyGenerator(), _type: schema.span.name, text: "", marks: [] }], normalizedChildren = options.normalize ? ( // Ensure that inline objects re surrounded by spans children.reduce((normalizedChildren2, child, index) => { if (isSpan({ schema }, child)) return [...normalizedChildren2, child]; const previousChild = normalizedChildren2.at(-1); return !previousChild || !isSpan({ schema }, previousChild) ? [...normalizedChildren2, { _key: keyGenerator(), _type: schema.span.name, text: "", marks: [] }, child, ...index === children.length - 1 ? [{ _key: keyGenerator(), _type: schema.span.name, text: "", marks: [] }] : []] : [...normalizedChildren2, child]; }, []) ) : children, parsedBlock = { _type: 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" && schema.styles.find((style) => style.name === block.style) && (parsedBlock.style = block.style), typeof block.listItem == "string" && schema.lists.find((list) => list.name === block.listItem) && (parsedBlock.listItem = block.listItem), typeof block.level == "number" && (parsedBlock.level = block.level), parsedBlock; } function parseMarkDefsInternal({ schema, keyGenerator, markDefs, options }) { const unparsedMarkDefs = Array.isArray(markDefs) ? markDefs : [], markDefKeyMap = /* @__PURE__ */ new Map(); return { markDefs: unparsedMarkDefs.flatMap((markDef) => { if (!isTypedObject(markDef)) return []; const schemaType = schema.annotations.find(({ name }) => name === markDef._type); if (!schemaType) return []; if (typeof markDef._key != "string") return []; const parsedAnnotation = parseObject({ object: markDef, schema, keyGenerator, schemaType, options }); return parsedAnnotation ? (markDefKeyMap.set(markDef._key, parsedAnnotation._key), [parsedAnnotation]) : []; }), markDefKeyMap }; } function parseChildInternal({ child, schema, keyGenerator, markDefKeyMap, options }) { return parseSpanInternal({ span: child, schema, keyGenerator, markDefKeyMap, options }) ?? parseInlineObjectInternal({ inlineObject: child, schema, keyGenerator, options }); } function parseSpanInternal({ span, schema, keyGenerator, 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] : schema.decorators.some((decorator) => decorator.name === mark) ? [mark] : []; }); if (!(typeof span._type == "string" && span._type !== schema.span.name)) return typeof span._type != "string" ? typeof span.text == "string" ? { _type: schema.span.name, _key: typeof span._key == "string" ? span._key : keyGenerator(), text: span.text, marks, ...options.validateFields ? {} : customFields } : void 0 : { _type: schema.span.name, _key: typeof span._key == "string" ? span._key : keyGenerator(), text: typeof span.text == "string" ? span.text : "", marks, ...options.validateFields ? {} : customFields }; } function parseInlineObjectInternal({ inlineObject, schema, keyGenerator, options }) { if (!isTypedObject(inlineObject)) return; const schemaType = schema.inlineObjects.find(({ name }) => name === inlineObject._type); if (schemaType) return parseObject({ object: inlineObject, schema, keyGenerator, schemaType, options }); } function parseAnnotation({ annotation, schema, keyGenerator, options }) { if (!isTypedObject(annotation)) return; const schemaType = schema.annotations.find(({ name }) => name === annotation._type); if (schemaType) return parseObject({ object: annotation, schema, keyGenerator, schemaType, options }); } function resolveOfMember(of, typeName, schema, ancestorFields) { for (const member of of) if (member.type !== "block") { if (member.type === "object" && "name" in member && member.name) { if (member.name === typeName && "fields" in member && member.fields) return { name: member.name, fields: member.fields }; continue; } if (member.type === typeName) { const ancestorMatch = ancestorFields.get(typeName); if (ancestorMatch) return { name: typeName, fields: ancestorMatch }; const rootMatch = schema.blockObjects.find((blockObject) => blockObject.name === typeName); return rootMatch && "fields" in rootMatch && rootMatch.fields ? { name: rootMatch.name, fields: rootMatch.fields } : void 0; } } } function parseObject({ object, schema, keyGenerator, schemaType, ancestorFields, options }) { const { _key, ...customFields } = object, fieldsByName = new Map(schemaType.fields.map((field) => [field.name, field])), nextAncestors = new Map(ancestorFields ?? []); nextAncestors.set(schemaType.name, schemaType.fields); const values = {}; for (const [key, value] of Object.entries(customFields)) { if (key === "_type" || value === void 0) continue; const field = fieldsByName.get(key); if (!(options.validateFields && !field)) { if (field && field.type === "array" && field.of && Array.isArray(value)) { values[key] = parseContainerFieldValue({ schema, keyGenerator, of: field.of, value, ancestorFields: nextAncestors, options }); continue; } values[key] = value; } } return { _type: schemaType.name, _key: typeof _key == "string" ? _key : keyGenerator(), ...values }; } function parseContainerFieldValue({ schema, keyGenerator, of, value, ancestorFields, options }) { const childBlockSubSchema = of.some((member) => member.type === "block") ? getSubSchema(schema, of) : void 0; return value.flatMap((item) => { if (!isTypedObject(item)) return [item]; if (childBlockSubSchema && item._type === childBlockSubSchema.block.name) { const parsed = parseTextBlock({ block: item, schema: childBlockSubSchema, keyGenerator, options: { normalize: !1, removeUnusedMarkDefs: !1, validateFields: options.validateFields } }); return parsed ? [parsed] : []; } const schemaType = resolveOfMember(of, item._type, schema, ancestorFields); return schemaType ? [parseObject({ object: item, schema, keyGenerator, schemaType: { name: schemaType.name, fields: schemaType.fields }, ancestorFields, options })] : [item]; }); } 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({ keyGenerator: context.keyGenerator ?? defaultKeyGenerator, block, options: { normalize: !1, removeUnusedMarkDefs: !0, validateFields: !1 }, schema: context.schema }) ?? block); } const parsedStartBlock = startBlock ? parseBlock({ keyGenerator: context.keyGenerator ?? defaultKeyGenerator, block: startBlock, options: { normalize: !1, removeUnusedMarkDefs: !0, validateFields: !1 }, schema: context.schema }) : void 0, parsedEndBlock = endBlock ? parseBlock({ keyGenerator: context.keyGenerator ?? defaultKeyGenerator, block: endBlock, options: { normalize: !1, removeUnusedMarkDefs: !0, validateFields: !1 }, schema: context.schema }) : void 0; return [...parsedStartBlock ? [parsedStartBlock] : [], ...middleBlocks, ...parsedEndBlock ? [parsedEndBlock] : []]; } export { blockOffsetToSpanSelectionPoint, defaultKeyGenerator, getAncestorTextBlock, getBlockEndPoint, getBlockKeyFromSelectionPoint, getBlockStartPoint, getSelectionEndPoint, getSelectionStartPoint, isEqualPathSegments, isEqualPaths, isEqualSelectionPoints, isListBlock, isSelectionCollapsed, parseAnnotation, parseBlock, parseBlocks, parseInlineObject, parseMarkDefs, parseSpan, sliceBlocks, spanSelectionPointToBlockOffset }; //# sourceMappingURL=util.slice-blocks.js.map