UNPKG

@portabletext/editor

Version:

Portable Text Editor made in React

1,045 lines 288 kB
import { jsx, jsxs, Fragment } from "react/jsx-runtime"; import { useSelector, useActorRef } from "@xstate/react"; import React, { useEffect, createContext, useContext, useState, startTransition, Component, useMemo } from "react"; import { withReact, ReactEditor, Slate } from "slate-react"; import { c } from "react-compiler-runtime"; import debug$g from "debug"; import isEqual from "lodash/isEqual.js"; import { Element, Text, Editor, Path, Operation, Transforms, Node, Range, Point, createEditor } from "slate"; import { setup, stateIn, fromCallback, assign, enqueueActions, emit, assertEvent, and, not, createActor } from "xstate"; import { unset, set, setIfMissing, insert, diffMatchPatch as diffMatchPatch$1, applyAll } from "@portabletext/patches"; import { isPortableTextTextBlock, isKeySegment, isPortableTextSpan as isPortableTextSpan$1, isPortableTextListBlock, defineType, defineField } from "@sanity/types"; import flatten from "lodash/flatten.js"; import isPlainObject from "lodash/isPlainObject.js"; import uniq from "lodash/uniq.js"; import getRandomValues from "get-random-values-esm"; import { parseBlock, parseAnnotation, isTextBlock, parseInlineObject } from "./parse-blocks.js"; import { sliceBlocks, blockOffsetToSpanSelectionPoint, isEmptyTextBlock, getBlockEndPoint, getBlockStartPoint, getTextBlockText } from "./util.slice-blocks.js"; import { htmlToBlocks } from "@portabletext/block-tools"; import { toHTML } from "@portabletext/to-html"; import { Schema } from "@sanity/schema"; import get from "lodash/get.js"; import isUndefined from "lodash/isUndefined.js"; import omitBy from "lodash/omitBy.js"; import omit from "lodash/omit.js"; import { selectionPointToBlockOffset, blockOffsetsToSelection } from "./util.selection-point-to-block-offset.js"; import { getTrimmedSelection, isActiveAnnotation, isActiveDecorator, getSelectedTextBlocks, isActiveListItem, isActiveStyle, isSelectingEntireBlocks, getActiveAnnotations } from "./selector.is-selecting-entire-blocks.js"; import { DOMEditor } from "slate-dom"; import startCase from "lodash.startcase"; import { defineBehavior, raise, coreBehaviors } from "./behavior.core.js"; import { getFocusTextBlock, getPreviousBlock, getNextBlock, getFocusSpan, isSelectionCollapsed, isOverlappingSelection, getSelectedBlocks, isSelectionExpanded } from "./selector.is-overlapping-selection.js"; import { Subject } from "rxjs"; import { useEffectEvent } from "use-effect-event"; const rootName = "sanity-pte:"; debug$g(rootName); function debugWithName(name) { const namespace = `${rootName}${name}`; return debug$g && debug$g.enabled(namespace) ? debug$g(namespace) : debug$g(rootName); } const VOID_CHILD_KEY = "void-child"; function keepObjectEquality(object, keyMap) { const value = keyMap[object._key]; return value && isEqual(object, value) ? value : (keyMap[object._key] = object, object); } function toSlateValue(value, { schemaTypes }, keyMap = {}) { return value && Array.isArray(value) ? value.map((block) => { const { _type, _key, ...rest } = block; if (block && block._type === schemaTypes.block.name) { const textBlock = block; let hasInlines = !1; const hasMissingStyle = typeof textBlock.style > "u", hasMissingMarkDefs = typeof textBlock.markDefs > "u", hasMissingChildren = typeof textBlock.children > "u", children = (textBlock.children || []).map((child) => { const { _type: cType, _key: cKey, ...cRest } = child; return cType !== "span" ? (hasInlines = !0, keepObjectEquality({ _type: cType, _key: cKey, children: [{ _key: VOID_CHILD_KEY, _type: "span", text: "", marks: [] }], value: cRest, __inline: !0 }, keyMap)) : child; }); return !hasMissingStyle && !hasMissingMarkDefs && !hasMissingChildren && !hasInlines && Element.isElement(block) ? block : (hasMissingStyle && (rest.style = schemaTypes.styles[0].name), keepObjectEquality({ _type, _key, ...rest, children }, keyMap)); } return keepObjectEquality({ _type, _key, children: [{ _key: VOID_CHILD_KEY, _type: "span", text: "", marks: [] }], value: rest }, keyMap); }) : []; } function fromSlateValue(value, textBlockType, keyMap = {}) { return value.map((block) => { const { _key, _type } = block; if (!_key || !_type) throw new Error("Not a valid block"); if (_type === textBlockType && "children" in block && Array.isArray(block.children) && _key) { let hasInlines = !1; const children = block.children.map((child) => { const { _type: _cType } = child; if ("value" in child && _cType !== "span") { hasInlines = !0; const { value: v, _key: k, _type: t, __inline: _i, children: _c, ...rest } = child; return keepObjectEquality({ ...rest, ...v, _key: k, _type: t }, keyMap); } return child; }); return hasInlines ? keepObjectEquality({ ...block, children, _key, _type }, keyMap) : block; } const blockValue = "value" in block && block.value; return keepObjectEquality({ _key, _type, ...typeof blockValue == "object" ? blockValue : {} }, keyMap); }); } function isEqualToEmptyEditor(children, schemaTypes) { return children === void 0 || children && Array.isArray(children) && children.length === 0 || children && Array.isArray(children) && children.length === 1 && Element.isElement(children[0]) && children[0]._type === schemaTypes.block.name && "style" in children[0] && children[0].style === schemaTypes.styles[0].name && !("listItem" in children[0]) && Array.isArray(children[0].children) && children[0].children.length === 1 && Text.isText(children[0].children[0]) && children[0].children[0]._type === "span" && !children[0].children[0].marks?.join("") && children[0].children[0].text === ""; } const IS_PROCESSING_REMOTE_CHANGES = /* @__PURE__ */ new WeakMap(), KEY_TO_SLATE_ELEMENT = /* @__PURE__ */ new WeakMap(), KEY_TO_VALUE_ELEMENT = /* @__PURE__ */ new WeakMap(), SLATE_TO_PORTABLE_TEXT_RANGE = /* @__PURE__ */ new WeakMap(), mutationMachine = setup({ types: { context: {}, events: {}, input: {}, emitted: {} }, actions: { "emit has pending patches": emit({ type: "has pending patches" }), "emit mutations": enqueueActions(({ context, enqueue }) => { for (const bulk of context.pendingMutations) enqueue.emit({ type: "mutation", patches: bulk.patches, snapshot: bulk.value }); }), "clear pending mutations": assign({ pendingMutations: [] }), "defer patch": assign({ pendingMutations: ({ context, event }) => { if (assertEvent(event, "patch"), context.pendingMutations.length === 0) return [{ actionId: event.actionId, value: event.value, patches: [event.patch] }]; const lastBulk = context.pendingMutations.at(-1); return lastBulk && lastBulk.actionId === event.actionId ? context.pendingMutations.slice(0, -1).concat({ value: event.value, actionId: lastBulk.actionId, patches: [...lastBulk.patches, event.patch] }) : context.pendingMutations.concat({ value: event.value, actionId: event.actionId, patches: [event.patch] }); } }) }, actors: { "type listener": fromCallback(({ input, sendBack }) => { const originalApply = input.slateEditor.apply; return input.slateEditor.apply = (op) => { op.type === "insert_text" || op.type === "remove_text" ? sendBack({ type: "typing" }) : sendBack({ type: "not typing" }), originalApply(op); }, () => { input.slateEditor.apply = originalApply; }; }) }, guards: { "is typing": stateIn({ typing: "typing" }), "no pending mutations": ({ context }) => context.pendingMutations.length === 0, "slate is normalizing": ({ context }) => Editor.isNormalizing(context.slateEditor) }, delays: { "mutation debounce": process.env.NODE_ENV === "test" ? 250 : 0, "type debounce": process.env.NODE_ENV === "test" ? 0 : 250 } }).createMachine({ id: "mutation", context: ({ input }) => ({ pendingMutations: [], schema: input.schema, slateEditor: input.slateEditor }), type: "parallel", states: { typing: { initial: "idle", invoke: { src: "type listener", input: ({ context }) => ({ slateEditor: context.slateEditor }) }, states: { idle: { on: { typing: { target: "typing" } } }, typing: { after: { "type debounce": { target: "idle" } }, on: { "not typing": { target: "idle" }, typing: { target: "typing", reenter: !0 } } } } }, mutations: { initial: "idle", states: { idle: { on: { patch: { actions: ["defer patch", "emit has pending patches"], target: "emitting mutations" } } }, "emitting mutations": { after: { "mutation debounce": [{ guard: and([not("is typing"), "slate is normalizing"]), target: "idle", actions: ["emit mutations", "clear pending mutations"] }, { target: "emitting mutations", reenter: !0 }] }, on: { patch: { target: "emitting mutations", actions: ["defer patch"], reenter: !0 } } } } } } }); function validateValue(value, types, keyGenerator) { let resolution = null, valid = !0; const validChildTypes = [types.span.name, ...types.inlineObjects.map((t) => t.name)], validBlockTypes = [types.block.name, ...types.blockObjects.map((t) => t.name)]; return value === void 0 ? { valid: !0, resolution: null, value } : !Array.isArray(value) || value.length === 0 ? { valid: !1, resolution: { patches: [unset([])], description: "Editor value must be an array of Portable Text blocks, or undefined.", action: "Unset the value", item: value, i18n: { description: "inputs.portable-text.invalid-value.not-an-array.description", action: "inputs.portable-text.invalid-value.not-an-array.action" } }, value } : (value.some((blk, index) => { if (!isPlainObject(blk)) return resolution = { patches: [unset([index])], description: `Block must be an object, got ${String(blk)}`, action: "Unset invalid item", item: blk, i18n: { description: "inputs.portable-text.invalid-value.not-an-object.description", action: "inputs.portable-text.invalid-value.not-an-object.action", values: { index } } }, !0; if (!blk._key || typeof blk._key != "string") return resolution = { patches: [set({ ...blk, _key: keyGenerator() }, [index])], description: `Block at index ${index} is missing required _key.`, action: "Set the block with a random _key value", item: blk, i18n: { description: "inputs.portable-text.invalid-value.missing-key.description", action: "inputs.portable-text.invalid-value.missing-key.action", values: { index } } }, !0; if (!blk._type || !validBlockTypes.includes(blk._type)) { if (blk._type === "block") { const currentBlockTypeName = types.block.name; return resolution = { patches: [set({ ...blk, _type: currentBlockTypeName }, [{ _key: blk._key }])], description: `Block with _key '${blk._key}' has invalid type name '${blk._type}'. According to the schema, the block type name is '${currentBlockTypeName}'`, action: `Use type '${currentBlockTypeName}'`, item: blk, i18n: { description: "inputs.portable-text.invalid-value.incorrect-block-type.description", action: "inputs.portable-text.invalid-value.incorrect-block-type.action", values: { key: blk._key, expectedTypeName: currentBlockTypeName } } }, !0; } return !blk._type && isPortableTextTextBlock({ ...blk, _type: types.block.name }) ? (resolution = { patches: [set({ ...blk, _type: types.block.name }, [{ _key: blk._key }])], description: `Block with _key '${blk._key}' is missing a type name. According to the schema, the block type name is '${types.block.name}'`, action: `Use type '${types.block.name}'`, item: blk, i18n: { description: "inputs.portable-text.invalid-value.missing-block-type.description", action: "inputs.portable-text.invalid-value.missing-block-type.action", values: { key: blk._key, expectedTypeName: types.block.name } } }, !0) : blk._type ? (resolution = { patches: [unset([{ _key: blk._key }])], description: `Block with _key '${blk._key}' has invalid _type '${blk._type}'`, action: "Remove the block", item: blk, i18n: { description: "inputs.portable-text.invalid-value.disallowed-type.description", action: "inputs.portable-text.invalid-value.disallowed-type.action", values: { key: blk._key, typeName: blk._type } } }, !0) : (resolution = { patches: [unset([{ _key: blk._key }])], description: `Block with _key '${blk._key}' is missing an _type property`, action: "Remove the block", item: blk, i18n: { description: "inputs.portable-text.invalid-value.missing-type.description", action: "inputs.portable-text.invalid-value.missing-type.action", values: { key: blk._key } } }, !0); } if (blk._type === types.block.name) { const textBlock = blk; if (textBlock.children && !Array.isArray(textBlock.children)) return resolution = { patches: [set({ children: [] }, [{ _key: textBlock._key }])], description: `Text block with _key '${textBlock._key}' has a invalid required property 'children'.`, action: "Reset the children property", item: textBlock, i18n: { description: "inputs.portable-text.invalid-value.missing-or-invalid-children.description", action: "inputs.portable-text.invalid-value.missing-or-invalid-children.action", values: { key: textBlock._key } } }, !0; if (textBlock.children === void 0 || Array.isArray(textBlock.children) && textBlock.children.length === 0) { const newSpan = { _type: types.span.name, _key: keyGenerator(), text: "", marks: [] }; return resolution = { autoResolve: !0, patches: [setIfMissing([], [{ _key: blk._key }, "children"]), insert([newSpan], "after", [{ _key: blk._key }, "children", 0])], description: `Children for text block with _key '${blk._key}' is empty.`, action: "Insert an empty text", item: blk, i18n: { description: "inputs.portable-text.invalid-value.empty-children.description", action: "inputs.portable-text.invalid-value.empty-children.action", values: { key: blk._key } } }, !0; } const allUsedMarks = uniq(flatten(textBlock.children.filter((cld) => cld._type === types.span.name).map((cld) => cld.marks || []))); if (Array.isArray(blk.markDefs) && blk.markDefs.length > 0) { const unusedMarkDefs = uniq(blk.markDefs.map((def) => def._key).filter((key) => !allUsedMarks.includes(key))); if (unusedMarkDefs.length > 0) return resolution = { autoResolve: !0, patches: unusedMarkDefs.map((markDefKey) => unset([{ _key: blk._key }, "markDefs", { _key: markDefKey }])), description: `Block contains orphaned data (unused mark definitions): ${unusedMarkDefs.join(", ")}.`, action: "Remove unused mark definition item", item: blk, i18n: { description: "inputs.portable-text.invalid-value.orphaned-mark-defs.description", action: "inputs.portable-text.invalid-value.orphaned-mark-defs.action", values: { key: blk._key, unusedMarkDefs: unusedMarkDefs.map((m) => m.toString()) } } }, !0; } const orphanedMarks = allUsedMarks.filter((mark) => !types.decorators.map((dec) => dec.name).includes(mark)).filter((mark) => textBlock.markDefs === void 0 || !textBlock.markDefs.find((def) => def._key === mark)); if (orphanedMarks.length > 0) { const spanChildren = textBlock.children.filter((cld) => cld._type === types.span.name && Array.isArray(cld.marks) && cld.marks.some((mark) => orphanedMarks.includes(mark))); if (spanChildren) { const orphaned = orphanedMarks.join(", "); return resolution = { autoResolve: !0, patches: spanChildren.map((child) => set((child.marks || []).filter((cMrk) => !orphanedMarks.includes(cMrk)), [{ _key: blk._key }, "children", { _key: child._key }, "marks"])), description: `Block with _key '${blk._key}' contains marks (${orphaned}) not supported by the current content model.`, action: "Remove invalid marks", item: blk, i18n: { description: "inputs.portable-text.invalid-value.orphaned-marks.description", action: "inputs.portable-text.invalid-value.orphaned-marks.action", values: { key: blk._key, orphanedMarks: orphanedMarks.map((m) => m.toString()) } } }, !0; } } textBlock.children.some((child, cIndex) => { if (!isPlainObject(child)) return resolution = { patches: [unset([{ _key: blk._key }, "children", cIndex])], description: `Child at index '${cIndex}' in block with key '${blk._key}' is not an object.`, action: "Remove the item", item: blk, i18n: { description: "inputs.portable-text.invalid-value.non-object-child.description", action: "inputs.portable-text.invalid-value.non-object-child.action", values: { key: blk._key, index: cIndex } } }, !0; if (!child._key || typeof child._key != "string") { const newChild = { ...child, _key: keyGenerator() }; return resolution = { autoResolve: !0, patches: [set(newChild, [{ _key: blk._key }, "children", cIndex])], description: `Child at index ${cIndex} is missing required _key in block with _key ${blk._key}.`, action: "Set a new random _key on the object", item: blk, i18n: { description: "inputs.portable-text.invalid-value.missing-child-key.description", action: "inputs.portable-text.invalid-value.missing-child-key.action", values: { key: blk._key, index: cIndex } } }, !0; } return child._type ? validChildTypes.includes(child._type) ? child._type === types.span.name && typeof child.text != "string" ? (resolution = { patches: [set({ ...child, text: "" }, [{ _key: blk._key }, "children", { _key: child._key }])], description: `Child with _key '${child._key}' in block with key '${blk._key}' has missing or invalid text property!`, action: "Write an empty text property to the object", item: blk, i18n: { description: "inputs.portable-text.invalid-value.invalid-span-text.description", action: "inputs.portable-text.invalid-value.invalid-span-text.action", values: { key: blk._key, childKey: child._key } } }, !0) : !1 : (resolution = { patches: [unset([{ _key: blk._key }, "children", { _key: child._key }])], description: `Child with _key '${child._key}' in block with key '${blk._key}' has invalid '_type' property (${child._type}).`, action: "Remove the object", item: blk, i18n: { description: "inputs.portable-text.invalid-value.disallowed-child-type.description", action: "inputs.portable-text.invalid-value.disallowed-child-type.action", values: { key: blk._key, childKey: child._key, childType: child._type } } }, !0) : (resolution = { patches: [unset([{ _key: blk._key }, "children", { _key: child._key }])], description: `Child with _key '${child._key}' in block with key '${blk._key}' is missing '_type' property.`, action: "Remove the object", item: blk, i18n: { description: "inputs.portable-text.invalid-value.missing-child-type.description", action: "inputs.portable-text.invalid-value.missing-child-type.action", values: { key: blk._key, childKey: child._key } } }, !0); }) && (valid = !1); } return !1; }) && (valid = !1), { valid, resolution, value }); } function withRemoteChanges(editor, fn) { const prev = isChangingRemotely(editor) || !1; IS_PROCESSING_REMOTE_CHANGES.set(editor, !0), fn(), IS_PROCESSING_REMOTE_CHANGES.set(editor, prev); } function isChangingRemotely(editor) { return IS_PROCESSING_REMOTE_CHANGES.get(editor); } const PATCHING = /* @__PURE__ */ new WeakMap(); function withoutPatching(editor, fn) { const prev = isPatching(editor); PATCHING.set(editor, !1), fn(), PATCHING.set(editor, prev); } function isPatching(editor) { return PATCHING.get(editor); } function cloneDiff(diff2) { const [type, patch] = diff2; return [type, patch]; } function getCommonOverlap(textA, textB) { let text1 = textA, text2 = textB; const text1Length = text1.length, text2Length = text2.length; if (text1Length === 0 || text2Length === 0) return 0; text1Length > text2Length ? text1 = text1.substring(text1Length - text2Length) : text1Length < text2Length && (text2 = text2.substring(0, text1Length)); const textLength = Math.min(text1Length, text2Length); if (text1 === text2) return textLength; let best = 0, length = 1; for (let found = 0; found !== -1; ) { const pattern = text1.substring(textLength - length); if (found = text2.indexOf(pattern), found === -1) return best; length += found, (found === 0 || text1.substring(textLength - length) === text2.substring(0, length)) && (best = length, length++); } return best; } function getCommonPrefix(text1, text2) { if (!text1 || !text2 || text1[0] !== text2[0]) return 0; let pointerMin = 0, pointerMax = Math.min(text1.length, text2.length), pointerMid = pointerMax, pointerStart = 0; for (; pointerMin < pointerMid; ) text1.substring(pointerStart, pointerMid) === text2.substring(pointerStart, pointerMid) ? (pointerMin = pointerMid, pointerStart = pointerMin) : pointerMax = pointerMid, pointerMid = Math.floor((pointerMax - pointerMin) / 2 + pointerMin); return pointerMid; } function getCommonSuffix(text1, text2) { if (!text1 || !text2 || text1[text1.length - 1] !== text2[text2.length - 1]) return 0; let pointerMin = 0, pointerMax = Math.min(text1.length, text2.length), pointerMid = pointerMax, pointerEnd = 0; for (; pointerMin < pointerMid; ) 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); return pointerMid; } function isHighSurrogate(char) { const charCode = char.charCodeAt(0); return charCode >= 55296 && charCode <= 56319; } function isLowSurrogate(char) { const charCode = char.charCodeAt(0); return charCode >= 56320 && charCode <= 57343; } function bisect(text1, text2, deadline) { 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); for (let x = 0; x < vLength; x++) v1[x] = -1, v2[x] = -1; v1[vOffset + 1] = 0, v2[vOffset + 1] = 0; const delta = text1Length - text2Length, front = delta % 2 !== 0; let k1start = 0, k1end = 0, k2start = 0, k2end = 0; for (let d = 0; d < maxD && !(Date.now() > deadline); d++) { for (let k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { const k1Offset = vOffset + k1; let x1; k1 === -d || k1 !== d && v1[k1Offset - 1] < v1[k1Offset + 1] ? x1 = v1[k1Offset + 1] : x1 = v1[k1Offset - 1] + 1; let y1 = x1 - k1; for (; x1 < text1Length && y1 < text2Length && text1.charAt(x1) === text2.charAt(y1); ) x1++, y1++; if (v1[k1Offset] = x1, x1 > text1Length) k1end += 2; else if (y1 > text2Length) k1start += 2; else if (front) { const k2Offset = vOffset + delta - k1; if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) { const x2 = text1Length - v2[k2Offset]; if (x1 >= x2) return bisectSplit(text1, text2, x1, y1, deadline); } } } for (let k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { const k2Offset = vOffset + k2; let x2; k2 === -d || k2 !== d && v2[k2Offset - 1] < v2[k2Offset + 1] ? x2 = v2[k2Offset + 1] : x2 = v2[k2Offset - 1] + 1; let y2 = x2 - k2; for (; x2 < text1Length && y2 < text2Length && text1.charAt(text1Length - x2 - 1) === text2.charAt(text2Length - y2 - 1); ) x2++, y2++; if (v2[k2Offset] = x2, x2 > text1Length) k2end += 2; else if (y2 > text2Length) k2start += 2; else if (!front) { const k1Offset = vOffset + delta - k2; if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) { const x1 = v1[k1Offset], y1 = vOffset + x1 - k1Offset; if (x2 = text1Length - x2, x1 >= x2) return bisectSplit(text1, text2, x1, y1, deadline); } } } } return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; } function bisectSplit(text1, text2, x, y, deadline) { 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 }); return diffs.concat(diffsb); } function findHalfMatch(text1, text2, timeout = 1) { if (timeout <= 0) return null; const longText = text1.length > text2.length ? text1 : text2, shortText = text1.length > text2.length ? text2 : text1; if (longText.length < 4 || shortText.length * 2 < longText.length) return null; const halfMatch1 = halfMatchI(longText, shortText, Math.ceil(longText.length / 4)), halfMatch2 = halfMatchI(longText, shortText, Math.ceil(longText.length / 2)); let halfMatch; if (halfMatch1 && halfMatch2) halfMatch = halfMatch1[4].length > halfMatch2[4].length ? halfMatch1 : halfMatch2; else { if (!halfMatch1 && !halfMatch2) return null; halfMatch2 ? halfMatch1 || (halfMatch = halfMatch2) : halfMatch = halfMatch1; } if (!halfMatch) throw new Error("Unable to find a half match."); let text1A, text1B, text2A, text2B; 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]); const midCommon = halfMatch[4]; return [text1A, text1B, text2A, text2B, midCommon]; } function halfMatchI(longText, shortText, i) { const seed = longText.slice(i, i + Math.floor(longText.length / 4)); let j = -1, bestCommon = "", bestLongTextA, bestLongTextB, bestShortTextA, bestShortTextB; for (; (j = shortText.indexOf(seed, j + 1)) !== -1; ) { const prefixLength = getCommonPrefix(longText.slice(i), shortText.slice(j)), suffixLength = getCommonSuffix(longText.slice(0, i), shortText.slice(0, j)); 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)); } return bestCommon.length * 2 >= longText.length ? [bestLongTextA || "", bestLongTextB || "", bestShortTextA || "", bestShortTextB || "", bestCommon || ""] : null; } function charsToLines(diffs, lineArray) { for (let x = 0; x < diffs.length; x++) { const chars = diffs[x][1], text = []; for (let y = 0; y < chars.length; y++) text[y] = lineArray[chars.charCodeAt(y)]; diffs[x][1] = text.join(""); } } function linesToChars(textA, textB) { const lineArray = [], lineHash = {}; lineArray[0] = ""; function diffLinesToMunge(text) { let chars = "", lineStart = 0, lineEnd = -1, lineArrayLength = lineArray.length; for (; lineEnd < text.length - 1; ) { lineEnd = text.indexOf(` `, lineStart), lineEnd === -1 && (lineEnd = text.length - 1); let line = text.slice(lineStart, lineEnd + 1); (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; } return chars; } let maxLines = 4e4; const chars1 = diffLinesToMunge(textA); maxLines = 65535; const chars2 = diffLinesToMunge(textB); return { chars1, chars2, lineArray }; } function doLineModeDiff(textA, textB, opts) { let text1 = textA, text2 = textB; const a = linesToChars(text1, text2); text1 = a.chars1, text2 = a.chars2; const linearray = a.lineArray; let diffs = doDiff(text1, text2, { checkLines: !1, deadline: opts.deadline }); charsToLines(diffs, linearray), diffs = cleanupSemantic(diffs), diffs.push([DIFF_EQUAL, ""]); let pointer = 0, countDelete = 0, countInsert = 0, textDelete = "", textInsert = ""; for (; pointer < diffs.length; ) { switch (diffs[pointer][0]) { case DIFF_INSERT: countInsert++, textInsert += diffs[pointer][1]; break; case DIFF_DELETE: countDelete++, textDelete += diffs[pointer][1]; break; case DIFF_EQUAL: if (countDelete >= 1 && countInsert >= 1) { diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert), pointer = pointer - countDelete - countInsert; const aa = doDiff(textDelete, textInsert, { checkLines: !1, deadline: opts.deadline }); for (let j = aa.length - 1; j >= 0; j--) diffs.splice(pointer, 0, aa[j]); pointer += aa.length; } countInsert = 0, countDelete = 0, textDelete = "", textInsert = ""; break; default: throw new Error("Unknown diff operation."); } pointer++; } return diffs.pop(), diffs; } function computeDiff(text1, text2, opts) { let diffs; if (!text1) return [[DIFF_INSERT, text2]]; if (!text2) return [[DIFF_DELETE, text1]]; const longtext = text1.length > text2.length ? text1 : text2, shorttext = text1.length > text2.length ? text2 : text1, i = longtext.indexOf(shorttext); if (i !== -1) return diffs = [[DIFF_INSERT, longtext.substring(0, i)], [DIFF_EQUAL, shorttext], [DIFF_INSERT, longtext.substring(i + shorttext.length)]], text1.length > text2.length && (diffs[0][0] = DIFF_DELETE, diffs[2][0] = DIFF_DELETE), diffs; if (shorttext.length === 1) return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; const halfMatch = findHalfMatch(text1, text2); if (halfMatch) { 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); return diffsA.concat([[DIFF_EQUAL, midCommon]], diffsB); } return opts.checkLines && text1.length > 100 && text2.length > 100 ? doLineModeDiff(text1, text2, opts) : bisect(text1, text2, opts.deadline); } var __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) => { for (var prop in b || (b = {})) __hasOwnProp$2.call(b, prop) && __defNormalProp$2(a, prop, b[prop]); if (__getOwnPropSymbols$2) for (var prop of __getOwnPropSymbols$2(b)) __propIsEnum$2.call(b, prop) && __defNormalProp$2(a, prop, b[prop]); return a; }; const DIFF_DELETE = -1, DIFF_INSERT = 1, DIFF_EQUAL = 0; function diff(textA, textB, opts) { if (textA === null || textB === null) throw new Error("Null input. (diff)"); const diffs = doDiff(textA, textB, createInternalOpts(opts || {})); return adjustDiffForSurrogatePairs(diffs), diffs; } function doDiff(textA, textB, options) { let text1 = textA, text2 = textB; if (text1 === text2) return text1 ? [[DIFF_EQUAL, text1]] : []; let commonlength = getCommonPrefix(text1, text2); const commonprefix = text1.substring(0, commonlength); text1 = text1.substring(commonlength), text2 = text2.substring(commonlength), commonlength = getCommonSuffix(text1, text2); const commonsuffix = text1.substring(text1.length - commonlength); text1 = text1.substring(0, text1.length - commonlength), text2 = text2.substring(0, text2.length - commonlength); let diffs = computeDiff(text1, text2, options); return commonprefix && diffs.unshift([DIFF_EQUAL, commonprefix]), commonsuffix && diffs.push([DIFF_EQUAL, commonsuffix]), diffs = cleanupMerge(diffs), diffs; } function createDeadLine(timeout) { let t = 1; return typeof timeout < "u" && (t = timeout <= 0 ? Number.MAX_VALUE : timeout), Date.now() + t * 1e3; } function createInternalOpts(opts) { return __spreadValues$2({ checkLines: !0, deadline: createDeadLine(opts.timeout || 1) }, opts); } function combineChar(data, char, dir) { return dir === 1 ? data + char : char + data; } function splitChar(data, dir) { return dir === 1 ? [data.substring(0, data.length - 1), data[data.length - 1]] : [data.substring(1), data[0]]; } function hasSharedChar(diffs, i, j, dir) { 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]; } function deisolateChar(diffs, i, dir) { const inv = dir === 1 ? -1 : 1; let insertIdx = null, deleteIdx = null, j = i + dir; for (; j >= 0 && j < diffs.length && (insertIdx === null || deleteIdx === null); j += dir) { const [op, text2] = diffs[j]; if (text2.length !== 0) { if (op === DIFF_INSERT) { insertIdx === null && (insertIdx = j); continue; } else if (op === DIFF_DELETE) { deleteIdx === null && (deleteIdx = j); continue; } else if (op === DIFF_EQUAL) { if (insertIdx === null && deleteIdx === null) { const [rest, char2] = splitChar(diffs[i][1], dir); diffs[i][1] = rest, diffs[j][1] = combineChar(diffs[j][1], char2, inv); return; } break; } } } if (insertIdx !== null && deleteIdx !== null && hasSharedChar(diffs, insertIdx, deleteIdx, dir)) { const [insertText, insertChar] = splitChar(diffs[insertIdx][1], inv), [deleteText] = splitChar(diffs[deleteIdx][1], inv); diffs[insertIdx][1] = insertText, diffs[deleteIdx][1] = deleteText, diffs[i][1] = combineChar(diffs[i][1], insertChar, dir); return; } const [text, char] = splitChar(diffs[i][1], dir); diffs[i][1] = text, insertIdx === null ? (diffs.splice(j, 0, [DIFF_INSERT, char]), deleteIdx !== null && deleteIdx >= j && deleteIdx++) : diffs[insertIdx][1] = combineChar(diffs[insertIdx][1], char, inv), deleteIdx === null ? diffs.splice(j, 0, [DIFF_DELETE, char]) : diffs[deleteIdx][1] = combineChar(diffs[deleteIdx][1], char, inv); } function adjustDiffForSurrogatePairs(diffs) { for (let i = 0; i < diffs.length; i++) { const [diffType, diffText] = diffs[i]; if (diffText.length === 0) continue; const firstChar = diffText[0], lastChar = diffText[diffText.length - 1]; isHighSurrogate(lastChar) && diffType === DIFF_EQUAL && deisolateChar(diffs, i, 1), isLowSurrogate(firstChar) && diffType === DIFF_EQUAL && deisolateChar(diffs, i, -1); } for (let i = 0; i < diffs.length; i++) diffs[i][1].length === 0 && diffs.splice(i, 1); } function cleanupSemantic(rawDiffs) { let diffs = rawDiffs.map((diff2) => cloneDiff(diff2)), hasChanges = !1; const equalities = []; let equalitiesLength = 0, lastEquality = null, pointer = 0, lengthInsertions1 = 0, lengthDeletions1 = 0, lengthInsertions2 = 0, lengthDeletions2 = 0; for (; pointer < diffs.length; ) diffs[pointer][0] === DIFF_EQUAL ? (equalities[equalitiesLength++] = pointer, lengthInsertions1 = lengthInsertions2, lengthDeletions1 = lengthDeletions2, lengthInsertions2 = 0, lengthDeletions2 = 0, lastEquality = diffs[pointer][1]) : (diffs[pointer][0] === DIFF_INSERT ? lengthInsertions2 += diffs[pointer][1].length : lengthDeletions2 += diffs[pointer][1].length, lastEquality && lastEquality.length <= Math.max(lengthInsertions1, lengthDeletions1) && lastEquality.length <= Math.max(lengthInsertions2, lengthDeletions2) && (diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastEquality]), diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT, equalitiesLength--, equalitiesLength--, pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1, lengthInsertions1 = 0, lengthDeletions1 = 0, lengthInsertions2 = 0, lengthDeletions2 = 0, lastEquality = null, hasChanges = !0)), pointer++; for (hasChanges && (diffs = cleanupMerge(diffs)), diffs = cleanupSemanticLossless(diffs), pointer = 1; pointer < diffs.length; ) { if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) { const deletion = diffs[pointer - 1][1], insertion = diffs[pointer][1], overlapLength1 = getCommonOverlap(deletion, insertion), overlapLength2 = getCommonOverlap(insertion, deletion); overlapLength1 >= overlapLength2 ? (overlapLength1 >= deletion.length / 2 || overlapLength1 >= insertion.length / 2) && (diffs.splice(pointer, 0, [DIFF_EQUAL, insertion.substring(0, overlapLength1)]), diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlapLength1), diffs[pointer + 1][1] = insertion.substring(overlapLength1), pointer++) : (overlapLength2 >= deletion.length / 2 || overlapLength2 >= insertion.length / 2) && (diffs.splice(pointer, 0, [DIFF_EQUAL, deletion.substring(0, overlapLength2)]), diffs[pointer - 1][0] = DIFF_INSERT, diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlapLength2), diffs[pointer + 1][0] = DIFF_DELETE, diffs[pointer + 1][1] = deletion.substring(overlapLength2), pointer++), pointer++; } pointer++; } return diffs; } const nonAlphaNumericRegex = /[^a-zA-Z0-9]/, whitespaceRegex = /\s/, linebreakRegex = /[\r\n]/, blanklineEndRegex = /\n\r?\n$/, blanklineStartRegex = /^\r?\n\r?\n/; function cleanupSemanticLossless(rawDiffs) { const diffs = rawDiffs.map((diff2) => cloneDiff(diff2)); function diffCleanupSemanticScore(one, two) { if (!one || !two) return 6; const char1 = one.charAt(one.length - 1), char2 = two.charAt(0), nonAlphaNumeric1 = char1.match(nonAlphaNumericRegex), nonAlphaNumeric2 = char2.match(nonAlphaNumericRegex), whitespace1 = nonAlphaNumeric1 && char1.match(whitespaceRegex), whitespace2 = nonAlphaNumeric2 && char2.match(whitespaceRegex), lineBreak1 = whitespace1 && char1.match(linebreakRegex), lineBreak2 = whitespace2 && char2.match(linebreakRegex), blankLine1 = lineBreak1 && one.match(blanklineEndRegex), blankLine2 = lineBreak2 && two.match(blanklineStartRegex); return blankLine1 || blankLine2 ? 5 : lineBreak1 || lineBreak2 ? 4 : nonAlphaNumeric1 && !whitespace1 && whitespace2 ? 3 : whitespace1 || whitespace2 ? 2 : nonAlphaNumeric1 || nonAlphaNumeric2 ? 1 : 0; } let pointer = 1; for (; pointer < diffs.length - 1; ) { if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) { let equality1 = diffs[pointer - 1][1], edit = diffs[pointer][1], equality2 = diffs[pointer + 1][1]; const commonOffset = getCommonSuffix(equality1, edit); if (commonOffset) { const commonString = edit.substring(edit.length - commonOffset); equality1 = equality1.substring(0, equality1.length - commonOffset), edit = commonString + edit.substring(0, edit.length - commonOffset), equality2 = commonString + equality2; } let bestEquality1 = equality1, bestEdit = edit, bestEquality2 = equality2, bestScore = diffCleanupSemanticScore(equality1, edit) + diffCleanupSemanticScore(edit, equality2); for (; edit.charAt(0) === equality2.charAt(0); ) { equality1 += edit.charAt(0), edit = edit.substring(1) + equality2.charAt(0), equality2 = equality2.substring(1); const score = diffCleanupSemanticScore(equality1, edit) + diffCleanupSemanticScore(edit, equality2); score >= bestScore && (bestScore = score, bestEquality1 = equality1, bestEdit = edit, bestEquality2 = equality2); } diffs[pointer - 1][1] !== bestEquality1 && (bestEquality1 ? diffs[pointer - 1][1] = bestEquality1 : (diffs.splice(pointer - 1, 1), pointer--), diffs[pointer][1] = bestEdit, bestEquality2 ? diffs[pointer + 1][1] = bestEquality2 : (diffs.splice(pointer + 1, 1), pointer--)); } pointer++; } return diffs; } function cleanupMerge(rawDiffs) { let diffs = rawDiffs.map((diff2) => cloneDiff(diff2)); diffs.push([DIFF_EQUAL, ""]); let pointer = 0, countDelete = 0, countInsert = 0, textDelete = "", textInsert = "", commonlength; for (; pointer < diffs.length; ) switch (diffs[pointer][0]) { case DIFF_INSERT: countInsert++, textInsert += diffs[pointer][1], pointer++; break; case DIFF_DELETE: countDelete++, textDelete += diffs[pointer][1], pointer++; break; case DIFF_EQUAL: countDelete + countInsert > 1 ? (countDelete !== 0 && countInsert !== 0 && (commonlength = getCommonPrefix(textInsert, textDelete), commonlength !== 0 && (pointer - countDelete - countInsert > 0 && diffs[pointer - countDelete - countInsert - 1][0] === DIFF_EQUAL ? diffs[pointer - countDelete - countInsert - 1][1] += textInsert.substring(0, commonlength) : (diffs.splice(0, 0, [DIFF_EQUAL, textInsert.substring(0, commonlength)]), pointer++), textInsert = textInsert.substring(commonlength), textDelete = textDelete.substring(commonlength)), commonlength = getCommonSuffix(textInsert, textDelete), commonlength !== 0 && (diffs[pointer][1] = textInsert.substring(textInsert.length - commonlength) + diffs[pointer][1], textInsert = textInsert.substring(0, textInsert.length - commonlength), textDelete = textDelete.substring(0, textDelete.length - commonlength))), pointer -= countDelete + countInsert, diffs.splice(pointer, countDelete + countInsert), textDelete.length && (diffs.splice(pointer, 0, [DIFF_DELETE, textDelete]), pointer++), textInsert.length && (diffs.splice(pointer, 0, [DIFF_INSERT, textInsert]), pointer++), pointer++) : pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL ? (diffs[pointer - 1][1] += diffs[pointer][1], diffs.splice(pointer, 1)) : pointer++, countInsert = 0, countDelete = 0, textDelete = "", textInsert = ""; break; default: throw new Error("Unknown diff operation"); } diffs[diffs.length - 1][1] === "" && diffs.pop(); let hasChanges = !1; for (pointer = 1; pointer < diffs.length - 1; ) diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL && (diffs[pointer][1].substring(diffs[pointer][1].length - diffs[pointer - 1][1].length) === diffs[pointer - 1][1] ? (diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length), diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1], diffs.splice(pointer - 1, 1), hasChanges = !0) : diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1] && (diffs[pointer - 1][1] += diffs[pointer + 1][1], diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1], diffs.splice(pointer + 1, 1), hasChanges = !0)), pointer++; return hasChanges && (diffs = cleanupMerge(diffs)), diffs; } function trueCount(...args) { return args.reduce((n, bool) => n + (bool ? 1 : 0), 0); } function cleanupEfficiency(rawDiffs, editCost = 4) { let diffs = rawDiffs.map((diff2) => cloneDiff(diff2)), hasChanges = !1; const equalities = []; let equalitiesLength = 0, lastEquality = null, pointer = 0, preIns = !1, preDel = !1, postIns = !1, postDel = !1; for (; pointer < diffs.length; ) diffs[pointer][0] === DIFF_EQUAL ? (diffs[pointer][1].length < editCost && (postIns || postDel) ? (equalities[equalitiesLength++] = pointer, preIns = postIns, preDel = postDel, lastEquality = diffs[pointer][1]) : (equalitiesLength = 0, lastEquality = null), postIns = !1, postDel = !1) : (diffs[pointer][0] === DIFF_DELETE ? postDel = !0 : postIns = !0, lastEquality && (preIns && preDel && postIns && postDel || lastEquality.length < editCost / 2 && trueCount(preIns, preDel, postIns, postDel) === 3) && (diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastEquality]), diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT, equalitiesLength--, lastEquality = null, preIns && preDel ? (postIns = !0, postDel = !0, equalitiesLength = 0) : (equalitiesLength--, pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1, postIns = !1, postDel = !1), hasChanges = !0)), pointer++; return hasChanges && (diffs = cleanupMerge(diffs)), diffs; } var __defProp$1 = Object.defineProperty, __getOwnPropSymbols$1 = Object.getOwnPropertySymbols, __hasOwnProp$1 = Object.prototype.hasOwnProperty, __propIsEnum$1 = Object.prototype.propertyIsEnumerable, __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value, __spreadValues$1 = (a, b) => { for (var prop in b || (b = {})) __hasOwnProp$1.call(b, prop) && __defNormalProp$1(a, prop, b[prop]); if (__getOwnPropSymbols$1) for (var prop of __getOwnPropSymbols$1(b)) __propIsEnum$1.call(b, prop) && __defNormalProp$1(a, prop, b[prop]); return a; }; const DEFAULT_OPTIONS = { /** * At what point is no match declared (0.0 = perfection, 1.0 = very loose). */ threshold: 0.5, /** * How far to search for a match (0 = exact location, 1000+ = broad match). * A match this many characters away from the expected location will add * 1.0 to the score (0.0 is a perfect match). */ distance: 1e3 }; function applyDefaults(options) { return __spreadValues$1(__spreadValues$1({}, DEFAULT_OPTIONS), options); } const MAX_BITS$1 = 32; function bitap(text, pattern, loc, opts = {}) { if (pattern.length > MAX_BITS$1) throw new Error("Pattern too long for this browser."); const options = applyDefaults(opts), s = getAlphabetFromPattern(pattern); function getBitapScore(e, x) { const accuracy = e / pattern.length, proximity = Math.abs(loc - x); return options.distance ? accuracy + proximity / options.distance : proximity ? 1 : accuracy; } let scoreThreshold = options.threshold, bestLoc = text.indexOf(pattern, loc); bestLoc !== -1 && (scoreThreshold = Math.min(getBitapScore(0, bestL