UNPKG

@tiptap/core

Version:

headless rich text editor

1,582 lines (1,497 loc) 209 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/commands/index.ts var commands_exports = {}; __export(commands_exports, { blur: () => blur, clearContent: () => clearContent, clearNodes: () => clearNodes, command: () => command, createParagraphNear: () => createParagraphNear, cut: () => cut, deleteCurrentNode: () => deleteCurrentNode, deleteNode: () => deleteNode, deleteRange: () => deleteRange, deleteSelection: () => deleteSelection, enter: () => enter, exitCode: () => exitCode, extendMarkRange: () => extendMarkRange, first: () => first, focus: () => focus, forEach: () => forEach, insertContent: () => insertContent, insertContentAt: () => insertContentAt, joinBackward: () => joinBackward, joinDown: () => joinDown, joinForward: () => joinForward, joinItemBackward: () => joinItemBackward, joinItemForward: () => joinItemForward, joinTextblockBackward: () => joinTextblockBackward, joinTextblockForward: () => joinTextblockForward, joinUp: () => joinUp, keyboardShortcut: () => keyboardShortcut, lift: () => lift, liftEmptyBlock: () => liftEmptyBlock, liftListItem: () => liftListItem, newlineInCode: () => newlineInCode, resetAttributes: () => resetAttributes, scrollIntoView: () => scrollIntoView, selectAll: () => selectAll, selectNodeBackward: () => selectNodeBackward, selectNodeForward: () => selectNodeForward, selectParentNode: () => selectParentNode, selectTextblockEnd: () => selectTextblockEnd, selectTextblockStart: () => selectTextblockStart, setContent: () => setContent, setMark: () => setMark, setMeta: () => setMeta, setNode: () => setNode, setNodeSelection: () => setNodeSelection, setTextSelection: () => setTextSelection, sinkListItem: () => sinkListItem, splitBlock: () => splitBlock, splitListItem: () => splitListItem, toggleList: () => toggleList, toggleMark: () => toggleMark, toggleNode: () => toggleNode, toggleWrap: () => toggleWrap, undoInputRule: () => undoInputRule, unsetAllMarks: () => unsetAllMarks, unsetMark: () => unsetMark, updateAttributes: () => updateAttributes, wrapIn: () => wrapIn, wrapInList: () => wrapInList }); // src/commands/blur.ts var blur = () => ({ editor, view }) => { requestAnimationFrame(() => { var _a; if (!editor.isDestroyed) { ; view.dom.blur(); (_a = window == null ? void 0 : window.getSelection()) == null ? void 0 : _a.removeAllRanges(); } }); return true; }; // src/commands/clearContent.ts var clearContent = (emitUpdate = true) => ({ commands }) => { return commands.setContent("", { emitUpdate }); }; // src/commands/clearNodes.ts import { liftTarget } from "@tiptap/pm/transform"; var clearNodes = () => ({ state, tr, dispatch }) => { const { selection } = tr; const { ranges } = selection; if (!dispatch) { return true; } ranges.forEach(({ $from, $to }) => { state.doc.nodesBetween($from.pos, $to.pos, (node, pos) => { if (node.type.isText) { return; } const { doc, mapping } = tr; const $mappedFrom = doc.resolve(mapping.map(pos)); const $mappedTo = doc.resolve(mapping.map(pos + node.nodeSize)); const nodeRange = $mappedFrom.blockRange($mappedTo); if (!nodeRange) { return; } const targetLiftDepth = liftTarget(nodeRange); if (node.type.isTextblock) { const { defaultType } = $mappedFrom.parent.contentMatchAt($mappedFrom.index()); tr.setNodeMarkup(nodeRange.start, defaultType); } if (targetLiftDepth || targetLiftDepth === 0) { tr.lift(nodeRange, targetLiftDepth); } }); }); return true; }; // src/commands/command.ts var command = (fn) => (props) => { return fn(props); }; // src/commands/createParagraphNear.ts import { createParagraphNear as originalCreateParagraphNear } from "@tiptap/pm/commands"; var createParagraphNear = () => ({ state, dispatch }) => { return originalCreateParagraphNear(state, dispatch); }; // src/commands/cut.ts import { TextSelection } from "@tiptap/pm/state"; var cut = (originRange, targetPos) => ({ editor, tr }) => { const { state } = editor; const contentSlice = state.doc.slice(originRange.from, originRange.to); tr.deleteRange(originRange.from, originRange.to); const newPos = tr.mapping.map(targetPos); tr.insert(newPos, contentSlice.content); tr.setSelection(new TextSelection(tr.doc.resolve(Math.max(newPos - 1, 0)))); return true; }; // src/commands/deleteCurrentNode.ts var deleteCurrentNode = () => ({ tr, dispatch }) => { const { selection } = tr; const currentNode = selection.$anchor.node(); if (currentNode.content.size > 0) { return false; } const $pos = tr.selection.$anchor; for (let depth = $pos.depth; depth > 0; depth -= 1) { const node = $pos.node(depth); if (node.type === currentNode.type) { if (dispatch) { const from = $pos.before(depth); const to = $pos.after(depth); tr.delete(from, to).scrollIntoView(); } return true; } } return false; }; // 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/commands/deleteNode.ts var deleteNode = (typeOrName) => ({ tr, state, dispatch }) => { const type = getNodeType(typeOrName, state.schema); const $pos = tr.selection.$anchor; for (let depth = $pos.depth; depth > 0; depth -= 1) { const node = $pos.node(depth); if (node.type === type) { if (dispatch) { const from = $pos.before(depth); const to = $pos.after(depth); tr.delete(from, to).scrollIntoView(); } return true; } } return false; }; // src/commands/deleteRange.ts var deleteRange = (range) => ({ tr, dispatch }) => { const { from, to } = range; if (dispatch) { tr.delete(from, to); } return true; }; // src/commands/deleteSelection.ts import { deleteSelection as originalDeleteSelection } from "@tiptap/pm/commands"; var deleteSelection = () => ({ state, dispatch }) => { return originalDeleteSelection(state, dispatch); }; // src/commands/enter.ts var enter = () => ({ commands }) => { return commands.keyboardShortcut("Enter"); }; // src/commands/exitCode.ts import { exitCode as originalExitCode } from "@tiptap/pm/commands"; var exitCode = () => ({ state, dispatch }) => { return originalExitCode(state, dispatch); }; // src/commands/extendMarkRange.ts import { TextSelection as TextSelection2 } from "@tiptap/pm/state"; // 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/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/commands/extendMarkRange.ts var extendMarkRange = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => { const type = getMarkType(typeOrName, state.schema); const { doc, selection } = tr; const { $from, from, to } = selection; if (dispatch) { const range = getMarkRange($from, type, attributes); if (range && range.from <= from && range.to >= to) { const newSelection = TextSelection2.create(doc, range.from, range.to); tr.setSelection(newSelection); } } return true; }; // src/commands/first.ts var first = (commands) => (props) => { const items = typeof commands === "function" ? commands(props) : commands; for (let i = 0; i < items.length; i += 1) { if (items[i](props)) { return true; } } return false; }; // src/helpers/isTextSelection.ts import { TextSelection as TextSelection3 } from "@tiptap/pm/state"; function isTextSelection(value) { return value instanceof TextSelection3; } // src/helpers/resolveFocusPosition.ts import { Selection, TextSelection as TextSelection4 } from "@tiptap/pm/state"; // src/utilities/minMax.ts function minMax(value = 0, min = 0, max = 0) { return Math.min(Math.max(value, min), max); } // src/helpers/resolveFocusPosition.ts 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 TextSelection4.create(doc, minMax(0, minPos, maxPos), minMax(doc.content.size, minPos, maxPos)); } return TextSelection4.create(doc, minMax(position, minPos, maxPos), minMax(position, minPos, maxPos)); } // src/utilities/isAndroid.ts function isAndroid() { return navigator.platform === "Android" || /android/i.test(navigator.userAgent); } // src/utilities/isiOS.ts function isiOS() { return ["iPad Simulator", "iPhone Simulator", "iPod Simulator", "iPad", "iPhone", "iPod"].includes(navigator.platform) || // iPad on iOS 13 detection navigator.userAgent.includes("Mac") && "ontouchend" in document; } // src/commands/focus.ts var focus = (position = null, options = {}) => ({ editor, view, tr, dispatch }) => { options = { scrollIntoView: true, ...options }; const delayedFocus = () => { if (isiOS() || isAndroid()) { ; view.dom.focus(); } requestAnimationFrame(() => { if (!editor.isDestroyed) { view.focus(); if (options == null ? void 0 : options.scrollIntoView) { editor.commands.scrollIntoView(); } } }); }; if (view.hasFocus() && position === null || position === false) { return true; } if (dispatch && position === null && !isTextSelection(editor.state.selection)) { delayedFocus(); return true; } const selection = resolveFocusPosition(tr.doc, position) || editor.state.selection; const isSameSelection = editor.state.selection.eq(selection); if (dispatch) { if (!isSameSelection) { tr.setSelection(selection); } if (isSameSelection && tr.storedMarks) { tr.setStoredMarks(tr.storedMarks); } delayedFocus(); } return true; }; // src/commands/forEach.ts var forEach = (items, fn) => (props) => { return items.every((item, index) => fn(item, { ...props, index })); }; // src/commands/insertContent.ts var insertContent = (value, options) => ({ tr, commands }) => { return commands.insertContentAt({ from: tr.selection.from, to: tr.selection.to }, value, options); }; // src/commands/insertContentAt.ts import { Fragment as Fragment2 } from "@tiptap/pm/model"; // 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/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/commands/insertContentAt.ts var isFragment = (nodeOrFragment) => { return !("type" in nodeOrFragment); }; var insertContentAt = (position, value, options) => ({ tr, dispatch, editor }) => { var _a; if (dispatch) { options = { parseOptions: editor.options.parseOptions, updateSelection: true, applyInputRules: false, applyPasteRules: false, ...options }; let content; const emitContentError = (error) => { editor.emit("contentError", { editor, error, disableCollaboration: () => { if ("collaboration" in editor.storage && typeof editor.storage.collaboration === "object" && editor.storage.collaboration) { ; editor.storage.collaboration.isDisabled = true; } } }); }; const parseOptions = { preserveWhitespace: "full", ...options.parseOptions }; if (!options.errorOnInvalidContent && !editor.options.enableContentCheck && editor.options.emitContentError) { try { createNodeFromContent(value, editor.schema, { parseOptions, errorOnInvalidContent: true }); } catch (e) { emitContentError(e); } } try { content = createNodeFromContent(value, editor.schema, { parseOptions, errorOnInvalidContent: (_a = options.errorOnInvalidContent) != null ? _a : editor.options.enableContentCheck }); } catch (e) { emitContentError(e); return false; } let { from, to } = typeof position === "number" ? { from: position, to: position } : { from: position.from, to: position.to }; let isOnlyTextContent = true; let isOnlyBlockContent = true; const nodes = isFragment(content) ? content : [content]; nodes.forEach((node) => { node.check(); isOnlyTextContent = isOnlyTextContent ? node.isText && node.marks.length === 0 : false; isOnlyBlockContent = isOnlyBlockContent ? node.isBlock : false; }); if (from === to && isOnlyBlockContent) { const { parent } = tr.doc.resolve(from); const isEmptyTextBlock = parent.isTextblock && !parent.type.spec.code && !parent.childCount; if (isEmptyTextBlock) { from -= 1; to += 1; } } let newContent; if (isOnlyTextContent) { if (Array.isArray(value)) { newContent = value.map((v) => v.text || "").join(""); } else if (value instanceof Fragment2) { let text = ""; value.forEach((node) => { if (node.text) { text += node.text; } }); newContent = text; } else if (typeof value === "object" && !!value && !!value.text) { newContent = value.text; } else { newContent = value; } tr.insertText(newContent, from, to); } else { newContent = content; const $from = tr.doc.resolve(from); const $fromNode = $from.node(); const fromSelectionAtStart = $from.parentOffset === 0; const isTextSelection2 = $fromNode.isText || $fromNode.isTextblock; const hasContent = $fromNode.content.size > 0; if (fromSelectionAtStart && isTextSelection2 && hasContent) { from = Math.max(0, from - 1); } tr.replaceWith(from, to, newContent); } if (options.updateSelection) { selectionToInsertionEnd(tr, tr.steps.length - 1, -1); } if (options.applyInputRules) { tr.setMeta("applyInputRules", { from, text: newContent }); } if (options.applyPasteRules) { tr.setMeta("applyPasteRules", { from, text: newContent }); } } return true; }; // src/commands/join.ts import { joinBackward as originalJoinBackward, joinDown as originalJoinDown, joinForward as originalJoinForward, joinUp as originalJoinUp } from "@tiptap/pm/commands"; var joinUp = () => ({ state, dispatch }) => { return originalJoinUp(state, dispatch); }; var joinDown = () => ({ state, dispatch }) => { return originalJoinDown(state, dispatch); }; var joinBackward = () => ({ state, dispatch }) => { return originalJoinBackward(state, dispatch); }; var joinForward = () => ({ state, dispatch }) => { return originalJoinForward(state, dispatch); }; // src/commands/joinItemBackward.ts import { joinPoint } from "@tiptap/pm/transform"; var joinItemBackward = () => ({ state, dispatch, tr }) => { try { const point = joinPoint(state.doc, state.selection.$from.pos, -1); if (point === null || point === void 0) { return false; } tr.join(point, 2); if (dispatch) { dispatch(tr); } return true; } catch { return false; } }; // src/commands/joinItemForward.ts import { joinPoint as joinPoint2 } from "@tiptap/pm/transform"; var joinItemForward = () => ({ state, dispatch, tr }) => { try { const point = joinPoint2(state.doc, state.selection.$from.pos, 1); if (point === null || point === void 0) { return false; } tr.join(point, 2); if (dispatch) { dispatch(tr); } return true; } catch { return false; } }; // src/commands/joinTextblockBackward.ts import { joinTextblockBackward as originalCommand } from "@tiptap/pm/commands"; var joinTextblockBackward = () => ({ state, dispatch }) => { return originalCommand(state, dispatch); }; // src/commands/joinTextblockForward.ts import { joinTextblockForward as originalCommand2 } from "@tiptap/pm/commands"; var joinTextblockForward = () => ({ state, dispatch }) => { return originalCommand2(state, dispatch); }; // src/utilities/isMacOS.ts function isMacOS() { return typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; } // src/commands/keyboardShortcut.ts function normalizeKeyName(name) { const parts = name.split(/-(?!$)/); let result = parts[parts.length - 1]; if (result === "Space") { result = " "; } let alt; let ctrl; let shift; let meta; for (let i = 0; i < parts.length - 1; i += 1) { const mod = parts[i]; if (/^(cmd|meta|m)$/i.test(mod)) { meta = true; } else if (/^a(lt)?$/i.test(mod)) { alt = true; } else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; } else if (/^s(hift)?$/i.test(mod)) { shift = true; } else if (/^mod$/i.test(mod)) { if (isiOS() || isMacOS()) { meta = true; } else { ctrl = true; } } else { throw new Error(`Unrecognized modifier name: ${mod}`); } } if (alt) { result = `Alt-${result}`; } if (ctrl) { result = `Ctrl-${result}`; } if (meta) { result = `Meta-${result}`; } if (shift) { result = `Shift-${result}`; } return result; } var keyboardShortcut = (name) => ({ editor, view, tr, dispatch }) => { const keys = normalizeKeyName(name).split(/-(?!$)/); const key = keys.find((item) => !["Alt", "Ctrl", "Meta", "Shift"].includes(item)); const event = new KeyboardEvent("keydown", { key: key === "Space" ? " " : key, altKey: keys.includes("Alt"), ctrlKey: keys.includes("Ctrl"), metaKey: keys.includes("Meta"), shiftKey: keys.includes("Shift"), bubbles: true, cancelable: true }); const capturedTransaction = editor.captureTransaction(() => { view.someProp("handleKeyDown", (f) => f(view, event)); }); capturedTransaction == null ? void 0 : capturedTransaction.steps.forEach((step) => { const newStep = step.map(tr.mapping); if (newStep && dispatch) { tr.maybeStep(newStep); } }); return true; }; // src/commands/lift.ts import { lift as originalLift } from "@tiptap/pm/commands"; // 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/commands/lift.ts var lift = (typeOrName, attributes = {}) => ({ state, dispatch }) => { const type = getNodeType(typeOrName, state.schema); const isActive2 = isNodeActive(state, type, attributes); if (!isActive2) { return false; } return originalLift(state, dispatch); }; // src/commands/liftEmptyBlock.ts import { liftEmptyBlock as originalLiftEmptyBlock } from "@tiptap/pm/commands"; var liftEmptyBlock = () => ({ state, dispatch }) => { return originalLiftEmptyBlock(state, dispatch); }; // src/commands/liftListItem.ts import { liftListItem as originalLiftListItem } from "@tiptap/pm/schema-list"; var liftListItem = (typeOrName) => ({ state, dispatch }) => { const type = getNodeType(typeOrName, state.schema); return originalLiftListItem(type)(state, dispatch); }; // src/commands/newlineInCode.ts import { newlineInCode as originalNewlineInCode } from "@tiptap/pm/commands"; var newlineInCode = () => ({ state, dispatch }) => { return originalNewlineInCode(state, dispatch); }; // src/helpers/getSchemaTypeNameByName.ts function getSchemaTypeNameByName(name, schema) { if (schema.nodes[name]) { return "node"; } if (schema.marks[name]) { return "mark"; } return null; } // src/utilities/deleteProps.ts function deleteProps(obj, propOrProps) { const props = typeof propOrProps === "string" ? [propOrProps] : propOrProps; return Object.keys(obj).reduce((newObj, prop) => { if (!props.includes(prop)) { newObj[prop] = obj[prop]; } return newObj; }, {}); } // src/commands/resetAttributes.ts var resetAttributes = (typeOrName, attributes) => ({ tr, state, dispatch }) => { let nodeType = null; let markType = null; const schemaType = getSchemaTypeNameByName( typeof typeOrName === "string" ? typeOrName : typeOrName.name, state.schema ); if (!schemaType) { return false; } if (schemaType === "node") { nodeType = getNodeType(typeOrName, state.schema); } if (schemaType === "mark") { markType = getMarkType(typeOrName, state.schema); } if (dispatch) { tr.selection.ranges.forEach((range) => { state.doc.nodesBetween(range.$from.pos, range.$to.pos, (node, pos) => { if (nodeType && nodeType === node.type) { tr.setNodeMarkup(pos, void 0, deleteProps(node.attrs, attributes)); } if (markType && node.marks.length) { node.marks.forEach((mark) => { if (markType === mark.type) { tr.addMark(pos, pos + node.nodeSize, markType.create(deleteProps(mark.attrs, attributes))); } }); } }); }); } return true; }; // src/commands/scrollIntoView.ts var scrollIntoView = () => ({ tr, dispatch }) => { if (dispatch) { tr.scrollIntoView(); } return true; }; // src/commands/selectAll.ts import { AllSelection } from "@tiptap/pm/state"; var selectAll = () => ({ tr, dispatch }) => { if (dispatch) { const selection = new AllSelection(tr.doc); tr.setSelection(selection); } return true; }; // src/commands/selectNodeBackward.ts import { selectNodeBackward as originalSelectNodeBackward } from "@tiptap/pm/commands"; var selectNodeBackward = () => ({ state, dispatch }) => { return originalSelectNodeBackward(state, dispatch); }; // src/commands/selectNodeForward.ts import { selectNodeForward as originalSelectNodeForward } from "@tiptap/pm/commands"; var selectNodeForward = () => ({ state, dispatch }) => { return originalSelectNodeForward(state, dispatch); }; // src/commands/selectParentNode.ts import { selectParentNode as originalSelectParentNode } from "@tiptap/pm/commands"; var selectParentNode = () => ({ state, dispatch }) => { return originalSelectParentNode(state, dispatch); }; // src/commands/selectTextblockEnd.ts import { selectTextblockEnd as originalSelectTextblockEnd } from "@tiptap/pm/commands"; var selectTextblockEnd = () => ({ state, dispatch }) => { return originalSelectTextblockEnd(state, dispatch); }; // src/commands/selectTextblockStart.ts import { selectTextblockStart as originalSelectTextblockStart } from "@tiptap/pm/commands"; var selectTextblockStart = () => ({ state, dispatch }) => { return originalSelectTextblockStart(state, dispatch); }; // src/helpers/createDocument.ts function createDocument(content, schema, parseOptions = {}, options = {}) { return createNodeFromContent(content, schema, { slice: false, parseOptions, errorOnInvalidContent: options.errorOnInvalidContent }); } // src/commands/setContent.ts var setContent = (content, { errorOnInvalidContent, emitUpdate = true, parseOptions = {} } = {}) => ({ editor, tr, dispatch, commands }) => { const { doc } = tr; if (parseOptions.preserveWhitespace !== "full") { const document2 = createDocument(content, editor.schema, parseOptions, { errorOnInvalidContent: errorOnInvalidContent != null ? errorOnInvalidContent : editor.options.enableContentCheck }); if (dispatch) { tr.replaceWith(0, doc.content.size, document2).setMeta("preventUpdate", !emitUpdate); } return true; } if (dispatch) { tr.setMeta("preventUpdate", !emitUpdate); } return commands.insertContentAt({ from: 0, to: doc.content.size }, content, { parseOptions, errorOnInvalidContent: errorOnInvalidContent != null ? errorOnInvalidContent : editor.options.enableContentCheck }); }; // 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/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/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, .