@tiptap/core 2.2.5 → 2.3.0

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.cjs CHANGED
@@ -665,6 +665,23 @@ function inputRulesPlugin(props) {
665
665
  if (stored) {
666
666
  return stored;
667
667
  }
668
+ // if InputRule is triggered by insertContent()
669
+ const simulatedInputMeta = tr.getMeta('applyInputRules');
670
+ const isSimulatedInput = !!simulatedInputMeta;
671
+ if (isSimulatedInput) {
672
+ setTimeout(() => {
673
+ const { from, text } = simulatedInputMeta;
674
+ const to = from + text.length;
675
+ run$1({
676
+ editor,
677
+ from,
678
+ to,
679
+ text,
680
+ rules,
681
+ plugin,
682
+ });
683
+ });
684
+ }
668
685
  return tr.selectionSet || tr.docChanged ? null : prev;
669
686
  },
670
687
  },
@@ -796,6 +813,14 @@ function run(config) {
796
813
  const success = handlers.every(handler => handler !== null);
797
814
  return success;
798
815
  }
816
+ const createClipboardPasteEvent = (text) => {
817
+ var _a;
818
+ const event = new ClipboardEvent('paste', {
819
+ clipboardData: new DataTransfer(),
820
+ });
821
+ (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.setData('text/html', text);
822
+ return event;
823
+ };
799
824
  /**
800
825
  * Create an paste rules plugin. When enabled, it will cause pasted
801
826
  * text that matches any of the given rules to trigger the rule’s
@@ -808,6 +833,28 @@ function pasteRulesPlugin(props) {
808
833
  let isDroppedFromProseMirror = false;
809
834
  let pasteEvent = typeof ClipboardEvent !== 'undefined' ? new ClipboardEvent('paste') : null;
810
835
  let dropEvent = typeof DragEvent !== 'undefined' ? new DragEvent('drop') : null;
836
+ const processEvent = ({ state, from, to, rule, pasteEvt, }) => {
837
+ const tr = state.tr;
838
+ const chainableState = createChainableState({
839
+ state,
840
+ transaction: tr,
841
+ });
842
+ const handler = run({
843
+ editor,
844
+ state: chainableState,
845
+ from: Math.max(from - 1, 0),
846
+ to: to.b - 1,
847
+ rule,
848
+ pasteEvent: pasteEvt,
849
+ dropEvent,
850
+ });
851
+ if (!handler || !tr.steps.length) {
852
+ return;
853
+ }
854
+ dropEvent = typeof DragEvent !== 'undefined' ? new DragEvent('drop') : null;
855
+ pasteEvent = typeof ClipboardEvent !== 'undefined' ? new ClipboardEvent('paste') : null;
856
+ return tr;
857
+ };
811
858
  const plugins = rules.map(rule => {
812
859
  return new state.Plugin({
813
860
  // we register a global drag handler to track the current drag source element
@@ -845,38 +892,39 @@ function pasteRulesPlugin(props) {
845
892
  const transaction = transactions[0];
846
893
  const isPaste = transaction.getMeta('uiEvent') === 'paste' && !isPastedFromProseMirror;
847
894
  const isDrop = transaction.getMeta('uiEvent') === 'drop' && !isDroppedFromProseMirror;
848
- if (!isPaste && !isDrop) {
895
+ // if PasteRule is triggered by insertContent()
896
+ const simulatedPasteMeta = transaction.getMeta('applyPasteRules');
897
+ const isSimulatedPaste = !!simulatedPasteMeta;
898
+ if (!isPaste && !isDrop && !isSimulatedPaste) {
849
899
  return;
850
900
  }
851
- // stop if there is no changed range
901
+ // Handle simulated paste
902
+ if (isSimulatedPaste) {
903
+ const { from, text } = simulatedPasteMeta;
904
+ const to = from + text.length;
905
+ const pasteEvt = createClipboardPasteEvent(text);
906
+ return processEvent({
907
+ rule,
908
+ state,
909
+ from,
910
+ to: { b: to },
911
+ pasteEvt,
912
+ });
913
+ }
914
+ // handle actual paste/drop
852
915
  const from = oldState.doc.content.findDiffStart(state.doc.content);
853
916
  const to = oldState.doc.content.findDiffEnd(state.doc.content);
917
+ // stop if there is no changed range
854
918
  if (!isNumber(from) || !to || from === to.b) {
855
919
  return;
856
920
  }
857
- // build a chainable state
858
- // so we can use a single transaction for all paste rules
859
- const tr = state.tr;
860
- const chainableState = createChainableState({
861
- state,
862
- transaction: tr,
863
- });
864
- const handler = run({
865
- editor,
866
- state: chainableState,
867
- from: Math.max(from - 1, 0),
868
- to: to.b - 1,
921
+ return processEvent({
869
922
  rule,
870
- pasteEvent,
871
- dropEvent,
923
+ state,
924
+ from,
925
+ to,
926
+ pasteEvt: pasteEvent,
872
927
  });
873
- // stop if there are no changes
874
- if (!handler || !tr.steps.length) {
875
- return;
876
- }
877
- dropEvent = typeof DragEvent !== 'undefined' ? new DragEvent('drop') : null;
878
- pasteEvent = typeof ClipboardEvent !== 'undefined' ? new ClipboardEvent('paste') : null;
879
- return tr;
880
928
  },
881
929
  });
882
930
  });
@@ -1220,8 +1268,10 @@ function getTextBetween(startNode, range, options) {
1220
1268
  range,
1221
1269
  });
1222
1270
  }
1271
+ // do not descend into child nodes when there exists a serializer
1272
+ return false;
1223
1273
  }
1224
- else if (node.isText) {
1274
+ if (node.isText) {
1225
1275
  text += (_a = node === null || node === void 0 ? void 0 : node.text) === null || _a === void 0 ? void 0 : _a.slice(Math.max(from, pos) - pos, to - pos); // eslint-disable-line
1226
1276
  separated = false;
1227
1277
  }
@@ -1241,6 +1291,11 @@ function getTextSerializersFromSchema(schema) {
1241
1291
 
1242
1292
  const ClipboardTextSerializer = Extension.create({
1243
1293
  name: 'clipboardTextSerializer',
1294
+ addOptions() {
1295
+ return {
1296
+ blockSeparator: undefined,
1297
+ };
1298
+ },
1244
1299
  addProseMirrorPlugins() {
1245
1300
  return [
1246
1301
  new state.Plugin({
@@ -1256,6 +1311,9 @@ const ClipboardTextSerializer = Extension.create({
1256
1311
  const textSerializers = getTextSerializersFromSchema(schema);
1257
1312
  const range = { from, to };
1258
1313
  return getTextBetween(doc, range, {
1314
+ ...(this.options.blockSeparator !== undefined
1315
+ ? { blockSeparator: this.options.blockSeparator }
1316
+ : {}),
1259
1317
  textSerializers,
1260
1318
  });
1261
1319
  },
@@ -1662,6 +1720,8 @@ const insertContentAt = (position, value, options) => ({ tr, dispatch, editor })
1662
1720
  options = {
1663
1721
  parseOptions: {},
1664
1722
  updateSelection: true,
1723
+ applyInputRules: false,
1724
+ applyPasteRules: false,
1665
1725
  ...options,
1666
1726
  };
1667
1727
  const content = createNodeFromContent(value, editor.schema, {
@@ -1697,28 +1757,37 @@ const insertContentAt = (position, value, options) => ({ tr, dispatch, editor })
1697
1757
  to += 1;
1698
1758
  }
1699
1759
  }
1760
+ let newContent;
1700
1761
  // if there is only plain text we have to use `insertText`
1701
1762
  // because this will keep the current marks
1702
1763
  if (isOnlyTextContent) {
1703
1764
  // if value is string, we can use it directly
1704
1765
  // otherwise if it is an array, we have to join it
1705
1766
  if (Array.isArray(value)) {
1706
- tr.insertText(value.map(v => v.text || '').join(''), from, to);
1767
+ newContent = value.map(v => v.text || '').join('');
1707
1768
  }
1708
1769
  else if (typeof value === 'object' && !!value && !!value.text) {
1709
- tr.insertText(value.text, from, to);
1770
+ newContent = value.text;
1710
1771
  }
1711
1772
  else {
1712
- tr.insertText(value, from, to);
1773
+ newContent = value;
1713
1774
  }
1775
+ tr.insertText(newContent, from, to);
1714
1776
  }
1715
1777
  else {
1716
- tr.replaceWith(from, to, content);
1778
+ newContent = content;
1779
+ tr.replaceWith(from, to, newContent);
1717
1780
  }
1718
1781
  // set cursor at end of inserted content
1719
1782
  if (options.updateSelection) {
1720
1783
  selectionToInsertionEnd(tr, tr.steps.length - 1, -1);
1721
1784
  }
1785
+ if (options.applyInputRules) {
1786
+ tr.setMeta('applyInputRules', { from, text: newContent });
1787
+ }
1788
+ if (options.applyPasteRules) {
1789
+ tr.setMeta('applyPasteRules', { from, text: newContent });
1790
+ }
1722
1791
  }
1723
1792
  return true;
1724
1793
  };
@@ -3213,7 +3282,7 @@ const Keymap = Extension.create({
3213
3282
  const { selection, doc } = tr;
3214
3283
  const { empty, $anchor } = selection;
3215
3284
  const { pos, parent } = $anchor;
3216
- const $parentPos = $anchor.parent.isTextblock ? tr.doc.resolve(pos - 1) : $anchor;
3285
+ const $parentPos = $anchor.parent.isTextblock && pos > 0 ? tr.doc.resolve(pos - 1) : $anchor;
3217
3286
  const parentIsIsolating = $parentPos.parent.type.spec.isolating;
3218
3287
  const parentPos = $anchor.pos - $anchor.parentOffset;
3219
3288
  const isAtStart = (parentIsIsolating && $parentPos.parent.childCount === 1)
@@ -3329,7 +3398,7 @@ const Tabindex = Extension.create({
3329
3398
  },
3330
3399
  });
3331
3400
 
3332
- var extensions = /*#__PURE__*/Object.freeze({
3401
+ var index = /*#__PURE__*/Object.freeze({
3333
3402
  __proto__: null,
3334
3403
  ClipboardTextSerializer: ClipboardTextSerializer,
3335
3404
  Commands: Commands,
@@ -3433,7 +3502,7 @@ class NodePos {
3433
3502
  const children = [];
3434
3503
  this.node.content.forEach((node, offset) => {
3435
3504
  const isBlock = node.isBlock && !node.isTextblock;
3436
- const targetPos = this.pos + offset + (isBlock ? 0 : 1);
3505
+ const targetPos = this.pos + offset + 1;
3437
3506
  const $pos = this.resolvedPos.doc.resolve(targetPos);
3438
3507
  if (!isBlock && $pos.depth <= this.depth) {
3439
3508
  return;
@@ -3482,7 +3551,7 @@ class NodePos {
3482
3551
  querySelectorAll(selector, attributes = {}, firstItemOnly = false) {
3483
3552
  let nodes = [];
3484
3553
  // iterate through children recursively finding all nodes which match the selector with the node name
3485
- if (this.isBlock || !this.children || this.children.length === 0) {
3554
+ if (!this.children || this.children.length === 0) {
3486
3555
  return nodes;
3487
3556
  }
3488
3557
  this.children.forEach(childPos => {
@@ -3619,6 +3688,7 @@ class Editor extends EventEmitter {
3619
3688
  editable: true,
3620
3689
  editorProps: {},
3621
3690
  parseOptions: {},
3691
+ coreExtensionOptions: {},
3622
3692
  enableInputRules: true,
3623
3693
  enablePasteRules: true,
3624
3694
  enableCoreExtensions: true,
@@ -3764,7 +3834,17 @@ class Editor extends EventEmitter {
3764
3834
  * Creates an extension manager.
3765
3835
  */
3766
3836
  createExtensionManager() {
3767
- const coreExtensions = this.options.enableCoreExtensions ? Object.values(extensions) : [];
3837
+ var _a, _b;
3838
+ const coreExtensions = this.options.enableCoreExtensions ? [
3839
+ Editable,
3840
+ ClipboardTextSerializer.configure({
3841
+ blockSeparator: (_b = (_a = this.options.coreExtensionOptions) === null || _a === void 0 ? void 0 : _a.clipboardTextSerializer) === null || _b === void 0 ? void 0 : _b.blockSeparator,
3842
+ }),
3843
+ Commands,
3844
+ FocusEvents,
3845
+ Keymap,
3846
+ Tabindex,
3847
+ ] : [];
3768
3848
  const allExtensions = [...coreExtensions, ...this.options.extensions].filter(extension => {
3769
3849
  return ['extension', 'node', 'mark'].includes(extension === null || extension === void 0 ? void 0 : extension.type);
3770
3850
  });
@@ -4635,7 +4715,7 @@ exports.defaultBlockAt = defaultBlockAt;
4635
4715
  exports.deleteProps = deleteProps;
4636
4716
  exports.elementFromString = elementFromString;
4637
4717
  exports.escapeForRegEx = escapeForRegEx;
4638
- exports.extensions = extensions;
4718
+ exports.extensions = index;
4639
4719
  exports.findChildren = findChildren;
4640
4720
  exports.findChildrenInRange = findChildrenInRange;
4641
4721
  exports.findDuplicates = findDuplicates;