@tiptap/core
Version:
headless rich text editor
1,585 lines (1,513 loc) • 176 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
CommandManager: () => CommandManager,
Editor: () => Editor,
Extension: () => Extension,
Fragment: () => Fragment6,
InputRule: () => InputRule,
Mark: () => Mark,
MarkView: () => MarkView,
Node: () => Node3,
NodePos: () => NodePos,
NodeView: () => NodeView,
PasteRule: () => PasteRule,
Tracker: () => Tracker,
callOrReturn: () => callOrReturn,
canInsertNode: () => canInsertNode,
combineTransactionSteps: () => combineTransactionSteps,
createChainableState: () => createChainableState,
createDocument: () => createDocument,
createElement: () => h,
createNodeFromContent: () => createNodeFromContent,
createStyleTag: () => createStyleTag,
defaultBlockAt: () => defaultBlockAt,
deleteProps: () => deleteProps,
elementFromString: () => elementFromString,
escapeForRegEx: () => escapeForRegEx,
extensions: () => extensions_exports,
findChildren: () => findChildren,
findChildrenInRange: () => findChildrenInRange,
findDuplicates: () => findDuplicates,
findParentNode: () => findParentNode,
findParentNodeClosestToPos: () => findParentNodeClosestToPos,
flattenExtensions: () => flattenExtensions,
fromString: () => fromString,
generateHTML: () => generateHTML,
generateJSON: () => generateJSON,
generateText: () => generateText,
getAttributes: () => getAttributes,
getAttributesFromExtensions: () => getAttributesFromExtensions,
getChangedRanges: () => getChangedRanges,
getDebugJSON: () => getDebugJSON,
getExtensionField: () => getExtensionField,
getHTMLFromFragment: () => getHTMLFromFragment,
getMarkAttributes: () => getMarkAttributes,
getMarkRange: () => getMarkRange,
getMarkType: () => getMarkType,
getMarksBetween: () => getMarksBetween,
getNodeAtPosition: () => getNodeAtPosition,
getNodeAttributes: () => getNodeAttributes,
getNodeType: () => getNodeType,
getRenderedAttributes: () => getRenderedAttributes,
getSchema: () => getSchema,
getSchemaByResolvedExtensions: () => getSchemaByResolvedExtensions,
getSchemaTypeByName: () => getSchemaTypeByName,
getSchemaTypeNameByName: () => getSchemaTypeNameByName,
getSplittedAttributes: () => getSplittedAttributes,
getText: () => getText,
getTextBetween: () => getTextBetween,
getTextContentFromNodes: () => getTextContentFromNodes,
getTextSerializersFromSchema: () => getTextSerializersFromSchema,
h: () => h,
injectExtensionAttributesToParseRule: () => injectExtensionAttributesToParseRule,
inputRulesPlugin: () => inputRulesPlugin,
isActive: () => isActive,
isAndroid: () => isAndroid,
isAtEndOfNode: () => isAtEndOfNode,
isAtStartOfNode: () => isAtStartOfNode,
isEmptyObject: () => isEmptyObject,
isExtensionRulesEnabled: () => isExtensionRulesEnabled,
isFunction: () => isFunction,
isList: () => isList,
isMacOS: () => isMacOS,
isMarkActive: () => isMarkActive,
isNodeActive: () => isNodeActive,
isNodeEmpty: () => isNodeEmpty,
isNodeSelection: () => isNodeSelection,
isNumber: () => isNumber,
isPlainObject: () => isPlainObject,
isRegExp: () => isRegExp,
isString: () => isString,
isTextSelection: () => isTextSelection,
isiOS: () => isiOS,
markInputRule: () => markInputRule,
markPasteRule: () => markPasteRule,
mergeAttributes: () => mergeAttributes,
mergeDeep: () => mergeDeep,
minMax: () => minMax,
nodeInputRule: () => nodeInputRule,
nodePasteRule: () => nodePasteRule,
objectIncludes: () => objectIncludes,
pasteRulesPlugin: () => pasteRulesPlugin,
posToDOMRect: () => posToDOMRect,
removeDuplicates: () => removeDuplicates,
resolveExtensions: () => resolveExtensions,
resolveFocusPosition: () => resolveFocusPosition,
rewriteUnknownContent: () => rewriteUnknownContent,
selectionToInsertionEnd: () => selectionToInsertionEnd,
sortExtensions: () => sortExtensions,
splitExtensions: () => splitExtensions,
textInputRule: () => textInputRule,
textPasteRule: () => textPasteRule,
textblockTypeInputRule: () => textblockTypeInputRule,
updateMarkViewAttributes: () => updateMarkViewAttributes,
wrappingInputRule: () => wrappingInputRule
});
module.exports = __toCommonJS(index_exports);
// 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
var import_state21 = require("@tiptap/pm/state");
var import_view = require("@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
var import_keymap = require("@tiptap/pm/keymap");
// src/helpers/combineTransactionSteps.ts
var import_transform = require("@tiptap/pm/transform");
function combineTransactionSteps(oldDoc, transactions) {
const transform = new import_transform.Transform(oldDoc);
transactions.forEach((transaction) => {
transaction.steps.forEach((step) => {
transform.step(step);
});
});
return transform;
}
// src/helpers/createNodeFromContent.ts
var import_model = require("@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 import_model.Node || content instanceof import_model.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 import_model.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 import_model.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) {
import_model.DOMParser.fromSchema(contentCheckSchema).parseSlice(elementFromString(content), options.parseOptions);
} else {
import_model.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 = import_model.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
var import_model4 = require("@tiptap/pm/model");
// src/helpers/getHTMLFromFragment.ts
var import_model2 = require("@tiptap/pm/model");
function getHTMLFromFragment(fragment, schema) {
const documentFragment = import_model2.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
var import_model3 = require("@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 import_model3.Schema({
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 = import_model4.Node.fromJSON(schema, doc);
return getHTMLFromFragment(contentNode.content, schema);
}
// src/helpers/generateJSON.ts
var import_model5 = require("@tiptap/pm/model");
function generateJSON(html, extensions) {
const schema = getSchema(extensions);
const dom = elementFromString(html);
return import_model5.DOMParser.fromSchema(schema).parse(dom).toJSON();
}
// src/helpers/generateText.ts
var import_model6 = require("@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 = import_model6.Node.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
var import_state = require("@tiptap/pm/state");
function isNodeSelection(value) {
return value instanceof import_state.NodeSelection;
}
// src/helpers/isTextSelection.ts
var import_state2 = require("@tiptap/pm/state");
function isTextSelection(value) {
return value instanceof import_state2.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
var import_state3 = require("@tiptap/pm/state");
function re