@portabletext/editor
Version:
Portable Text Editor made in React
1 lines • 660 kB
Source Map (JSON)
{"version":3,"file":"editor-provider.cjs","sources":["../../src/internal-utils/debug.ts","../../src/internal-utils/values.ts","../../src/internal-utils/weakMaps.ts","../../src/editor/mutation-machine.ts","../../src/internal-utils/validateValue.ts","../../src/internal-utils/withChanges.ts","../../src/internal-utils/withoutPatching.ts","../../../../node_modules/.pnpm/@sanity+diff-match-patch@3.2.0/node_modules/@sanity/diff-match-patch/dist/index.js","../../src/internal-utils/withUndoRedo.ts","../../src/editor/key-generator.ts","../../src/editor/with-applying-behavior-actions.ts","../../src/editor/plugins/createWithUndoRedo.ts","../../src/editor/sync-machine.ts","../../src/editor/components/Synchronizer.tsx","../../src/converters/converter.json.ts","../../src/converters/converter.portable-text.ts","../../src/converters/converter.text-html.ts","../../src/converters/converter.text-plain.ts","../../src/converters/converters.core.ts","../../src/internal-utils/schema.ts","../../src/internal-utils/operationToPatches.ts","../../src/behavior-actions/behavior.action.insert.text.ts","../../../../node_modules/.pnpm/@portabletext+toolkit@2.0.17/node_modules/@portabletext/toolkit/dist/index.js","../../src/internal-utils/sibling-utils.ts","../../src/editor/plugins/createWithPortableTextMarkModel.ts","../../src/behavior-actions/behavior.action.annotation.add.ts","../../src/behavior-actions/behavior.action.annotation.remove.ts","../../src/internal-utils/paths.ts","../../src/internal-utils/ranges.ts","../../src/behavior-actions/behavior.action.block.set.ts","../../src/behavior-actions/behavior.action.block.unset.ts","../../src/internal-utils/slate-utils.ts","../../src/behavior-actions/behavior.action.decorator.add.ts","../../src/behavior-actions/behavior.action.delete.ts","../../src/behavior-actions/behavior.action.delete.backward.ts","../../src/behavior-actions/behavior.action.delete.block.ts","../../src/behavior-actions/behavior.action.delete.forward.ts","../../src/behavior-actions/behavior.action.effect.ts","../../src/behavior-actions/behavior.action.insert-inline-object.ts","../../src/behavior-actions/behavior.action.insert-span.ts","../../src/behavior-actions/behavior.action.insert.block.ts","../../src/behavior-actions/behavior.action.move.backward.ts","../../src/behavior-actions/behavior.action.move.block.ts","../../src/behavior-actions/behavior.action.move.forward.ts","../../src/behavior-actions/behavior.action.noop.ts","../../src/behavior-actions/behavior.action.select.ts","../../src/behavior-actions/behavior.action.split.block.ts","../../src/behavior-actions/behavior.actions.ts","../../src/editor/plugins/create-with-event-listeners.ts","../../src/editor/plugins/createWithMaxBlocks.ts","../../src/editor/plugins/createWithObjectKeys.ts","../../src/internal-utils/applyPatch.ts","../../src/editor/plugins/createWithPatches.ts","../../src/editor/plugins/createWithPlaceholderBlock.ts","../../src/editor/plugins/createWithPortableTextBlockStyle.ts","../../src/editor/plugins/createWithPortableTextSelections.ts","../../src/editor/plugins/createWithSchemaTypes.ts","../../src/editor/plugins/createWithUtils.ts","../../src/editor/plugins/with-plugins.ts","../../src/editor/create-slate-editor.tsx","../../src/editor/legacy-schema.ts","../../src/editor/editor-schema.ts","../../src/internal-utils/slate-children-to-blocks.ts","../../src/editor/get-active-decorators.ts","../../src/editor/editor-selector.ts","../../src/editor/plugins/createWithEditableAPI.ts","../../src/editor/create-editor.ts","../../src/editor/editor-actor-context.ts","../../src/behaviors/behavior.abstract.annotation.ts","../../src/behaviors/behavior.abstract.decorator.ts","../../src/behaviors/behavior.abstract.delete.ts","../../src/behaviors/behavior.abstract.insert.ts","../../src/behaviors/behavior.abstract.list-item.ts","../../src/behaviors/behavior.abstract.move.ts","../../src/behaviors/behavior.abstract.select.ts","../../src/behaviors/behavior.abstract.style.ts","../../src/internal-utils/key-is.ts","../../src/behaviors/behavior.default.raise-soft-break.ts","../../src/behaviors/behavior.default.ts","../../src/behaviors/behavior.types.event.ts","../../src/behaviors/behavior.perform-event.ts","../../src/editor/editor-snapshot.ts","../../src/editor/editor-machine.ts","../../src/editor/hooks/usePortableTextEditor.ts","../../src/editor/hooks/usePortableTextEditorSelection.tsx","../../src/editor/PortableTextEditor.tsx","../../src/editor/editor-provider.tsx"],"sourcesContent":["import debug from 'debug'\n\nconst rootName = 'sanity-pte:'\n\nexport default debug(rootName)\nexport function debugWithName(name: string): debug.Debugger {\n const namespace = `${rootName}${name}`\n if (debug && debug.enabled(namespace)) {\n return debug(namespace)\n }\n return debug(rootName)\n}\n","import type {\n PathSegment,\n PortableTextBlock,\n PortableTextChild,\n PortableTextObject,\n PortableTextTextBlock,\n} from '@sanity/types'\nimport {isEqual} from 'lodash'\nimport {Element, Text, type Descendant, type Node} from 'slate'\nimport type {EditorSchema} from '../editor/editor-schema'\n\nexport const EMPTY_MARKDEFS: PortableTextObject[] = []\n\nexport const VOID_CHILD_KEY = 'void-child'\n\ntype Partial<T> = {\n [P in keyof T]?: T[P]\n}\n\nfunction keepObjectEquality(\n object: PortableTextBlock | PortableTextChild,\n keyMap: Record<string, PortableTextBlock | PortableTextChild>,\n) {\n const value = keyMap[object._key]\n if (value && isEqual(object, value)) {\n return value\n }\n keyMap[object._key] = object\n return object\n}\n\nexport function toSlateValue(\n value: PortableTextBlock[] | undefined,\n {schemaTypes}: {schemaTypes: EditorSchema},\n keyMap: Record<string, any> = {},\n): Descendant[] {\n if (value && Array.isArray(value)) {\n return value.map((block) => {\n const {_type, _key, ...rest} = block\n const isPortableText = block && block._type === schemaTypes.block.name\n if (isPortableText) {\n const textBlock = block as PortableTextTextBlock\n let hasInlines = false\n const hasMissingStyle = typeof textBlock.style === 'undefined'\n const hasMissingMarkDefs = typeof textBlock.markDefs === 'undefined'\n const hasMissingChildren = typeof textBlock.children === 'undefined'\n\n const children = (textBlock.children || []).map((child) => {\n const {_type: cType, _key: cKey, ...cRest} = child\n // Return 'slate' version of inline object where the actual\n // value is stored in the `value` property.\n // In slate, inline objects are represented as regular\n // children with actual text node in order to be able to\n // be selected the same way as the rest of the (text) content.\n if (cType !== 'span') {\n hasInlines = true\n return keepObjectEquality(\n {\n _type: cType,\n _key: cKey,\n children: [\n {\n _key: VOID_CHILD_KEY,\n _type: 'span',\n text: '',\n marks: [],\n },\n ],\n value: cRest,\n __inline: true,\n },\n keyMap,\n )\n }\n // Original child object (span)\n return child\n })\n // Return original block\n if (\n !hasMissingStyle &&\n !hasMissingMarkDefs &&\n !hasMissingChildren &&\n !hasInlines &&\n Element.isElement(block)\n ) {\n // Original object\n return block\n }\n // TODO: remove this when we have a better way to handle missing style\n if (hasMissingStyle) {\n rest.style = schemaTypes.styles[0].name\n }\n return keepObjectEquality({_type, _key, ...rest, children}, keyMap)\n }\n return keepObjectEquality(\n {\n _type,\n _key,\n children: [\n {\n _key: VOID_CHILD_KEY,\n _type: 'span',\n text: '',\n marks: [],\n },\n ],\n value: rest,\n },\n keyMap,\n )\n }) as Descendant[]\n }\n return []\n}\n\nexport function fromSlateValue(\n value: Descendant[],\n textBlockType: string,\n keyMap: Record<string, PortableTextBlock | PortableTextChild> = {},\n): PortableTextBlock[] {\n return value.map((block) => {\n const {_key, _type} = block\n if (!_key || !_type) {\n throw new Error('Not a valid block')\n }\n if (\n _type === textBlockType &&\n 'children' in block &&\n Array.isArray(block.children) &&\n _key\n ) {\n let hasInlines = false\n const children = block.children.map((child) => {\n const {_type: _cType} = child\n if ('value' in child && _cType !== 'span') {\n hasInlines = true\n const {\n value: v,\n _key: k,\n _type: t,\n __inline: _i,\n children: _c,\n ...rest\n } = child\n return keepObjectEquality(\n {...rest, ...v, _key: k as string, _type: t as string},\n keyMap,\n )\n }\n return child\n })\n if (!hasInlines) {\n return block as PortableTextBlock // Original object\n }\n return keepObjectEquality(\n {...block, children, _key, _type},\n keyMap,\n ) as PortableTextBlock\n }\n const blockValue = 'value' in block && block.value\n return keepObjectEquality(\n {_key, _type, ...(typeof blockValue === 'object' ? blockValue : {})},\n keyMap,\n ) as PortableTextBlock\n })\n}\n\nexport function isEqualToEmptyEditor(\n children: Descendant[] | PortableTextBlock[],\n schemaTypes: EditorSchema,\n): boolean {\n return (\n children === undefined ||\n (children && Array.isArray(children) && children.length === 0) ||\n (children &&\n Array.isArray(children) &&\n children.length === 1 &&\n Element.isElement(children[0]) &&\n children[0]._type === schemaTypes.block.name &&\n 'style' in children[0] &&\n children[0].style === schemaTypes.styles[0].name &&\n !('listItem' in children[0]) &&\n Array.isArray(children[0].children) &&\n children[0].children.length === 1 &&\n Text.isText(children[0].children[0]) &&\n children[0].children[0]._type === 'span' &&\n !children[0].children[0].marks?.join('') &&\n children[0].children[0].text === '')\n )\n}\n\nexport function findBlockAndIndexFromPath(\n firstPathSegment: PathSegment,\n children: (Node | Partial<Node>)[],\n): [Element | undefined, number | undefined] {\n let blockIndex = -1\n const isNumber = Number.isInteger(Number(firstPathSegment))\n if (isNumber) {\n blockIndex = Number(firstPathSegment)\n } else if (children) {\n blockIndex = children.findIndex(\n (blk) =>\n Element.isElement(blk) && isEqual({_key: blk._key}, firstPathSegment),\n )\n }\n if (blockIndex > -1) {\n return [children[blockIndex] as Element, blockIndex]\n }\n return [undefined, -1]\n}\n\nexport function findChildAndIndexFromPath(\n secondPathSegment: PathSegment,\n block: Element,\n): [Element | Text | undefined, number] {\n let childIndex = -1\n const isNumber = Number.isInteger(Number(secondPathSegment))\n if (isNumber) {\n childIndex = Number(secondPathSegment)\n } else {\n childIndex = block.children.findIndex((child) =>\n isEqual({_key: child._key}, secondPathSegment),\n )\n }\n if (childIndex > -1) {\n return [block.children[childIndex] as Element | Text, childIndex]\n }\n return [undefined, -1]\n}\n\nexport function getValueOrInitialValue(\n value: unknown,\n initialValue: PortableTextBlock[],\n): PortableTextBlock[] | undefined {\n if (value && Array.isArray(value) && value.length > 0) {\n return value\n }\n return initialValue\n}\n","import type {Editor, Range} from 'slate'\nimport type {EditorSelection} from '..'\n\n// Is the editor currently receiving remote changes that are being applied to the content?\nexport const IS_PROCESSING_REMOTE_CHANGES: WeakMap<Editor, boolean> =\n new WeakMap()\n\nexport const KEY_TO_SLATE_ELEMENT: WeakMap<Editor, any | undefined> =\n new WeakMap()\nexport const KEY_TO_VALUE_ELEMENT: WeakMap<Editor, any | undefined> =\n new WeakMap()\n\n// Keep object relation to slate range in the portable-text-range\nexport const SLATE_TO_PORTABLE_TEXT_RANGE = new WeakMap<\n Range,\n EditorSelection\n>()\n","import type {Patch} from '@portabletext/patches'\nimport type {PortableTextBlock} from '@sanity/types'\nimport {Editor} from 'slate'\nimport {\n and,\n assertEvent,\n assign,\n emit,\n enqueueActions,\n fromCallback,\n not,\n setup,\n stateIn,\n type AnyEventObject,\n} from 'xstate'\nimport type {PortableTextSlateEditor} from '../types/editor'\nimport type {EditorSchema} from './editor-schema'\n\n/**\n * Makes sure editor mutation events are debounced\n */\nexport const mutationMachine = setup({\n types: {\n context: {} as {\n pendingMutations: Array<{\n actionId?: string\n value: Array<PortableTextBlock> | undefined\n patches: Array<Patch>\n }>\n schema: EditorSchema\n slateEditor: PortableTextSlateEditor\n },\n events: {} as\n | {\n type: 'patch'\n patch: Patch\n actionId?: string\n value: Array<PortableTextBlock>\n }\n | {\n type: 'typing'\n }\n | {\n type: 'not typing'\n },\n input: {} as {\n schema: EditorSchema\n slateEditor: PortableTextSlateEditor\n },\n emitted: {} as\n | {\n type: 'has pending patches'\n }\n | {\n type: 'mutation'\n patches: Array<Patch>\n snapshot: Array<PortableTextBlock> | undefined\n },\n },\n actions: {\n 'emit has pending patches': emit({type: 'has pending patches'}),\n 'emit mutations': enqueueActions(({context, enqueue}) => {\n for (const bulk of context.pendingMutations) {\n enqueue.emit({\n type: 'mutation',\n patches: bulk.patches,\n snapshot: bulk.value,\n })\n }\n }),\n 'clear pending mutations': assign({\n pendingMutations: [],\n }),\n 'defer patch': assign({\n pendingMutations: ({context, event}) => {\n assertEvent(event, 'patch')\n\n if (context.pendingMutations.length === 0) {\n return [\n {\n actionId: event.actionId,\n value: event.value,\n patches: [event.patch],\n },\n ]\n }\n\n const lastBulk = context.pendingMutations.at(-1)\n\n if (lastBulk && lastBulk.actionId === event.actionId) {\n return context.pendingMutations.slice(0, -1).concat({\n value: event.value,\n actionId: lastBulk.actionId,\n patches: [...lastBulk.patches, event.patch],\n })\n }\n\n return context.pendingMutations.concat({\n value: event.value,\n actionId: event.actionId,\n patches: [event.patch],\n })\n },\n }),\n },\n actors: {\n 'type listener': fromCallback<\n AnyEventObject,\n {slateEditor: PortableTextSlateEditor},\n {type: 'typing'} | {type: 'not typing'}\n >(({input, sendBack}) => {\n const originalApply = input.slateEditor.apply\n\n input.slateEditor.apply = (op) => {\n if (op.type === 'insert_text' || op.type === 'remove_text') {\n sendBack({type: 'typing'})\n } else {\n sendBack({type: 'not typing'})\n }\n originalApply(op)\n }\n\n return () => {\n input.slateEditor.apply = originalApply\n }\n }),\n },\n guards: {\n 'is typing': stateIn({typing: 'typing'}),\n 'no pending mutations': ({context}) =>\n context.pendingMutations.length === 0,\n 'slate is normalizing': ({context}) =>\n Editor.isNormalizing(context.slateEditor),\n },\n delays: {\n 'mutation debounce': process.env.NODE_ENV === 'test' ? 250 : 0,\n 'type debounce': process.env.NODE_ENV === 'test' ? 0 : 250,\n },\n}).createMachine({\n id: 'mutation',\n context: ({input}) => ({\n pendingMutations: [],\n schema: input.schema,\n slateEditor: input.slateEditor,\n }),\n type: 'parallel',\n states: {\n typing: {\n initial: 'idle',\n invoke: {\n src: 'type listener',\n input: ({context}) => ({slateEditor: context.slateEditor}),\n },\n states: {\n idle: {\n on: {\n typing: {\n target: 'typing',\n },\n },\n },\n typing: {\n after: {\n 'type debounce': {\n target: 'idle',\n },\n },\n on: {\n 'not typing': {\n target: 'idle',\n },\n 'typing': {\n target: 'typing',\n reenter: true,\n },\n },\n },\n },\n },\n mutations: {\n initial: 'idle',\n states: {\n 'idle': {\n on: {\n patch: {\n actions: ['defer patch', 'emit has pending patches'],\n target: 'emitting mutations',\n },\n },\n },\n 'emitting mutations': {\n after: {\n 'mutation debounce': [\n {\n guard: and([not('is typing'), 'slate is normalizing']),\n target: 'idle',\n actions: ['emit mutations', 'clear pending mutations'],\n },\n {\n target: 'emitting mutations',\n reenter: true,\n },\n ],\n },\n on: {\n patch: {\n target: 'emitting mutations',\n actions: ['defer patch'],\n reenter: true,\n },\n },\n },\n },\n },\n },\n})\n","import {insert, set, setIfMissing, unset} from '@portabletext/patches'\nimport {\n isPortableTextTextBlock,\n type PortableTextBlock,\n type PortableTextSpan,\n type PortableTextTextBlock,\n} from '@sanity/types'\nimport {flatten, isPlainObject, uniq} from 'lodash'\nimport type {EditorSchema} from '../editor/editor-schema'\nimport type {InvalidValueResolution} from '../types/editor'\n\nexport interface Validation {\n valid: boolean\n resolution: InvalidValueResolution | null\n value: PortableTextBlock[] | undefined\n}\n\nexport function validateValue(\n value: PortableTextBlock[] | undefined,\n types: EditorSchema,\n keyGenerator: () => string,\n): Validation {\n let resolution: InvalidValueResolution | null = null\n let valid = true\n const validChildTypes = [\n types.span.name,\n ...types.inlineObjects.map((t) => t.name),\n ]\n const validBlockTypes = [\n types.block.name,\n ...types.blockObjects.map((t) => t.name),\n ]\n\n // Undefined is allowed\n if (value === undefined) {\n return {valid: true, resolution: null, value}\n }\n // Only lengthy arrays are allowed in the editor.\n if (!Array.isArray(value) || value.length === 0) {\n return {\n valid: false,\n resolution: {\n patches: [unset([])],\n description:\n 'Editor value must be an array of Portable Text blocks, or undefined.',\n action: 'Unset the value',\n item: value,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.not-an-array.description',\n action: 'inputs.portable-text.invalid-value.not-an-array.action',\n },\n },\n value,\n }\n }\n if (\n value.some((blk: PortableTextBlock, index: number): boolean => {\n // Is the block an object?\n if (!isPlainObject(blk)) {\n resolution = {\n patches: [unset([index])],\n description: `Block must be an object, got ${String(blk)}`,\n action: `Unset invalid item`,\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.not-an-object.description',\n action: 'inputs.portable-text.invalid-value.not-an-object.action',\n values: {index},\n },\n }\n return true\n }\n // Test that every block has a _key prop\n if (!blk._key || typeof blk._key !== 'string') {\n resolution = {\n patches: [set({...blk, _key: keyGenerator()}, [index])],\n description: `Block at index ${index} is missing required _key.`,\n action: 'Set the block with a random _key value',\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.missing-key.description',\n action: 'inputs.portable-text.invalid-value.missing-key.action',\n values: {index},\n },\n }\n return true\n }\n // Test that every block has valid _type\n if (!blk._type || !validBlockTypes.includes(blk._type)) {\n // Special case where block type is set to default 'block', but the block type is named something else according to the schema.\n if (blk._type === 'block') {\n const currentBlockTypeName = types.block.name\n resolution = {\n patches: [\n set({...blk, _type: currentBlockTypeName}, [{_key: blk._key}]),\n ],\n description: `Block with _key '${blk._key}' has invalid type name '${blk._type}'. According to the schema, the block type name is '${currentBlockTypeName}'`,\n action: `Use type '${currentBlockTypeName}'`,\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.incorrect-block-type.description',\n action:\n 'inputs.portable-text.invalid-value.incorrect-block-type.action',\n values: {key: blk._key, expectedTypeName: currentBlockTypeName},\n },\n }\n return true\n }\n\n // If the block has no `_type`, but aside from that is a valid Portable Text block\n if (\n !blk._type &&\n isPortableTextTextBlock({...blk, _type: types.block.name})\n ) {\n resolution = {\n patches: [\n set({...blk, _type: types.block.name}, [{_key: blk._key}]),\n ],\n description: `Block with _key '${blk._key}' is missing a type name. According to the schema, the block type name is '${types.block.name}'`,\n action: `Use type '${types.block.name}'`,\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.missing-block-type.description',\n action:\n 'inputs.portable-text.invalid-value.missing-block-type.action',\n values: {key: blk._key, expectedTypeName: types.block.name},\n },\n }\n return true\n }\n\n if (!blk._type) {\n resolution = {\n patches: [unset([{_key: blk._key}])],\n description: `Block with _key '${blk._key}' is missing an _type property`,\n action: 'Remove the block',\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.missing-type.description',\n action: 'inputs.portable-text.invalid-value.missing-type.action',\n values: {key: blk._key},\n },\n }\n return true\n }\n\n resolution = {\n patches: [unset([{_key: blk._key}])],\n description: `Block with _key '${blk._key}' has invalid _type '${blk._type}'`,\n action: 'Remove the block',\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.disallowed-type.description',\n action: 'inputs.portable-text.invalid-value.disallowed-type.action',\n values: {key: blk._key, typeName: blk._type},\n },\n }\n return true\n }\n\n // Test regular text blocks\n if (blk._type === types.block.name) {\n const textBlock = blk as PortableTextTextBlock\n // Test that it has a valid children property (array)\n if (textBlock.children && !Array.isArray(textBlock.children)) {\n resolution = {\n patches: [set({children: []}, [{_key: textBlock._key}])],\n description: `Text block with _key '${textBlock._key}' has a invalid required property 'children'.`,\n action: 'Reset the children property',\n item: textBlock,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.missing-or-invalid-children.description',\n action:\n 'inputs.portable-text.invalid-value.missing-or-invalid-children.action',\n values: {key: textBlock._key},\n },\n }\n return true\n }\n // Test that children is set and lengthy\n if (\n textBlock.children === undefined ||\n (Array.isArray(textBlock.children) && textBlock.children.length === 0)\n ) {\n const newSpan = {\n _type: types.span.name,\n _key: keyGenerator(),\n text: '',\n marks: [],\n }\n resolution = {\n autoResolve: true,\n patches: [\n setIfMissing([], [{_key: blk._key}, 'children']),\n insert([newSpan], 'after', [{_key: blk._key}, 'children', 0]),\n ],\n description: `Children for text block with _key '${blk._key}' is empty.`,\n action: 'Insert an empty text',\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.empty-children.description',\n action:\n 'inputs.portable-text.invalid-value.empty-children.action',\n values: {key: blk._key},\n },\n }\n return true\n }\n\n const allUsedMarks = uniq(\n flatten(\n textBlock.children\n .filter((cld) => cld._type === types.span.name)\n .map((cld) => cld.marks || []),\n ) as string[],\n )\n\n // Test that all markDefs are in use (remove orphaned markDefs)\n if (Array.isArray(blk.markDefs) && blk.markDefs.length > 0) {\n const unusedMarkDefs: string[] = uniq(\n blk.markDefs\n .map((def) => def._key)\n .filter((key) => !allUsedMarks.includes(key)),\n )\n if (unusedMarkDefs.length > 0) {\n resolution = {\n autoResolve: true,\n patches: unusedMarkDefs.map((markDefKey) =>\n unset([{_key: blk._key}, 'markDefs', {_key: markDefKey}]),\n ),\n description: `Block contains orphaned data (unused mark definitions): ${unusedMarkDefs.join(\n ', ',\n )}.`,\n action: 'Remove unused mark definition item',\n item: blk,\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.orphaned-mark-defs.description',\n action:\n 'inputs.portable-text.invalid-value.orphaned-mark-defs.action',\n values: {\n key: blk._key,\n unusedMarkDefs: unusedMarkDefs.map((m) => m.toString()),\n },\n },\n }\n return true\n }\n }\n\n // Test that every annotation mark used has a definition\n const annotationMarks = allUsedMarks.filter(\n (mark) => !types.decorators.map((dec) => dec.name).includes(mark),\n )\n const orphanedMarks = annotationMarks.filter(\n (mark) =>\n textBlock.markDefs === undefined ||\n !textBlock.markDefs.find((def) => def._key === mark),\n )\n if (orphanedMarks.length > 0) {\n const spanChildren = textBlock.children.filter(\n (cld) =>\n cld._type === types.span.name &&\n Array.isArray(cld.marks) &&\n cld.marks.some((mark) => orphanedMarks.includes(mark)),\n ) as PortableTextSpan[]\n if (spanChildren) {\n const orphaned = orphanedMarks.join(', ')\n resolution = {\n autoResolve: true,\n patches: spanChildren.map((child) => {\n return set(\n (child.marks || []).filter(\n (cMrk) => !orphanedMarks.includes(cMrk),\n ),\n [{_key: blk._key}, 'children', {_key: child._key}, 'marks'],\n )\n }),\n description: `Block with _key '${blk._key}' contains marks (${orphaned}) not supported by the current content model.`,\n action: 'Remove invalid marks',\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.orphaned-marks.description',\n action:\n 'inputs.portable-text.invalid-value.orphaned-marks.action',\n values: {\n key: blk._key,\n orphanedMarks: orphanedMarks.map((m) => m.toString()),\n },\n },\n }\n return true\n }\n }\n\n // Test every child\n if (\n textBlock.children.some((child, cIndex: number) => {\n if (!isPlainObject(child)) {\n resolution = {\n patches: [unset([{_key: blk._key}, 'children', cIndex])],\n description: `Child at index '${cIndex}' in block with key '${blk._key}' is not an object.`,\n action: 'Remove the item',\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.non-object-child.description',\n action:\n 'inputs.portable-text.invalid-value.non-object-child.action',\n values: {key: blk._key, index: cIndex},\n },\n }\n return true\n }\n\n if (!child._key || typeof child._key !== 'string') {\n const newChild = {...child, _key: keyGenerator()}\n resolution = {\n autoResolve: true,\n patches: [\n set(newChild, [{_key: blk._key}, 'children', cIndex]),\n ],\n description: `Child at index ${cIndex} is missing required _key in block with _key ${blk._key}.`,\n action: 'Set a new random _key on the object',\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.missing-child-key.description',\n action:\n 'inputs.portable-text.invalid-value.missing-child-key.action',\n values: {key: blk._key, index: cIndex},\n },\n }\n return true\n }\n\n // Verify that children have valid types\n if (!child._type) {\n resolution = {\n patches: [\n unset([{_key: blk._key}, 'children', {_key: child._key}]),\n ],\n description: `Child with _key '${child._key}' in block with key '${blk._key}' is missing '_type' property.`,\n action: 'Remove the object',\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.missing-child-type.description',\n action:\n 'inputs.portable-text.invalid-value.missing-child-type.action',\n values: {key: blk._key, childKey: child._key},\n },\n }\n return true\n }\n\n if (!validChildTypes.includes(child._type)) {\n resolution = {\n patches: [\n unset([{_key: blk._key}, 'children', {_key: child._key}]),\n ],\n description: `Child with _key '${child._key}' in block with key '${blk._key}' has invalid '_type' property (${child._type}).`,\n action: 'Remove the object',\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.disallowed-child-type.description',\n action:\n 'inputs.portable-text.invalid-value.disallowed-child-type.action',\n values: {\n key: blk._key,\n childKey: child._key,\n childType: child._type,\n },\n },\n }\n return true\n }\n\n // Verify that spans have .text property that is a string\n if (\n child._type === types.span.name &&\n typeof child.text !== 'string'\n ) {\n resolution = {\n patches: [\n set({...child, text: ''}, [\n {_key: blk._key},\n 'children',\n {_key: child._key},\n ]),\n ],\n description: `Child with _key '${child._key}' in block with key '${blk._key}' has missing or invalid text property!`,\n action: `Write an empty text property to the object`,\n item: blk,\n\n i18n: {\n description:\n 'inputs.portable-text.invalid-value.invalid-span-text.description',\n action:\n 'inputs.portable-text.invalid-value.invalid-span-text.action',\n values: {key: blk._key, childKey: child._key},\n },\n }\n return true\n }\n return false\n })\n ) {\n valid = false\n }\n }\n return false\n })\n ) {\n valid = false\n }\n return {valid, resolution, value}\n}\n","import type {Editor} from 'slate'\nimport {IS_PROCESSING_REMOTE_CHANGES} from './weakMaps'\n\nexport function withRemoteChanges(editor: Editor, fn: () => void): void {\n const prev = isChangingRemotely(editor) || false\n IS_PROCESSING_REMOTE_CHANGES.set(editor, true)\n fn()\n IS_PROCESSING_REMOTE_CHANGES.set(editor, prev)\n}\n\nexport function isChangingRemotely(editor: Editor): boolean | undefined {\n return IS_PROCESSING_REMOTE_CHANGES.get(editor)\n}\n","import type {Editor} from 'slate'\n\nexport const PATCHING: WeakMap<Editor, boolean | undefined> = new WeakMap()\n\nexport function withoutPatching(editor: Editor, fn: () => void): void {\n const prev = isPatching(editor)\n PATCHING.set(editor, false)\n fn()\n PATCHING.set(editor, prev)\n}\n\nexport function isPatching(editor: Editor): boolean | undefined {\n return PATCHING.get(editor)\n}\n","function cloneDiff(diff2) {\n const [type, patch] = diff2;\n return [type, patch];\n}\nfunction getCommonOverlap(textA, textB) {\n let text1 = textA, text2 = textB;\n const text1Length = text1.length, text2Length = text2.length;\n if (text1Length === 0 || text2Length === 0)\n return 0;\n text1Length > text2Length ? text1 = text1.substring(text1Length - text2Length) : text1Length < text2Length && (text2 = text2.substring(0, text1Length));\n const textLength = Math.min(text1Length, text2Length);\n if (text1 === text2)\n return textLength;\n let best = 0, length = 1;\n for (let found = 0; found !== -1; ) {\n const pattern = text1.substring(textLength - length);\n if (found = text2.indexOf(pattern), found === -1)\n return best;\n length += found, (found === 0 || text1.substring(textLength - length) === text2.substring(0, length)) && (best = length, length++);\n }\n return best;\n}\nfunction getCommonPrefix(text1, text2) {\n if (!text1 || !text2 || text1[0] !== text2[0])\n return 0;\n let pointerMin = 0, pointerMax = Math.min(text1.length, text2.length), pointerMid = pointerMax, pointerStart = 0;\n for (; pointerMin < pointerMid; )\n text1.substring(pointerStart, pointerMid) === text2.substring(pointerStart, pointerMid) ? (pointerMin = pointerMid, pointerStart = pointerMin) : pointerMax = pointerMid, pointerMid = Math.floor((pointerMax - pointerMin) / 2 + pointerMin);\n return pointerMid;\n}\nfunction getCommonSuffix(text1, text2) {\n if (!text1 || !text2 || text1[text1.length - 1] !== text2[text2.length - 1])\n return 0;\n let pointerMin = 0, pointerMax = Math.min(text1.length, text2.length), pointerMid = pointerMax, pointerEnd = 0;\n for (; pointerMin < pointerMid; )\n text1.substring(text1.length - pointerMid, text1.length - pointerEnd) === text2.substring(text2.length - pointerMid, text2.length - pointerEnd) ? (pointerMin = pointerMid, pointerEnd = pointerMin) : pointerMax = pointerMid, pointerMid = Math.floor((pointerMax - pointerMin) / 2 + pointerMin);\n return pointerMid;\n}\nfunction isHighSurrogate(char) {\n const charCode = char.charCodeAt(0);\n return charCode >= 55296 && charCode <= 56319;\n}\nfunction isLowSurrogate(char) {\n const charCode = char.charCodeAt(0);\n return charCode >= 56320 && charCode <= 57343;\n}\nfunction bisect(text1, text2, deadline) {\n const text1Length = text1.length, text2Length = text2.length, maxD = Math.ceil((text1Length + text2Length) / 2), vOffset = maxD, vLength = 2 * maxD, v1 = new Array(vLength), v2 = new Array(vLength);\n for (let x = 0; x < vLength; x++)\n v1[x] = -1, v2[x] = -1;\n v1[vOffset + 1] = 0, v2[vOffset + 1] = 0;\n const delta = text1Length - text2Length, front = delta % 2 !== 0;\n let k1start = 0, k1end = 0, k2start = 0, k2end = 0;\n for (let d = 0; d < maxD && !(Date.now() > deadline); d++) {\n for (let k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {\n const k1Offset = vOffset + k1;\n let x1;\n k1 === -d || k1 !== d && v1[k1Offset - 1] < v1[k1Offset + 1] ? x1 = v1[k1Offset + 1] : x1 = v1[k1Offset - 1] + 1;\n let y1 = x1 - k1;\n for (; x1 < text1Length && y1 < text2Length && text1.charAt(x1) === text2.charAt(y1); )\n x1++, y1++;\n if (v1[k1Offset] = x1, x1 > text1Length)\n k1end += 2;\n else if (y1 > text2Length)\n k1start += 2;\n else if (front) {\n const k2Offset = vOffset + delta - k1;\n if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {\n const x2 = text1Length - v2[k2Offset];\n if (x1 >= x2)\n return bisectSplit(text1, text2, x1, y1, deadline);\n }\n }\n }\n for (let k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {\n const k2Offset = vOffset + k2;\n let x2;\n k2 === -d || k2 !== d && v2[k2Offset - 1] < v2[k2Offset + 1] ? x2 = v2[k2Offset + 1] : x2 = v2[k2Offset - 1] + 1;\n let y2 = x2 - k2;\n for (; x2 < text1Length && y2 < text2Length && text1.charAt(text1Length - x2 - 1) === text2.charAt(text2Length - y2 - 1); )\n x2++, y2++;\n if (v2[k2Offset] = x2, x2 > text1Length)\n k2end += 2;\n else if (y2 > text2Length)\n k2start += 2;\n else if (!front) {\n const k1Offset = vOffset + delta - k2;\n if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {\n const x1 = v1[k1Offset], y1 = vOffset + x1 - k1Offset;\n if (x2 = text1Length - x2, x1 >= x2)\n return bisectSplit(text1, text2, x1, y1, deadline);\n }\n }\n }\n }\n return [\n [DIFF_DELETE, text1],\n [DIFF_INSERT, text2]\n ];\n}\nfunction bisectSplit(text1, text2, x, y, deadline) {\n const text1a = text1.substring(0, x), text2a = text2.substring(0, y), text1b = text1.substring(x), text2b = text2.substring(y), diffs = doDiff(text1a, text2a, { checkLines: !1, deadline }), diffsb = doDiff(text1b, text2b, { checkLines: !1, deadline });\n return diffs.concat(diffsb);\n}\nfunction findHalfMatch(text1, text2, timeout = 1) {\n if (timeout <= 0)\n return null;\n const longText = text1.length > text2.length ? text1 : text2, shortText = text1.length > text2.length ? text2 : text1;\n if (longText.length < 4 || shortText.length * 2 < longText.length)\n return null;\n const halfMatch1 = halfMatchI(longText, shortText, Math.ceil(longText.length / 4)), halfMatch2 = halfMatchI(longText, shortText, Math.ceil(longText.length / 2));\n let halfMatch;\n if (halfMatch1 && halfMatch2)\n halfMatch = halfMatch1[4].length > halfMatch2[4].length ? halfMatch1 : halfMatch2;\n else {\n if (!halfMatch1 && !halfMatch2)\n return null;\n halfMatch2 ? halfMatch1 || (halfMatch = halfMatch2) : halfMatch = halfMatch1;\n }\n if (!halfMatch)\n throw new Error(\"Unable to find a half match.\");\n let text1A, text1B, text2A, text2B;\n text1.length > text2.length ? (text1A = halfMatch[0], text1B = halfMatch[1], text2A = halfMatch[2], text2B = halfMatch[3]) : (text2A = halfMatch[0], text2B = halfMatch[1], text1A = halfMatch[2], text1B = halfMatch[3]);\n const midCommon = halfMatch[4];\n return [text1A, text1B, text2A, text2B, midCommon];\n}\nfunction halfMatchI(longText, shortText, i) {\n const seed = longText.slice(i, i + Math.floor(longText.length / 4));\n let j = -1, bestCommon = \"\", bestLongTextA, bestLongTextB, bestShortTextA, bestShortTextB;\n for (; (j = shortText.indexOf(seed, j + 1)) !== -1; ) {\n const prefixLength = getCommonPrefix(longText.slice(i), shortText.slice(j)), suffixLength = getCommonSuffix(longText.slice(0, i), shortText.slice(0, j));\n bestCommon.length < suffixLength + prefixLength && (bestCommon = shortText.slice(j - suffixLength, j) + shortText.slice(j, j + prefixLength), bestLongTextA = longText.slice(0, i - suffixLength), bestLongTextB = longText.slice(i + prefixLength), bestShortTextA = shortText.slice(0, j - suffixLength), bestShortTextB = shortText.slice(j + prefixLength));\n }\n return bestCommon.length * 2 >= longText.length ? [\n bestLongTextA || \"\",\n bestLongTextB || \"\",\n bestShortTextA || \"\",\n bestShortTextB || \"\",\n bestCommon || \"\"\n ] : null;\n}\nfunction charsToLines(diffs, lineArray) {\n for (let x = 0; x < diffs.length; x++) {\n const chars = diffs[x][1], text = [];\n for (let y = 0; y < chars.length; y++)\n text[y] = lineArray[chars.charCodeAt(y)];\n diffs[x][1] = text.join(\"\");\n }\n}\nfunction linesToChars(textA, textB) {\n const lineArray = [], lineHash = {};\n lineArray[0] = \"\";\n function diffLinesToMunge(text) {\n let chars = \"\", lineStart = 0, lineEnd = -1, lineArrayLength = lineArray.length;\n for (; lineEnd < text.length - 1; ) {\n lineEnd = text.indexOf(`\n`, lineStart), lineEnd === -1 && (lineEnd = text.length - 1);\n let line = text.slice(lineStart, lineEnd + 1);\n (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== void 0) ? chars += String.fromCharCode(lineHash[line]) : (lineArrayLength === maxLines && (line = text.slice(lineStart), lineEnd = text.length), chars += String.fromCharCode(lineArrayLength), lineHash[line] = lineArrayLength, lineArray[lineArrayLength++] = line), lineStart = lineEnd + 1;\n }\n return chars;\n }\n let maxLines = 4e4;\n const chars1 = diffLinesToMunge(textA);\n maxLines = 65535;\n const chars2 = diffLinesToMunge(textB);\n return { chars1, chars2, lineArray };\n}\nfunction doLineModeDiff(textA, textB, opts) {\n let text1 = textA, text2 = textB;\n const a = linesToChars(text1, text2);\n text1 = a.chars1, text2 = a.chars2;\n const linearray = a.lineArray;\n let diffs = doDiff(text1, text2, {\n checkLines: !1,\n deadline: opts.deadline\n });\n charsToLines(diffs, linearray), diffs = cleanupSemantic(diffs), diffs.push([DIFF_EQUAL, \"\"]);\n let pointer = 0, countDelete = 0, countInsert = 0, textDelete = \"\", textInsert = \"\";\n for (; pointer < diffs.length; ) {\n switch (diffs[pointer][0]) {\n case DIFF_INSERT:\n countInsert++, textInsert += diffs[pointer][1];\n break;\n case DIFF_DELETE:\n countDelete++, textDelete += diffs[pointer][1];\n break;\n case DIFF_EQUAL:\n if (countDelete >= 1 && countInsert >= 1) {\n diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert), pointer = pointer - countDelete - countInsert;\n const aa = doDiff(textDelete, textInsert, {\n checkLines: !1,\n deadline: opts.deadline\n });\n for (let j = aa.length - 1; j >= 0; j--)\n diffs.splice(pointer, 0, aa[j]);\n pointer += aa.length;\n }\n countInsert = 0, countDelete = 0, textDelete = \"\", textInsert = \"\";\n break;\n default:\n throw new Error(\"Unknown diff operation.\");\n }\n pointer++;\n }\n return diffs.pop(), diffs;\n}\nfunction computeDiff(text1, text2, opts) {\n let diffs;\n if (!text1)\n return [[DIFF_INSERT, text2]];\n if (!text2)\n return [[DIFF_DELETE, text1]];\n const longtext = text1.length > text2.length ? text1 : text2, shorttext = text1.length > text2.length ? text2 : text1, i = longtext.indexOf(shorttext);\n if (i !== -1)\n return diffs = [\n [DIFF_INSERT, longtext.substring(0, i)],\n [DIFF_EQUAL, shorttext],\n [DIFF_INSERT, longtext.substring(i + shorttext.length)]\n ], text1.length > text2.length && (diffs[0][0] = DIFF_DELETE, diffs[2][0] = DIFF_DELETE), diffs;\n if (shorttext.length === 1)\n return [\n [DIFF_DELETE, text1],\n [DIFF_INSERT, text2]\n ];\n const halfMatch = findHalfMatch(text1, text2);\n if (halfMatch) {\n const text1A = halfMatch[0], text1B = halfMatch[1], text2A = halfMatch[2], text2B = halfMatch[3], midCommon = halfMatch[4], diffsA = doDiff(text1A, text2A, opts), diffsB = doDiff(text1B, text2B, opts);\n return diffsA.concat([[DIFF_EQUAL, midCommon]], diffsB);\n }\n return opts.checkLines && text1.length > 100 && text2.length > 100 ? doLineModeDiff(text1, text2, opts) : bisect(text1, text2, opts.deadline);\n}\nvar __defProp$2 = Object.defineProperty, __getOwnPropSymbols$2 = Object.getOwnPropertySymbols, __hasOwnProp$2 = Object.prototype.hasOwnProperty, __propIsEnum$2 = Object.prototype.propertyIsEnumerable, __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value, __spreadValues$2 = (a, b) => {\n for (var prop in b || (b = {}))\n __hasOwnProp$2.call(b, prop) && __defNormalProp$2(a, prop, b[prop]);\n if (__getOwnPropSymbols$2)\n for (var prop of __getOwnPropSymbols$2(b))\n __propIsEnum$2.call(b, prop) && __defNormalProp$2(a, prop, b[prop]);\n return a;\n};\nconst DIFF_DELETE = -1, DIFF_INSERT = 1, DIFF_EQUAL = 0;\nfunction diff(textA, textB, opts) {\n if (textA === null || textB === null)\n throw new Error(\"Null input. (diff)\");\n const diffs = doDiff(textA, textB, createInternalOpts(opts || {}));\n return adjustDiffForSurrogatePairs(diffs), diffs;\n}\nfunction doDiff(textA, textB, options) {\n let text1 = textA, text2 = textB;\n if (text1 === text2)\n return text1 ? [[DIFF_EQUAL, text1]] : [];\n let commonlength = getCommonPrefix(text1, text2);\n const commonprefix = text1.substring(0, commonlength);\n text1 = text1.substring(commonlength), text2 = text2.substring(commonlength), commonlength = getCommonSuffix(text1, text2);\n const commonsuffix = text1.substring(text1.length - commonlength);\n text1 = text1.substring(0, text1.length - commonlength), text2 = text2.substring(0, text2.length - commonlength);\n let diffs = computeDiff(text1, text2, options);\n return commonprefix && diffs.unshift([DIFF_EQUAL, commonprefix]), commonsuffix && diffs.push([DIFF_EQUAL, commonsuffix]), diffs = cleanupMerge(diffs), diffs;\n}\nfunction createDeadLine(timeout) {\n let t = 1;\n return typeof timeout < \"u\" && (t = timeout <= 0 ? Number.MAX_VALUE : timeout), Date.now() + t * 1e3;\n}\nfunction createInternalOpts(opts) {\n return __spreadValues$2({\n checkLines: !0,\n deadline: createDeadLine(opts.timeout || 1)\n }, opts);\n}\nfunction combineChar(data, char, dir) {\n return dir === 1 ? data + char : char + data;\n}\nfunction splitChar(data, dir) {\n return dir === 1 ? [data.substring(0, data.length - 1), data[data.length - 1]] : [data.substring(1), data[0]];\n}\nfunction hasSharedChar(diffs, i, j, dir) {\n return dir === 1 ? diffs[i][1][diffs[i][1].length - 1] === diffs[j][1][diffs[j][1].length - 1] : diffs[i][1][0] === diffs[j][1][0];\n}\nfunction deisolateChar(diffs, i, dir) {\n const inv = dir === 1 ? -1 : 1;\n let insertIdx = null, deleteIdx = null, j = i + dir;\n for (; j >= 0 && j < diffs.length && (insertIdx === null || deleteIdx === null); j += dir) {\n const [op, text2] = diffs[j];\n if (text2.length !== 0) {\n if (op === DIFF_INSERT) {\n insertIdx === null && (insertIdx = j);\n continue;\n } else if (op === DIFF_DELETE) {\n deleteIdx === null && (deleteIdx = j);\n continue;\n } else if (op === DIFF_EQUAL) {\n if (insertIdx === null && deleteIdx === null) {\n const [rest, char2] = splitChar(diffs[i][1], dir);\n diffs[i][1] = rest, diffs[j][1] = combineChar(diffs[j][1], char2, i