UNPKG

@tiptap/core

Version:

headless rich text editor

1,648 lines (1,573 loc) 170 kB
var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/helpers/createChainableState.ts function createChainableState(config) { const { state, transaction } = config; let { selection } = transaction; let { doc } = transaction; let { storedMarks } = transaction; return { ...state, apply: state.apply.bind(state), applyTransaction: state.applyTransaction.bind(state), plugins: state.plugins, schema: state.schema, reconfigure: state.reconfigure.bind(state), toJSON: state.toJSON.bind(state), get storedMarks() { return storedMarks; }, get selection() { return selection; }, get doc() { return doc; }, get tr() { selection = transaction.selection; doc = transaction.doc; storedMarks = transaction.storedMarks; return transaction; } }; } // src/CommandManager.ts var CommandManager = class { constructor(props) { this.editor = props.editor; this.rawCommands = this.editor.extensionManager.commands; this.customState = props.state; } get hasCustomState() { return !!this.customState; } get state() { return this.customState || this.editor.state; } get commands() { const { rawCommands, editor, state } = this; const { view } = editor; const { tr } = state; const props = this.buildProps(tr); return Object.fromEntries( Object.entries(rawCommands).map(([name, command2]) => { const method = (...args) => { const callback = command2(...args)(props); if (!tr.getMeta("preventDispatch") && !this.hasCustomState) { view.dispatch(tr); } return callback; }; return [name, method]; }) ); } get chain() { return () => this.createChain(); } get can() { return () => this.createCan(); } createChain(startTr, shouldDispatch = true) { const { rawCommands, editor, state } = this; const { view } = editor; const callbacks = []; const hasStartTransaction = !!startTr; const tr = startTr || state.tr; const run3 = () => { if (!hasStartTransaction && shouldDispatch && !tr.getMeta("preventDispatch") && !this.hasCustomState) { view.dispatch(tr); } return callbacks.every((callback) => callback === true); }; const chain = { ...Object.fromEntries( Object.entries(rawCommands).map(([name, command2]) => { const chainedCommand = (...args) => { const props = this.buildProps(tr, shouldDispatch); const callback = command2(...args)(props); callbacks.push(callback); return chain; }; return [name, chainedCommand]; }) ), run: run3 }; return chain; } createCan(startTr) { const { rawCommands, state } = this; const dispatch = false; const tr = startTr || state.tr; const props = this.buildProps(tr, dispatch); const formattedCommands = Object.fromEntries( Object.entries(rawCommands).map(([name, command2]) => { return [name, (...args) => command2(...args)({ ...props, dispatch: void 0 })]; }) ); return { ...formattedCommands, chain: () => this.createChain(tr, dispatch) }; } buildProps(tr, shouldDispatch = true) { const { rawCommands, editor, state } = this; const { view } = editor; const props = { tr, editor, view, state: createChainableState({ state, transaction: tr }), dispatch: shouldDispatch ? () => void 0 : void 0, chain: () => this.createChain(tr, shouldDispatch), can: () => this.createCan(tr), get commands() { return Object.fromEntries( Object.entries(rawCommands).map(([name, command2]) => { return [name, (...args) => command2(...args)(props)]; }) ); } }; return props; } }; // src/Editor.ts import { EditorState } from "@tiptap/pm/state"; import { EditorView } from "@tiptap/pm/view"; // src/EventEmitter.ts var EventEmitter = class { constructor() { this.callbacks = {}; } on(event, fn) { if (!this.callbacks[event]) { this.callbacks[event] = []; } this.callbacks[event].push(fn); return this; } emit(event, ...args) { const callbacks = this.callbacks[event]; if (callbacks) { callbacks.forEach((callback) => callback.apply(this, args)); } return this; } off(event, fn) { const callbacks = this.callbacks[event]; if (callbacks) { if (fn) { this.callbacks[event] = callbacks.filter((callback) => callback !== fn); } else { delete this.callbacks[event]; } } return this; } once(event, fn) { const onceFn = (...args) => { this.off(event, onceFn); fn.apply(this, args); }; return this.on(event, onceFn); } removeAllListeners() { this.callbacks = {}; } }; // src/ExtensionManager.ts import { keymap } from "@tiptap/pm/keymap"; // src/helpers/combineTransactionSteps.ts import { Transform } from "@tiptap/pm/transform"; function combineTransactionSteps(oldDoc, transactions) { const transform = new Transform(oldDoc); transactions.forEach((transaction) => { transaction.steps.forEach((step) => { transform.step(step); }); }); return transform; } // src/helpers/createNodeFromContent.ts import { DOMParser, Fragment, Node as ProseMirrorNode, Schema } from "@tiptap/pm/model"; // src/utilities/elementFromString.ts var removeWhitespaces = (node) => { const children = node.childNodes; for (let i = children.length - 1; i >= 0; i -= 1) { const child = children[i]; if (child.nodeType === 3 && child.nodeValue && /^(\n\s\s|\n)$/.test(child.nodeValue)) { node.removeChild(child); } else if (child.nodeType === 1) { removeWhitespaces(child); } } return node; }; function elementFromString(value) { if (typeof window === "undefined") { throw new Error("[tiptap error]: there is no window object available, so this function cannot be used"); } const wrappedValue = `<body>${value}</body>`; const html = new window.DOMParser().parseFromString(wrappedValue, "text/html").body; return removeWhitespaces(html); } // src/helpers/createNodeFromContent.ts function createNodeFromContent(content, schema, options) { if (content instanceof ProseMirrorNode || content instanceof Fragment) { return content; } options = { slice: true, parseOptions: {}, ...options }; const isJSONContent = typeof content === "object" && content !== null; const isTextContent = typeof content === "string"; if (isJSONContent) { try { const isArrayContent = Array.isArray(content) && content.length > 0; if (isArrayContent) { return Fragment.fromArray(content.map((item) => schema.nodeFromJSON(item))); } const node = schema.nodeFromJSON(content); if (options.errorOnInvalidContent) { node.check(); } return node; } catch (error) { if (options.errorOnInvalidContent) { throw new Error("[tiptap error]: Invalid JSON content", { cause: error }); } console.warn("[tiptap warn]: Invalid content.", "Passed value:", content, "Error:", error); return createNodeFromContent("", schema, options); } } if (isTextContent) { if (options.errorOnInvalidContent) { let hasInvalidContent = false; let invalidContent = ""; const contentCheckSchema = new Schema({ topNode: schema.spec.topNode, marks: schema.spec.marks, // Prosemirror's schemas are executed such that: the last to execute, matches last // This means that we can add a catch-all node at the end of the schema to catch any content that we don't know how to handle nodes: schema.spec.nodes.append({ __tiptap__private__unknown__catch__all__node: { content: "inline*", group: "block", parseDOM: [ { tag: "*", getAttrs: (e) => { hasInvalidContent = true; invalidContent = typeof e === "string" ? e : e.outerHTML; return null; } } ] } }) }); if (options.slice) { DOMParser.fromSchema(contentCheckSchema).parseSlice(elementFromString(content), options.parseOptions); } else { DOMParser.fromSchema(contentCheckSchema).parse(elementFromString(content), options.parseOptions); } if (options.errorOnInvalidContent && hasInvalidContent) { throw new Error("[tiptap error]: Invalid HTML content", { cause: new Error(`Invalid element found: ${invalidContent}`) }); } } const parser = DOMParser.fromSchema(schema); if (options.slice) { return parser.parseSlice(elementFromString(content), options.parseOptions).content; } return parser.parse(elementFromString(content), options.parseOptions); } return createNodeFromContent("", schema, options); } // src/helpers/createDocument.ts function createDocument(content, schema, parseOptions = {}, options = {}) { return createNodeFromContent(content, schema, { slice: false, parseOptions, errorOnInvalidContent: options.errorOnInvalidContent }); } // src/helpers/defaultBlockAt.ts function defaultBlockAt(match) { for (let i = 0; i < match.edgeCount; i += 1) { const { type } = match.edge(i); if (type.isTextblock && !type.hasRequiredAttrs()) { return type; } } return null; } // src/helpers/findChildren.ts function findChildren(node, predicate) { const nodesWithPos = []; node.descendants((child, pos) => { if (predicate(child)) { nodesWithPos.push({ node: child, pos }); } }); return nodesWithPos; } // src/helpers/findChildrenInRange.ts function findChildrenInRange(node, range, predicate) { const nodesWithPos = []; node.nodesBetween(range.from, range.to, (child, pos) => { if (predicate(child)) { nodesWithPos.push({ node: child, pos }); } }); return nodesWithPos; } // src/helpers/findParentNodeClosestToPos.ts function findParentNodeClosestToPos($pos, predicate) { for (let i = $pos.depth; i > 0; i -= 1) { const node = $pos.node(i); if (predicate(node)) { return { pos: i > 0 ? $pos.before(i) : 0, start: $pos.start(i), depth: i, node }; } } } // src/helpers/findParentNode.ts function findParentNode(predicate) { return (selection) => findParentNodeClosestToPos(selection.$from, predicate); } // src/helpers/getExtensionField.ts function getExtensionField(extension, field, context) { if (extension.config[field] === void 0 && extension.parent) { return getExtensionField(extension.parent, field, context); } if (typeof extension.config[field] === "function") { const value = extension.config[field].bind({ ...context, parent: extension.parent ? getExtensionField(extension.parent, field, context) : null }); return value; } return extension.config[field]; } // src/helpers/flattenExtensions.ts function flattenExtensions(extensions) { return extensions.map((extension) => { const context = { name: extension.name, options: extension.options, storage: extension.storage }; const addExtensions = getExtensionField(extension, "addExtensions", context); if (addExtensions) { return [extension, ...flattenExtensions(addExtensions())]; } return extension; }).flat(10); } // src/helpers/generateHTML.ts import { Node } from "@tiptap/pm/model"; // src/helpers/getHTMLFromFragment.ts import { DOMSerializer } from "@tiptap/pm/model"; function getHTMLFromFragment(fragment, schema) { const documentFragment = DOMSerializer.fromSchema(schema).serializeFragment(fragment); const temporaryDocument = document.implementation.createHTMLDocument(); const container = temporaryDocument.createElement("div"); container.appendChild(documentFragment); return container.innerHTML; } // src/helpers/getSchemaByResolvedExtensions.ts import { Schema as Schema2 } from "@tiptap/pm/model"; // src/utilities/isFunction.ts function isFunction(value) { return typeof value === "function"; } // src/utilities/callOrReturn.ts function callOrReturn(value, context = void 0, ...props) { if (isFunction(value)) { if (context) { return value.bind(context)(...props); } return value(...props); } return value; } // src/utilities/isEmptyObject.ts function isEmptyObject(value = {}) { return Object.keys(value).length === 0 && value.constructor === Object; } // src/helpers/splitExtensions.ts function splitExtensions(extensions) { const baseExtensions = extensions.filter((extension) => extension.type === "extension"); const nodeExtensions = extensions.filter((extension) => extension.type === "node"); const markExtensions = extensions.filter((extension) => extension.type === "mark"); return { baseExtensions, nodeExtensions, markExtensions }; } // src/helpers/getAttributesFromExtensions.ts function getAttributesFromExtensions(extensions) { const extensionAttributes = []; const { nodeExtensions, markExtensions } = splitExtensions(extensions); const nodeAndMarkExtensions = [...nodeExtensions, ...markExtensions]; const defaultAttribute = { default: null, validate: void 0, rendered: true, renderHTML: null, parseHTML: null, keepOnSplit: true, isRequired: false }; extensions.forEach((extension) => { const context = { name: extension.name, options: extension.options, storage: extension.storage, extensions: nodeAndMarkExtensions }; const addGlobalAttributes = getExtensionField( extension, "addGlobalAttributes", context ); if (!addGlobalAttributes) { return; } const globalAttributes = addGlobalAttributes(); globalAttributes.forEach((globalAttribute) => { globalAttribute.types.forEach((type) => { Object.entries(globalAttribute.attributes).forEach(([name, attribute]) => { extensionAttributes.push({ type, name, attribute: { ...defaultAttribute, ...attribute } }); }); }); }); }); nodeAndMarkExtensions.forEach((extension) => { const context = { name: extension.name, options: extension.options, storage: extension.storage }; const addAttributes = getExtensionField( extension, "addAttributes", context ); if (!addAttributes) { return; } const attributes = addAttributes(); Object.entries(attributes).forEach(([name, attribute]) => { const mergedAttr = { ...defaultAttribute, ...attribute }; if (typeof (mergedAttr == null ? void 0 : mergedAttr.default) === "function") { mergedAttr.default = mergedAttr.default(); } if ((mergedAttr == null ? void 0 : mergedAttr.isRequired) && (mergedAttr == null ? void 0 : mergedAttr.default) === void 0) { delete mergedAttr.default; } extensionAttributes.push({ type: extension.name, name, attribute: mergedAttr }); }); }); return extensionAttributes; } // src/utilities/mergeAttributes.ts function mergeAttributes(...objects) { return objects.filter((item) => !!item).reduce((items, item) => { const mergedAttributes = { ...items }; Object.entries(item).forEach(([key, value]) => { const exists = mergedAttributes[key]; if (!exists) { mergedAttributes[key] = value; return; } if (key === "class") { const valueClasses = value ? String(value).split(" ") : []; const existingClasses = mergedAttributes[key] ? mergedAttributes[key].split(" ") : []; const insertClasses = valueClasses.filter((valueClass) => !existingClasses.includes(valueClass)); mergedAttributes[key] = [...existingClasses, ...insertClasses].join(" "); } else if (key === "style") { const newStyles = value ? value.split(";").map((style2) => style2.trim()).filter(Boolean) : []; const existingStyles = mergedAttributes[key] ? mergedAttributes[key].split(";").map((style2) => style2.trim()).filter(Boolean) : []; const styleMap = /* @__PURE__ */ new Map(); existingStyles.forEach((style2) => { const [property, val] = style2.split(":").map((part) => part.trim()); styleMap.set(property, val); }); newStyles.forEach((style2) => { const [property, val] = style2.split(":").map((part) => part.trim()); styleMap.set(property, val); }); mergedAttributes[key] = Array.from(styleMap.entries()).map(([property, val]) => `${property}: ${val}`).join("; "); } else { mergedAttributes[key] = value; } }); return mergedAttributes; }, {}); } // src/helpers/getRenderedAttributes.ts function getRenderedAttributes(nodeOrMark, extensionAttributes) { return extensionAttributes.filter((attribute) => attribute.type === nodeOrMark.type.name).filter((item) => item.attribute.rendered).map((item) => { if (!item.attribute.renderHTML) { return { [item.name]: nodeOrMark.attrs[item.name] }; } return item.attribute.renderHTML(nodeOrMark.attrs) || {}; }).reduce((attributes, attribute) => mergeAttributes(attributes, attribute), {}); } // src/utilities/fromString.ts function fromString(value) { if (typeof value !== "string") { return value; } if (value.match(/^[+-]?(?:\d*\.)?\d+$/)) { return Number(value); } if (value === "true") { return true; } if (value === "false") { return false; } return value; } // src/helpers/injectExtensionAttributesToParseRule.ts function injectExtensionAttributesToParseRule(parseRule, extensionAttributes) { if ("style" in parseRule) { return parseRule; } return { ...parseRule, getAttrs: (node) => { const oldAttributes = parseRule.getAttrs ? parseRule.getAttrs(node) : parseRule.attrs; if (oldAttributes === false) { return false; } const newAttributes = extensionAttributes.reduce((items, item) => { const value = item.attribute.parseHTML ? item.attribute.parseHTML(node) : fromString(node.getAttribute(item.name)); if (value === null || value === void 0) { return items; } return { ...items, [item.name]: value }; }, {}); return { ...oldAttributes, ...newAttributes }; } }; } // src/helpers/getSchemaByResolvedExtensions.ts function cleanUpSchemaItem(data) { return Object.fromEntries( // @ts-ignore Object.entries(data).filter(([key, value]) => { if (key === "attrs" && isEmptyObject(value)) { return false; } return value !== null && value !== void 0; }) ); } function getSchemaByResolvedExtensions(extensions, editor) { var _a; const allAttributes = getAttributesFromExtensions(extensions); const { nodeExtensions, markExtensions } = splitExtensions(extensions); const topNode = (_a = nodeExtensions.find((extension) => getExtensionField(extension, "topNode"))) == null ? void 0 : _a.name; const nodes = Object.fromEntries( nodeExtensions.map((extension) => { const extensionAttributes = allAttributes.filter((attribute) => attribute.type === extension.name); const context = { name: extension.name, options: extension.options, storage: extension.storage, editor }; const extraNodeFields = extensions.reduce((fields, e) => { const extendNodeSchema = getExtensionField(e, "extendNodeSchema", context); return { ...fields, ...extendNodeSchema ? extendNodeSchema(extension) : {} }; }, {}); const schema = cleanUpSchemaItem({ ...extraNodeFields, content: callOrReturn(getExtensionField(extension, "content", context)), marks: callOrReturn(getExtensionField(extension, "marks", context)), group: callOrReturn(getExtensionField(extension, "group", context)), inline: callOrReturn(getExtensionField(extension, "inline", context)), atom: callOrReturn(getExtensionField(extension, "atom", context)), selectable: callOrReturn(getExtensionField(extension, "selectable", context)), draggable: callOrReturn(getExtensionField(extension, "draggable", context)), code: callOrReturn(getExtensionField(extension, "code", context)), whitespace: callOrReturn(getExtensionField(extension, "whitespace", context)), linebreakReplacement: callOrReturn( getExtensionField(extension, "linebreakReplacement", context) ), defining: callOrReturn(getExtensionField(extension, "defining", context)), isolating: callOrReturn(getExtensionField(extension, "isolating", context)), attrs: Object.fromEntries( extensionAttributes.map((extensionAttribute) => { var _a2, _b; return [ extensionAttribute.name, { default: (_a2 = extensionAttribute == null ? void 0 : extensionAttribute.attribute) == null ? void 0 : _a2.default, validate: (_b = extensionAttribute == null ? void 0 : extensionAttribute.attribute) == null ? void 0 : _b.validate } ]; }) ) }); const parseHTML = callOrReturn(getExtensionField(extension, "parseHTML", context)); if (parseHTML) { schema.parseDOM = parseHTML.map( (parseRule) => injectExtensionAttributesToParseRule(parseRule, extensionAttributes) ); } const renderHTML = getExtensionField(extension, "renderHTML", context); if (renderHTML) { schema.toDOM = (node) => renderHTML({ node, HTMLAttributes: getRenderedAttributes(node, extensionAttributes) }); } const renderText = getExtensionField(extension, "renderText", context); if (renderText) { schema.toText = renderText; } return [extension.name, schema]; }) ); const marks = Object.fromEntries( markExtensions.map((extension) => { const extensionAttributes = allAttributes.filter((attribute) => attribute.type === extension.name); const context = { name: extension.name, options: extension.options, storage: extension.storage, editor }; const extraMarkFields = extensions.reduce((fields, e) => { const extendMarkSchema = getExtensionField(e, "extendMarkSchema", context); return { ...fields, ...extendMarkSchema ? extendMarkSchema(extension) : {} }; }, {}); const schema = cleanUpSchemaItem({ ...extraMarkFields, inclusive: callOrReturn(getExtensionField(extension, "inclusive", context)), excludes: callOrReturn(getExtensionField(extension, "excludes", context)), group: callOrReturn(getExtensionField(extension, "group", context)), spanning: callOrReturn(getExtensionField(extension, "spanning", context)), code: callOrReturn(getExtensionField(extension, "code", context)), attrs: Object.fromEntries( extensionAttributes.map((extensionAttribute) => { var _a2, _b; return [ extensionAttribute.name, { default: (_a2 = extensionAttribute == null ? void 0 : extensionAttribute.attribute) == null ? void 0 : _a2.default, validate: (_b = extensionAttribute == null ? void 0 : extensionAttribute.attribute) == null ? void 0 : _b.validate } ]; }) ) }); const parseHTML = callOrReturn(getExtensionField(extension, "parseHTML", context)); if (parseHTML) { schema.parseDOM = parseHTML.map( (parseRule) => injectExtensionAttributesToParseRule(parseRule, extensionAttributes) ); } const renderHTML = getExtensionField(extension, "renderHTML", context); if (renderHTML) { schema.toDOM = (mark) => renderHTML({ mark, HTMLAttributes: getRenderedAttributes(mark, extensionAttributes) }); } return [extension.name, schema]; }) ); return new Schema2({ topNode, nodes, marks }); } // src/utilities/findDuplicates.ts function findDuplicates(items) { const filtered = items.filter((el, index) => items.indexOf(el) !== index); return Array.from(new Set(filtered)); } // src/helpers/sortExtensions.ts function sortExtensions(extensions) { const defaultPriority = 100; return extensions.sort((a, b) => { const priorityA = getExtensionField(a, "priority") || defaultPriority; const priorityB = getExtensionField(b, "priority") || defaultPriority; if (priorityA > priorityB) { return -1; } if (priorityA < priorityB) { return 1; } return 0; }); } // src/helpers/resolveExtensions.ts function resolveExtensions(extensions) { const resolvedExtensions = sortExtensions(flattenExtensions(extensions)); const duplicatedNames = findDuplicates(resolvedExtensions.map((extension) => extension.name)); if (duplicatedNames.length) { console.warn( `[tiptap warn]: Duplicate extension names found: [${duplicatedNames.map((item) => `'${item}'`).join(", ")}]. This can lead to issues.` ); } return resolvedExtensions; } // src/helpers/getSchema.ts function getSchema(extensions, editor) { const resolvedExtensions = resolveExtensions(extensions); return getSchemaByResolvedExtensions(resolvedExtensions, editor); } // src/helpers/generateHTML.ts function generateHTML(doc, extensions) { const schema = getSchema(extensions); const contentNode = Node.fromJSON(schema, doc); return getHTMLFromFragment(contentNode.content, schema); } // src/helpers/generateJSON.ts import { DOMParser as DOMParser2 } from "@tiptap/pm/model"; function generateJSON(html, extensions) { const schema = getSchema(extensions); const dom = elementFromString(html); return DOMParser2.fromSchema(schema).parse(dom).toJSON(); } // src/helpers/generateText.ts import { Node as Node2 } from "@tiptap/pm/model"; // src/helpers/getTextBetween.ts function getTextBetween(startNode, range, options) { const { from, to } = range; const { blockSeparator = "\n\n", textSerializers = {} } = options || {}; let text = ""; startNode.nodesBetween(from, to, (node, pos, parent, index) => { var _a; if (node.isBlock && pos > from) { text += blockSeparator; } const textSerializer = textSerializers == null ? void 0 : textSerializers[node.type.name]; if (textSerializer) { if (parent) { text += textSerializer({ node, pos, parent, index, range }); } return false; } if (node.isText) { text += (_a = node == null ? void 0 : node.text) == null ? void 0 : _a.slice(Math.max(from, pos) - pos, to - pos); } }); return text; } // src/helpers/getText.ts function getText(node, options) { const range = { from: 0, to: node.content.size }; return getTextBetween(node, range, options); } // src/helpers/getTextSerializersFromSchema.ts function getTextSerializersFromSchema(schema) { return Object.fromEntries( Object.entries(schema.nodes).filter(([, node]) => node.spec.toText).map(([name, node]) => [name, node.spec.toText]) ); } // src/helpers/generateText.ts function generateText(doc, extensions, options) { const { blockSeparator = "\n\n", textSerializers = {} } = options || {}; const schema = getSchema(extensions); const contentNode = Node2.fromJSON(schema, doc); return getText(contentNode, { blockSeparator, textSerializers: { ...getTextSerializersFromSchema(schema), ...textSerializers } }); } // src/helpers/getMarkType.ts function getMarkType(nameOrType, schema) { if (typeof nameOrType === "string") { if (!schema.marks[nameOrType]) { throw Error(`There is no mark type named '${nameOrType}'. Maybe you forgot to add the extension?`); } return schema.marks[nameOrType]; } return nameOrType; } // src/helpers/getMarkAttributes.ts function getMarkAttributes(state, typeOrName) { const type = getMarkType(typeOrName, state.schema); const { from, to, empty } = state.selection; const marks = []; if (empty) { if (state.storedMarks) { marks.push(...state.storedMarks); } marks.push(...state.selection.$head.marks()); } else { state.doc.nodesBetween(from, to, (node) => { marks.push(...node.marks); }); } const mark = marks.find((markItem) => markItem.type.name === type.name); if (!mark) { return {}; } return { ...mark.attrs }; } // src/helpers/getNodeType.ts function getNodeType(nameOrType, schema) { if (typeof nameOrType === "string") { if (!schema.nodes[nameOrType]) { throw Error(`There is no node type named '${nameOrType}'. Maybe you forgot to add the extension?`); } return schema.nodes[nameOrType]; } return nameOrType; } // src/helpers/getNodeAttributes.ts function getNodeAttributes(state, typeOrName) { const type = getNodeType(typeOrName, state.schema); const { from, to } = state.selection; const nodes = []; state.doc.nodesBetween(from, to, (node2) => { nodes.push(node2); }); const node = nodes.reverse().find((nodeItem) => nodeItem.type.name === type.name); if (!node) { return {}; } return { ...node.attrs }; } // src/helpers/getSchemaTypeNameByName.ts function getSchemaTypeNameByName(name, schema) { if (schema.nodes[name]) { return "node"; } if (schema.marks[name]) { return "mark"; } return null; } // src/helpers/getAttributes.ts function getAttributes(state, typeOrName) { const schemaType = getSchemaTypeNameByName( typeof typeOrName === "string" ? typeOrName : typeOrName.name, state.schema ); if (schemaType === "node") { return getNodeAttributes(state, typeOrName); } if (schemaType === "mark") { return getMarkAttributes(state, typeOrName); } return {}; } // src/utilities/removeDuplicates.ts function removeDuplicates(array, by = JSON.stringify) { const seen = {}; return array.filter((item) => { const key = by(item); return Object.prototype.hasOwnProperty.call(seen, key) ? false : seen[key] = true; }); } // src/helpers/getChangedRanges.ts function simplifyChangedRanges(changes) { const uniqueChanges = removeDuplicates(changes); return uniqueChanges.length === 1 ? uniqueChanges : uniqueChanges.filter((change, index) => { const rest = uniqueChanges.filter((_, i) => i !== index); return !rest.some((otherChange) => { return change.oldRange.from >= otherChange.oldRange.from && change.oldRange.to <= otherChange.oldRange.to && change.newRange.from >= otherChange.newRange.from && change.newRange.to <= otherChange.newRange.to; }); }); } function getChangedRanges(transform) { const { mapping, steps } = transform; const changes = []; mapping.maps.forEach((stepMap, index) => { const ranges = []; if (!stepMap.ranges.length) { const { from, to } = steps[index]; if (from === void 0 || to === void 0) { return; } ranges.push({ from, to }); } else { stepMap.forEach((from, to) => { ranges.push({ from, to }); }); } ranges.forEach(({ from, to }) => { const newStart = mapping.slice(index).map(from, -1); const newEnd = mapping.slice(index).map(to); const oldStart = mapping.invert().map(newStart, -1); const oldEnd = mapping.invert().map(newEnd); changes.push({ oldRange: { from: oldStart, to: oldEnd }, newRange: { from: newStart, to: newEnd } }); }); }); return simplifyChangedRanges(changes); } // src/helpers/getDebugJSON.ts function getDebugJSON(node, startOffset = 0) { const isTopNode = node.type === node.type.schema.topNodeType; const increment = isTopNode ? 0 : 1; const from = startOffset; const to = from + node.nodeSize; const marks = node.marks.map((mark) => { const output2 = { type: mark.type.name }; if (Object.keys(mark.attrs).length) { output2.attrs = { ...mark.attrs }; } return output2; }); const attrs = { ...node.attrs }; const output = { type: node.type.name, from, to }; if (Object.keys(attrs).length) { output.attrs = attrs; } if (marks.length) { output.marks = marks; } if (node.content.childCount) { output.content = []; node.forEach((child, offset) => { var _a; (_a = output.content) == null ? void 0 : _a.push(getDebugJSON(child, startOffset + offset + increment)); }); } if (node.text) { output.text = node.text; } return output; } // src/utilities/isRegExp.ts function isRegExp(value) { return Object.prototype.toString.call(value) === "[object RegExp]"; } // src/utilities/objectIncludes.ts function objectIncludes(object1, object2, options = { strict: true }) { const keys = Object.keys(object2); if (!keys.length) { return true; } return keys.every((key) => { if (options.strict) { return object2[key] === object1[key]; } if (isRegExp(object2[key])) { return object2[key].test(object1[key]); } return object2[key] === object1[key]; }); } // src/helpers/getMarkRange.ts function findMarkInSet(marks, type, attributes = {}) { return marks.find((item) => { return item.type === type && objectIncludes( // Only check equality for the attributes that are provided Object.fromEntries(Object.keys(attributes).map((k) => [k, item.attrs[k]])), attributes ); }); } function isMarkInSet(marks, type, attributes = {}) { return !!findMarkInSet(marks, type, attributes); } function getMarkRange($pos, type, attributes) { var _a; if (!$pos || !type) { return; } let start = $pos.parent.childAfter($pos.parentOffset); if (!start.node || !start.node.marks.some((mark2) => mark2.type === type)) { start = $pos.parent.childBefore($pos.parentOffset); } if (!start.node || !start.node.marks.some((mark2) => mark2.type === type)) { return; } attributes = attributes || ((_a = start.node.marks[0]) == null ? void 0 : _a.attrs); const mark = findMarkInSet([...start.node.marks], type, attributes); if (!mark) { return; } let startIndex = start.index; let startPos = $pos.start() + start.offset; let endIndex = startIndex + 1; let endPos = startPos + start.node.nodeSize; while (startIndex > 0 && isMarkInSet([...$pos.parent.child(startIndex - 1).marks], type, attributes)) { startIndex -= 1; startPos -= $pos.parent.child(startIndex).nodeSize; } while (endIndex < $pos.parent.childCount && isMarkInSet([...$pos.parent.child(endIndex).marks], type, attributes)) { endPos += $pos.parent.child(endIndex).nodeSize; endIndex += 1; } return { from: startPos, to: endPos }; } // src/helpers/getMarksBetween.ts function getMarksBetween(from, to, doc) { const marks = []; if (from === to) { doc.resolve(from).marks().forEach((mark) => { const $pos = doc.resolve(from); const range = getMarkRange($pos, mark.type); if (!range) { return; } marks.push({ mark, ...range }); }); } else { doc.nodesBetween(from, to, (node, pos) => { if (!node || (node == null ? void 0 : node.nodeSize) === void 0) { return; } marks.push( ...node.marks.map((mark) => ({ from: pos, to: pos + node.nodeSize, mark })) ); }); } return marks; } // src/helpers/getNodeAtPosition.ts var getNodeAtPosition = (state, typeOrName, pos, maxDepth = 20) => { const $pos = state.doc.resolve(pos); let currentDepth = maxDepth; let node = null; while (currentDepth > 0 && node === null) { const currentNode = $pos.node(currentDepth); if ((currentNode == null ? void 0 : currentNode.type.name) === typeOrName) { node = currentNode; } else { currentDepth -= 1; } } return [node, currentDepth]; }; // src/helpers/getSchemaTypeByName.ts function getSchemaTypeByName(name, schema) { return schema.nodes[name] || schema.marks[name] || null; } // src/helpers/getSplittedAttributes.ts function getSplittedAttributes(extensionAttributes, typeName, attributes) { return Object.fromEntries( Object.entries(attributes).filter(([name]) => { const extensionAttribute = extensionAttributes.find((item) => { return item.type === typeName && item.name === name; }); if (!extensionAttribute) { return false; } return extensionAttribute.attribute.keepOnSplit; }) ); } // src/helpers/getTextContentFromNodes.ts var getTextContentFromNodes = ($from, maxMatch = 500) => { let textBefore = ""; const sliceEndPos = $from.parentOffset; $from.parent.nodesBetween(Math.max(0, sliceEndPos - maxMatch), sliceEndPos, (node, pos, parent, index) => { var _a, _b; const chunk = ((_b = (_a = node.type.spec).toText) == null ? void 0 : _b.call(_a, { node, pos, parent, index })) || node.textContent || "%leaf%"; textBefore += node.isAtom && !node.isText ? chunk : chunk.slice(0, Math.max(0, sliceEndPos - pos)); }); return textBefore; }; // src/helpers/isMarkActive.ts function isMarkActive(state, typeOrName, attributes = {}) { const { empty, ranges } = state.selection; const type = typeOrName ? getMarkType(typeOrName, state.schema) : null; if (empty) { return !!(state.storedMarks || state.selection.$from.marks()).filter((mark) => { if (!type) { return true; } return type.name === mark.type.name; }).find((mark) => objectIncludes(mark.attrs, attributes, { strict: false })); } let selectionRange = 0; const markRanges = []; ranges.forEach(({ $from, $to }) => { const from = $from.pos; const to = $to.pos; state.doc.nodesBetween(from, to, (node, pos) => { if (!node.isText && !node.marks.length) { return; } const relativeFrom = Math.max(from, pos); const relativeTo = Math.min(to, pos + node.nodeSize); const range2 = relativeTo - relativeFrom; selectionRange += range2; markRanges.push( ...node.marks.map((mark) => ({ mark, from: relativeFrom, to: relativeTo })) ); }); }); if (selectionRange === 0) { return false; } const matchedRange = markRanges.filter((markRange) => { if (!type) { return true; } return type.name === markRange.mark.type.name; }).filter((markRange) => objectIncludes(markRange.mark.attrs, attributes, { strict: false })).reduce((sum, markRange) => sum + markRange.to - markRange.from, 0); const excludedRange = markRanges.filter((markRange) => { if (!type) { return true; } return markRange.mark.type !== type && markRange.mark.type.excludes(type); }).reduce((sum, markRange) => sum + markRange.to - markRange.from, 0); const range = matchedRange > 0 ? matchedRange + excludedRange : matchedRange; return range >= selectionRange; } // src/helpers/isNodeActive.ts function isNodeActive(state, typeOrName, attributes = {}) { const { from, to, empty } = state.selection; const type = typeOrName ? getNodeType(typeOrName, state.schema) : null; const nodeRanges = []; state.doc.nodesBetween(from, to, (node, pos) => { if (node.isText) { return; } const relativeFrom = Math.max(from, pos); const relativeTo = Math.min(to, pos + node.nodeSize); nodeRanges.push({ node, from: relativeFrom, to: relativeTo }); }); const selectionRange = to - from; const matchedNodeRanges = nodeRanges.filter((nodeRange) => { if (!type) { return true; } return type.name === nodeRange.node.type.name; }).filter((nodeRange) => objectIncludes(nodeRange.node.attrs, attributes, { strict: false })); if (empty) { return !!matchedNodeRanges.length; } const range = matchedNodeRanges.reduce((sum, nodeRange) => sum + nodeRange.to - nodeRange.from, 0); return range >= selectionRange; } // src/helpers/isActive.ts function isActive(state, name, attributes = {}) { if (!name) { return isNodeActive(state, null, attributes) || isMarkActive(state, null, attributes); } const schemaType = getSchemaTypeNameByName(name, state.schema); if (schemaType === "node") { return isNodeActive(state, name, attributes); } if (schemaType === "mark") { return isMarkActive(state, name, attributes); } return false; } // src/helpers/isAtEndOfNode.ts var isAtEndOfNode = (state, nodeType) => { const { $from, $to, $anchor } = state.selection; if (nodeType) { const parentNode = findParentNode((node) => node.type.name === nodeType)(state.selection); if (!parentNode) { return false; } const $parentPos = state.doc.resolve(parentNode.pos + 1); if ($anchor.pos + 1 === $parentPos.end()) { return true; } return false; } if ($to.parentOffset < $to.parent.nodeSize - 2 || $from.pos !== $to.pos) { return false; } return true; }; // src/helpers/isAtStartOfNode.ts var isAtStartOfNode = (state) => { const { $from, $to } = state.selection; if ($from.parentOffset > 0 || $from.pos !== $to.pos) { return false; } return true; }; // src/helpers/isExtensionRulesEnabled.ts function isExtensionRulesEnabled(extension, enabled) { if (Array.isArray(enabled)) { return enabled.some((enabledExtension) => { const name = typeof enabledExtension === "string" ? enabledExtension : enabledExtension.name; return name === extension.name; }); } return enabled; } // src/helpers/isList.ts function isList(name, extensions) { const { nodeExtensions } = splitExtensions(extensions); const extension = nodeExtensions.find((item) => item.name === name); if (!extension) { return false; } const context = { name: extension.name, options: extension.options, storage: extension.storage }; const group = callOrReturn(getExtensionField(extension, "group", context)); if (typeof group !== "string") { return false; } return group.split(" ").includes("list"); } // src/helpers/isNodeEmpty.ts function isNodeEmpty(node, { checkChildren = true, ignoreWhitespace = false } = {}) { var _a; if (ignoreWhitespace) { if (node.type.name === "hardBreak") { return true; } if (node.isText) { return /^\s*$/m.test((_a = node.text) != null ? _a : ""); } } if (node.isText) { return !node.text; } if (node.isAtom || node.isLeaf) { return false; } if (node.content.childCount === 0) { return true; } if (checkChildren) { let isContentEmpty = true; node.content.forEach((childNode) => { if (isContentEmpty === false) { return; } if (!isNodeEmpty(childNode, { ignoreWhitespace, checkChildren })) { isContentEmpty = false; } }); return isContentEmpty; } return false; } // src/helpers/isNodeSelection.ts import { NodeSelection } from "@tiptap/pm/state"; function isNodeSelection(value) { return value instanceof NodeSelection; } // src/helpers/isTextSelection.ts import { TextSelection } from "@tiptap/pm/state"; function isTextSelection(value) { return value instanceof TextSelection; } // src/utilities/minMax.ts function minMax(value = 0, min = 0, max = 0) { return Math.min(Math.max(value, min), max); } // src/helpers/posToDOMRect.ts function posToDOMRect(view, from, to) { const minPos = 0; const maxPos = view.state.doc.content.size; const resolvedFrom = minMax(from, minPos, maxPos); const resolvedEnd = minMax(to, minPos, maxPos); const start = view.coordsAtPos(resolvedFrom); const end = view.coordsAtPos(resolvedEnd, -1); const top = Math.min(start.top, end.top); const bottom = Math.max(start.bottom, end.bottom); const left = Math.min(start.left, end.left); const right = Math.max(start.right, end.right); const width = right - left; const height = bottom - top; const x = left; const y = top; const data = { top, bottom, left, right, width, height, x, y }; return { ...data, toJSON: () => data }; } // src/helpers/resolveFocusPosition.ts import { Selection, TextSelection as TextSelection2 } from "@tiptap/pm/state"; function resolveFocusPosition(doc, position = null) { if (!position) { return null; } const selectionAtStart = Selection.atStart(doc); const selectionAtEnd = Selection.atEnd(doc); if (position === "start" || position === true) { return selectionAtStart; } if (position === "end") { return selectionAtEnd; } const minPos = selectionAtStart.from; const maxPos = selectionAtEnd.to; if (position === "all") { return TextSelection2.create(doc, minMax(0, minPos, maxPos), minMax(doc.content.size, minPos, maxPos)); } return TextSelection2.create(doc, minMax(position, minPos, maxPos), minMax(position, minPos, maxPos)); } // src/helpers/rewriteUnknownContent.ts function rewriteUnknownContentInner({ json, validMarks, validNodes, options, rewrittenContent = [] }) { if (json.marks && Array.isArray(json.marks)) { json.marks = json.marks.filter((mark) => { const name = typeof mark === "string" ? mark : mark.type; if (validMarks.has(name)) { return true; } rewrittenContent.push({ original: JSON.parse(JSON.stringify(mark)), unsupported: name }); return false; }); } if (json.content && Array.isArray(json.content)) { json.content = json.content.map( (value) => rewriteUnknownContentInner({ json: value, validMarks, validNodes, options, rewrittenContent }).json ).filter((a) => a !== null && a !== void 0); } if (json.type && !validNodes.has(json.type)) { rewrittenContent.push({ original: JSON.parse(JSON.stringify(json)), unsupported: json.type }); if (json.content && Array.isArray(json.content) && (options == null ? void 0 : options.fallbackToParagraph) !== false) { json.type = "paragraph"; return { json, rewrittenContent }; } return { json: null, rewrittenContent }; } return { json, rewrittenContent }; } function rewriteUnknownContent(json, schema, options) { return rewriteUnknownContentInner({ json, validNodes: new Set(Object.keys(schema.nodes)), validMarks: new Set(Object.keys(schema.marks)), options }); } // src/helpers/selectionToInsertionEnd.ts import { Selection as Selection2 } from "@tiptap/pm/state"; import { ReplaceAroundStep, ReplaceStep } from "@tiptap/pm/transform"; function selectionToInsertionEnd(tr, startLen, bias) { const last = tr.steps.length - 1; if (last < startLen) { return; } const step = tr.steps[last]; if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep)) { return; } const map = tr.mapping.maps[last]; let end = 0; map.forEach((_from, _to, _newFrom, newTo) => { if (end === 0) { end = newTo; } }); tr.setSelection(Selection2.near(tr.doc.resolve(end), bias)); } // src/InputRule.ts import { Fragment as Fragment2 } from "@tiptap/pm/model"; import { Plugin } from "@tiptap/pm/state"; var InputRule = class { constructor(config) { this.find = config.find; this.handler = config.handler; } }; var inputRuleMatcherHandler = (text, find) => { if (isRegExp(find)) { return find.exec(text); } const inputRuleMatch = find(text); if (!inputRuleMatch) { return null; } const result = [inputRuleMatch.text]; result.index = inputRuleMatch.index; result.input = text; result.data = inputRuleMatch.data; if (inputRuleMatch.replaceWith) { if (!inputRuleMatch.text.includes(inputRuleMatch.replaceWith)) { console.warn('[tiptap warn]: "inputRuleMatch.replaceWith" must be part of "inputRuleMatch.text".'); } result.push(inputRuleMatch.replaceWith); } return result; }; function run(config) { var _a; const { editor, from, to, text, rules, plugin } = config; const { view } = editor; if (view.composing) { return false; } const $from = view.state.doc.resolve(from); if ( // check for code node $from.parent.type.spec.code || // check for code mark !!((_a = $from.nodeBefore || $from.nodeAfter) == null ? void 0 : _a.marks.find((mark) => mark.type.spec.code)) ) { return false; } let matched = false; const textBefore = getTextContentFromNodes($from) + text; rules.forEach((rule) => { if (matched) { return; } const match = inputRuleMatcherHandler(textBefore, rule.find); if (!match) { return; } const tr = view.state.tr; const state = createChainableState({ state: view.state, transaction: tr }); const range = { from: from - (match[0].length - text.length), to }; const { commands, chain, can } = new CommandManager({ editor, state }); const handler = rule.handler({ state, range, match, commands, chain, can }); if (handler === null || !tr.steps.length) { return; } tr.setMeta(plugin, { transform: tr, from, to,