@tiptap/core 3.19.0 → 3.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -719,6 +719,26 @@ interface ExtendableConfig<Options = any, Storage = any, Config extends Extensio
719
719
  type: PMType;
720
720
  parent: ParentConfig<Config>['addProseMirrorPlugins'];
721
721
  }) => Plugin[];
722
+ /**
723
+ * This function transforms pasted HTML content before it's parsed.
724
+ * Extensions can use this to modify or clean up pasted HTML.
725
+ * The transformations are chained - each extension's transform receives
726
+ * the output from the previous extension's transform.
727
+ * @see https://tiptap.dev/docs/editor/guide/custom-extensions#transform-pasted-html
728
+ * @example
729
+ * transformPastedHTML(html) {
730
+ * // Remove all style attributes
731
+ * return html.replace(/style="[^"]*"/g, '')
732
+ * }
733
+ */
734
+ transformPastedHTML?: (this: {
735
+ name: string;
736
+ options: Options;
737
+ storage: Storage;
738
+ editor: Editor;
739
+ type: PMType;
740
+ parent: ParentConfig<Config>['transformPastedHTML'];
741
+ }, html: string) => string;
722
742
  /**
723
743
  * This function adds additional extensions to the editor. This is useful for
724
744
  * building extension kits.
@@ -1496,8 +1516,18 @@ type ExtensionAttribute = {
1496
1516
  type GlobalAttributes = {
1497
1517
  /**
1498
1518
  * The node & mark types this attribute should be applied to.
1519
+ * Can be a specific array of type names, or a shorthand string:
1520
+ * - `'*'` applies to all nodes (excluding text) and all marks
1521
+ * - `'nodes'` applies to all nodes (excluding the built-in text node)
1522
+ * - `'marks'` applies to all marks
1523
+ * - `string[]` applies to specific node/mark types by name
1524
+ * @example
1525
+ * types: '*' // All nodes and marks
1526
+ * types: 'nodes' // All nodes
1527
+ * types: 'marks' // All marks
1528
+ * types: ['heading', 'paragraph'] // Specific types
1499
1529
  */
1500
- types: string[];
1530
+ types: string[] | 'nodes' | 'marks' | '*';
1501
1531
  /**
1502
1532
  * The attributes to add to the node or mark types.
1503
1533
  */
@@ -2320,6 +2350,12 @@ declare class ExtensionManager {
2320
2350
  * @returns A composed dispatch function
2321
2351
  */
2322
2352
  dispatchTransaction(baseDispatch: (tr: Transaction) => void): (tr: Transaction) => void;
2353
+ /**
2354
+ * Get the composed transformPastedHTML function from all extensions.
2355
+ * @param baseTransform The base transform function (e.g. from the editor props)
2356
+ * @returns A composed transform function that chains all extension transforms
2357
+ */
2358
+ transformPastedHTML(baseTransform?: (html: string, view?: any) => string): (html: string, view?: EditorView) => string;
2323
2359
  get markViews(): Record<string, MarkViewConstructor>;
2324
2360
  /**
2325
2361
  * Go through all extensions, create extension storages & setup marks
@@ -3528,7 +3564,7 @@ declare class Editor extends EventEmitter<EditorEvents> {
3528
3564
  */
3529
3565
  get isEditable(): boolean;
3530
3566
  /**
3531
- * Returns the editor state.
3567
+ * Returns the editor view.
3532
3568
  */
3533
3569
  get view(): EditorView;
3534
3570
  /**
package/dist/index.d.ts CHANGED
@@ -719,6 +719,26 @@ interface ExtendableConfig<Options = any, Storage = any, Config extends Extensio
719
719
  type: PMType;
720
720
  parent: ParentConfig<Config>['addProseMirrorPlugins'];
721
721
  }) => Plugin[];
722
+ /**
723
+ * This function transforms pasted HTML content before it's parsed.
724
+ * Extensions can use this to modify or clean up pasted HTML.
725
+ * The transformations are chained - each extension's transform receives
726
+ * the output from the previous extension's transform.
727
+ * @see https://tiptap.dev/docs/editor/guide/custom-extensions#transform-pasted-html
728
+ * @example
729
+ * transformPastedHTML(html) {
730
+ * // Remove all style attributes
731
+ * return html.replace(/style="[^"]*"/g, '')
732
+ * }
733
+ */
734
+ transformPastedHTML?: (this: {
735
+ name: string;
736
+ options: Options;
737
+ storage: Storage;
738
+ editor: Editor;
739
+ type: PMType;
740
+ parent: ParentConfig<Config>['transformPastedHTML'];
741
+ }, html: string) => string;
722
742
  /**
723
743
  * This function adds additional extensions to the editor. This is useful for
724
744
  * building extension kits.
@@ -1496,8 +1516,18 @@ type ExtensionAttribute = {
1496
1516
  type GlobalAttributes = {
1497
1517
  /**
1498
1518
  * The node & mark types this attribute should be applied to.
1519
+ * Can be a specific array of type names, or a shorthand string:
1520
+ * - `'*'` applies to all nodes (excluding text) and all marks
1521
+ * - `'nodes'` applies to all nodes (excluding the built-in text node)
1522
+ * - `'marks'` applies to all marks
1523
+ * - `string[]` applies to specific node/mark types by name
1524
+ * @example
1525
+ * types: '*' // All nodes and marks
1526
+ * types: 'nodes' // All nodes
1527
+ * types: 'marks' // All marks
1528
+ * types: ['heading', 'paragraph'] // Specific types
1499
1529
  */
1500
- types: string[];
1530
+ types: string[] | 'nodes' | 'marks' | '*';
1501
1531
  /**
1502
1532
  * The attributes to add to the node or mark types.
1503
1533
  */
@@ -2320,6 +2350,12 @@ declare class ExtensionManager {
2320
2350
  * @returns A composed dispatch function
2321
2351
  */
2322
2352
  dispatchTransaction(baseDispatch: (tr: Transaction) => void): (tr: Transaction) => void;
2353
+ /**
2354
+ * Get the composed transformPastedHTML function from all extensions.
2355
+ * @param baseTransform The base transform function (e.g. from the editor props)
2356
+ * @returns A composed transform function that chains all extension transforms
2357
+ */
2358
+ transformPastedHTML(baseTransform?: (html: string, view?: any) => string): (html: string, view?: EditorView) => string;
2323
2359
  get markViews(): Record<string, MarkViewConstructor>;
2324
2360
  /**
2325
2361
  * Go through all extensions, create extension storages & setup marks
@@ -3528,7 +3564,7 @@ declare class Editor extends EventEmitter<EditorEvents> {
3528
3564
  */
3529
3565
  get isEditable(): boolean;
3530
3566
  /**
3531
- * Returns the editor state.
3567
+ * Returns the editor view.
3532
3568
  */
3533
3569
  get view(): EditorView;
3534
3570
  /**
package/dist/index.js CHANGED
@@ -1348,6 +1348,9 @@ function getAttributesFromExtensions(extensions) {
1348
1348
  keepOnSplit: true,
1349
1349
  isRequired: false
1350
1350
  };
1351
+ const nodeExtensionTypes = nodeExtensions.filter((ext) => ext.name !== "text").map((ext) => ext.name);
1352
+ const markExtensionTypes = markExtensions.map((ext) => ext.name);
1353
+ const allExtensionTypes = [...nodeExtensionTypes, ...markExtensionTypes];
1351
1354
  extensions.forEach((extension) => {
1352
1355
  const context = {
1353
1356
  name: extension.name,
@@ -1365,7 +1368,19 @@ function getAttributesFromExtensions(extensions) {
1365
1368
  }
1366
1369
  const globalAttributes = addGlobalAttributes();
1367
1370
  globalAttributes.forEach((globalAttribute) => {
1368
- globalAttribute.types.forEach((type) => {
1371
+ let resolvedTypes;
1372
+ if (Array.isArray(globalAttribute.types)) {
1373
+ resolvedTypes = globalAttribute.types;
1374
+ } else if (globalAttribute.types === "*") {
1375
+ resolvedTypes = allExtensionTypes;
1376
+ } else if (globalAttribute.types === "nodes") {
1377
+ resolvedTypes = nodeExtensionTypes;
1378
+ } else if (globalAttribute.types === "marks") {
1379
+ resolvedTypes = markExtensionTypes;
1380
+ } else {
1381
+ resolvedTypes = [];
1382
+ }
1383
+ resolvedTypes.forEach((type) => {
1369
1384
  Object.entries(globalAttribute.attributes).forEach(([name, attribute]) => {
1370
1385
  extensionAttributes.push({
1371
1386
  type,
@@ -1416,6 +1431,67 @@ function getAttributesFromExtensions(extensions) {
1416
1431
  }
1417
1432
 
1418
1433
  // src/utilities/mergeAttributes.ts
1434
+ function splitStyleDeclarations(styles) {
1435
+ const result = [];
1436
+ let current = "";
1437
+ let inSingleQuote = false;
1438
+ let inDoubleQuote = false;
1439
+ let parenDepth = 0;
1440
+ const length = styles.length;
1441
+ for (let i = 0; i < length; i += 1) {
1442
+ const char = styles[i];
1443
+ if (char === "'" && !inDoubleQuote) {
1444
+ inSingleQuote = !inSingleQuote;
1445
+ current += char;
1446
+ continue;
1447
+ }
1448
+ if (char === '"' && !inSingleQuote) {
1449
+ inDoubleQuote = !inDoubleQuote;
1450
+ current += char;
1451
+ continue;
1452
+ }
1453
+ if (!inSingleQuote && !inDoubleQuote) {
1454
+ if (char === "(") {
1455
+ parenDepth += 1;
1456
+ current += char;
1457
+ continue;
1458
+ }
1459
+ if (char === ")" && parenDepth > 0) {
1460
+ parenDepth -= 1;
1461
+ current += char;
1462
+ continue;
1463
+ }
1464
+ if (char === ";" && parenDepth === 0) {
1465
+ result.push(current);
1466
+ current = "";
1467
+ continue;
1468
+ }
1469
+ }
1470
+ current += char;
1471
+ }
1472
+ if (current) {
1473
+ result.push(current);
1474
+ }
1475
+ return result;
1476
+ }
1477
+ function parseStyleEntries(styles) {
1478
+ const pairs = [];
1479
+ const declarations = splitStyleDeclarations(styles || "");
1480
+ const numDeclarations = declarations.length;
1481
+ for (let i = 0; i < numDeclarations; i += 1) {
1482
+ const declaration = declarations[i];
1483
+ const firstColonIndex = declaration.indexOf(":");
1484
+ if (firstColonIndex === -1) {
1485
+ continue;
1486
+ }
1487
+ const property = declaration.slice(0, firstColonIndex).trim();
1488
+ const value = declaration.slice(firstColonIndex + 1).trim();
1489
+ if (property && value) {
1490
+ pairs.push([property, value]);
1491
+ }
1492
+ }
1493
+ return pairs;
1494
+ }
1419
1495
  function mergeAttributes(...objects) {
1420
1496
  return objects.filter((item) => !!item).reduce((items, item) => {
1421
1497
  const mergedAttributes = { ...items };
@@ -1431,17 +1507,7 @@ function mergeAttributes(...objects) {
1431
1507
  const insertClasses = valueClasses.filter((valueClass) => !existingClasses.includes(valueClass));
1432
1508
  mergedAttributes[key] = [...existingClasses, ...insertClasses].join(" ");
1433
1509
  } else if (key === "style") {
1434
- const newStyles = value ? value.split(";").map((style2) => style2.trim()).filter(Boolean) : [];
1435
- const existingStyles = mergedAttributes[key] ? mergedAttributes[key].split(";").map((style2) => style2.trim()).filter(Boolean) : [];
1436
- const styleMap = /* @__PURE__ */ new Map();
1437
- existingStyles.forEach((style2) => {
1438
- const [property, val] = style2.split(":").map((part) => part.trim());
1439
- styleMap.set(property, val);
1440
- });
1441
- newStyles.forEach((style2) => {
1442
- const [property, val] = style2.split(":").map((part) => part.trim());
1443
- styleMap.set(property, val);
1444
- });
1510
+ const styleMap = new Map([...parseStyleEntries(mergedAttributes[key]), ...parseStyleEntries(value)]);
1445
1511
  mergedAttributes[key] = Array.from(styleMap.entries()).map(([property, val]) => `${property}: ${val}`).join("; ");
1446
1512
  } else {
1447
1513
  mergedAttributes[key] = value;
@@ -1983,6 +2049,9 @@ function isMarkActive(state, typeOrName, attributes = {}) {
1983
2049
  const from = $from.pos;
1984
2050
  const to = $to.pos;
1985
2051
  state.doc.nodesBetween(from, to, (node, pos) => {
2052
+ if (type && node.inlineContent && !node.type.allowsMarkType(type)) {
2053
+ return false;
2054
+ }
1986
2055
  if (!node.isText && !node.marks.length) {
1987
2056
  return;
1988
2057
  }
@@ -3683,6 +3752,39 @@ var ExtensionManager = class {
3683
3752
  };
3684
3753
  }, baseDispatch);
3685
3754
  }
3755
+ /**
3756
+ * Get the composed transformPastedHTML function from all extensions.
3757
+ * @param baseTransform The base transform function (e.g. from the editor props)
3758
+ * @returns A composed transform function that chains all extension transforms
3759
+ */
3760
+ transformPastedHTML(baseTransform) {
3761
+ const { editor } = this;
3762
+ const extensions = sortExtensions([...this.extensions]);
3763
+ return extensions.reduce(
3764
+ (transform, extension) => {
3765
+ const context = {
3766
+ name: extension.name,
3767
+ options: extension.options,
3768
+ storage: this.editor.extensionStorage[extension.name],
3769
+ editor,
3770
+ type: getSchemaTypeByName(extension.name, this.schema)
3771
+ };
3772
+ const extensionTransform = getExtensionField(
3773
+ extension,
3774
+ "transformPastedHTML",
3775
+ context
3776
+ );
3777
+ if (!extensionTransform) {
3778
+ return transform;
3779
+ }
3780
+ return (html, view) => {
3781
+ const transformedHtml = transform(html, view);
3782
+ return extensionTransform.call(context, transformedHtml);
3783
+ };
3784
+ },
3785
+ baseTransform || ((html) => html)
3786
+ );
3787
+ }
3686
3788
  get markViews() {
3687
3789
  const { editor } = this;
3688
3790
  const { markExtensions } = splitExtensions(this.extensions);
@@ -4705,7 +4807,7 @@ var Editor = class extends EventEmitter {
4705
4807
  return this.options.editable && this.view && this.view.editable;
4706
4808
  }
4707
4809
  /**
4708
- * Returns the editor state.
4810
+ * Returns the editor view.
4709
4811
  */
4710
4812
  get view() {
4711
4813
  if (this.editorView) {
@@ -4874,6 +4976,8 @@ var Editor = class extends EventEmitter {
4874
4976
  const { editorProps, enableExtensionDispatchTransaction } = this.options;
4875
4977
  const baseDispatch = editorProps.dispatchTransaction || this.dispatchTransaction.bind(this);
4876
4978
  const dispatch = enableExtensionDispatchTransaction ? this.extensionManager.dispatchTransaction(baseDispatch) : baseDispatch;
4979
+ const baseTransformPastedHTML = editorProps.transformPastedHTML;
4980
+ const transformPastedHTML = this.extensionManager.transformPastedHTML(baseTransformPastedHTML);
4877
4981
  this.editorView = new EditorView(element, {
4878
4982
  ...editorProps,
4879
4983
  attributes: {
@@ -4882,6 +4986,7 @@ var Editor = class extends EventEmitter {
4882
4986
  ...editorProps == null ? void 0 : editorProps.attributes
4883
4987
  },
4884
4988
  dispatchTransaction: dispatch,
4989
+ transformPastedHTML,
4885
4990
  state: this.editorState,
4886
4991
  markViews: this.extensionManager.markViews,
4887
4992
  nodeViews: this.extensionManager.nodeViews