@tiptap/core 3.0.0-next.3 → 3.0.0-next.4

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.
Files changed (128) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +5 -1
  3. package/dist/index.cjs +2402 -2540
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +1355 -1273
  6. package/dist/index.d.ts +1355 -1273
  7. package/dist/index.js +2408 -2562
  8. package/dist/index.js.map +1 -1
  9. package/package.json +9 -5
  10. package/src/CommandManager.ts +2 -9
  11. package/src/Editor.ts +87 -72
  12. package/src/EventEmitter.ts +7 -10
  13. package/src/Extension.ts +8 -14
  14. package/src/ExtensionManager.ts +26 -125
  15. package/src/InputRule.ts +35 -48
  16. package/src/Mark.ts +9 -9
  17. package/src/Node.ts +9 -9
  18. package/src/NodePos.ts +1 -3
  19. package/src/NodeView.ts +10 -20
  20. package/src/PasteRule.ts +43 -55
  21. package/src/Tracker.ts +7 -9
  22. package/src/commands/blur.ts +14 -12
  23. package/src/commands/clearContent.ts +6 -4
  24. package/src/commands/clearNodes.ts +32 -30
  25. package/src/commands/command.ts +1 -1
  26. package/src/commands/createParagraphNear.ts +5 -3
  27. package/src/commands/cut.ts +12 -10
  28. package/src/commands/deleteCurrentNode.ts +23 -21
  29. package/src/commands/deleteNode.ts +18 -16
  30. package/src/commands/deleteRange.ts +10 -8
  31. package/src/commands/deleteSelection.ts +5 -3
  32. package/src/commands/enter.ts +6 -4
  33. package/src/commands/exitCode.ts +5 -3
  34. package/src/commands/extendMarkRange.ts +14 -12
  35. package/src/commands/first.ts +2 -4
  36. package/src/commands/focus.ts +45 -48
  37. package/src/commands/forEach.ts +2 -2
  38. package/src/commands/insertContent.ts +12 -14
  39. package/src/commands/insertContentAt.ts +101 -98
  40. package/src/commands/join.ts +20 -12
  41. package/src/commands/joinItemBackward.ts +16 -18
  42. package/src/commands/joinItemForward.ts +16 -18
  43. package/src/commands/joinTextblockBackward.ts +5 -3
  44. package/src/commands/joinTextblockForward.ts +5 -3
  45. package/src/commands/keyboardShortcut.ts +29 -34
  46. package/src/commands/lift.ts +10 -8
  47. package/src/commands/liftEmptyBlock.ts +6 -4
  48. package/src/commands/liftListItem.ts +6 -4
  49. package/src/commands/newlineInCode.ts +5 -3
  50. package/src/commands/resetAttributes.ts +36 -41
  51. package/src/commands/scrollIntoView.ts +9 -7
  52. package/src/commands/selectAll.ts +10 -8
  53. package/src/commands/selectNodeBackward.ts +5 -3
  54. package/src/commands/selectNodeForward.ts +5 -3
  55. package/src/commands/selectParentNode.ts +5 -3
  56. package/src/commands/selectTextblockEnd.ts +5 -3
  57. package/src/commands/selectTextblockStart.ts +5 -3
  58. package/src/commands/setContent.ts +25 -25
  59. package/src/commands/setMark.ts +55 -57
  60. package/src/commands/setMeta.ts +7 -5
  61. package/src/commands/setNode.ts +32 -30
  62. package/src/commands/setNodeSelection.ts +11 -9
  63. package/src/commands/setTextSelection.ts +15 -13
  64. package/src/commands/sinkListItem.ts +6 -4
  65. package/src/commands/splitBlock.ts +67 -76
  66. package/src/commands/splitListItem.ts +93 -106
  67. package/src/commands/toggleList.ts +73 -71
  68. package/src/commands/toggleMark.ts +11 -9
  69. package/src/commands/toggleNode.ts +18 -16
  70. package/src/commands/toggleWrap.ts +10 -8
  71. package/src/commands/undoInputRule.ts +31 -29
  72. package/src/commands/unsetAllMarks.ts +16 -14
  73. package/src/commands/unsetMark.ts +27 -25
  74. package/src/commands/updateAttributes.ts +92 -100
  75. package/src/commands/wrapIn.ts +6 -4
  76. package/src/commands/wrapInList.ts +6 -4
  77. package/src/extensions/clipboardTextSerializer.ts +2 -4
  78. package/src/extensions/focusEvents.ts +2 -6
  79. package/src/extensions/keymap.ts +54 -50
  80. package/src/extensions/paste.ts +0 -1
  81. package/src/extensions/tabindex.ts +1 -1
  82. package/src/helpers/combineTransactionSteps.ts +1 -4
  83. package/src/helpers/createChainableState.ts +1 -4
  84. package/src/helpers/createDocument.ts +1 -3
  85. package/src/helpers/createNodeFromContent.ts +4 -10
  86. package/src/helpers/findChildrenInRange.ts +1 -5
  87. package/src/helpers/findParentNode.ts +3 -1
  88. package/src/helpers/flattenExtensions.ts +30 -0
  89. package/src/helpers/getAttributes.ts +1 -4
  90. package/src/helpers/getAttributesFromExtensions.ts +28 -37
  91. package/src/helpers/getChangedRanges.ts +13 -11
  92. package/src/helpers/getExtensionField.ts +1 -4
  93. package/src/helpers/getMarkAttributes.ts +1 -4
  94. package/src/helpers/getMarkRange.ts +5 -15
  95. package/src/helpers/getMarkType.ts +1 -3
  96. package/src/helpers/getNodeAttributes.ts +1 -4
  97. package/src/helpers/getNodeType.ts +1 -3
  98. package/src/helpers/getRenderedAttributes.ts +1 -3
  99. package/src/helpers/getSchema.ts +2 -2
  100. package/src/helpers/getSchemaByResolvedExtensions.ts +45 -77
  101. package/src/helpers/getSplittedAttributes.ts +4 -4
  102. package/src/helpers/getTextContentFromNodes.ts +8 -11
  103. package/src/helpers/index.ts +4 -0
  104. package/src/helpers/injectExtensionAttributesToParseRule.ts +1 -1
  105. package/src/helpers/isActive.ts +1 -5
  106. package/src/helpers/isExtensionRulesEnabled.ts +1 -3
  107. package/src/helpers/isNodeEmpty.ts +2 -2
  108. package/src/helpers/resolveExtensions.ts +25 -0
  109. package/src/helpers/resolveFocusPosition.ts +3 -14
  110. package/src/helpers/rewriteUnknownContent.ts +149 -0
  111. package/src/helpers/sortExtensions.ts +26 -0
  112. package/src/inputRules/markInputRule.ts +1 -5
  113. package/src/inputRules/nodeInputRule.ts +2 -9
  114. package/src/inputRules/textInputRule.ts +1 -4
  115. package/src/inputRules/textblockTypeInputRule.ts +2 -8
  116. package/src/inputRules/wrappingInputRule.ts +13 -19
  117. package/src/pasteRules/markPasteRule.ts +1 -3
  118. package/src/pasteRules/nodePasteRule.ts +2 -8
  119. package/src/pasteRules/textPasteRule.ts +1 -4
  120. package/src/types.ts +212 -172
  121. package/src/utilities/createStyleTag.ts +3 -1
  122. package/src/utilities/deleteProps.ts +7 -11
  123. package/src/utilities/findDuplicates.ts +4 -1
  124. package/src/utilities/isFunction.ts +1 -0
  125. package/src/utilities/isMacOS.ts +1 -3
  126. package/src/utilities/isiOS.ts +5 -10
  127. package/src/utilities/mergeAttributes.ts +16 -6
  128. package/src/utilities/removeDuplicates.ts +1 -3
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/core",
3
3
  "description": "headless rich text editor",
4
- "version": "3.0.0-next.3",
4
+ "version": "3.0.0-next.4",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -18,7 +18,10 @@
18
18
  "type": "module",
19
19
  "exports": {
20
20
  ".": {
21
- "types": "./dist/index.d.ts",
21
+ "types": {
22
+ "import": "./dist/index.d.ts",
23
+ "require": "./dist/index.d.cts"
24
+ },
22
25
  "import": "./dist/index.js",
23
26
  "require": "./dist/index.cjs"
24
27
  }
@@ -31,7 +34,7 @@
31
34
  "dist"
32
35
  ],
33
36
  "devDependencies": {
34
- "@tiptap/pm": "^3.0.0-next.3"
37
+ "@tiptap/pm": "^3.0.0-next.4"
35
38
  },
36
39
  "peerDependencies": {
37
40
  "@tiptap/pm": "^3.0.0-next.1"
@@ -43,6 +46,7 @@
43
46
  },
44
47
  "sideEffects": false,
45
48
  "scripts": {
46
- "build": "tsup"
49
+ "build": "tsup",
50
+ "lint": "prettier ./src/ --check && eslint --cache --quiet --no-error-on-unmatched-pattern ./src/"
47
51
  }
48
- }
52
+ }
@@ -2,9 +2,7 @@ import { EditorState, Transaction } from '@tiptap/pm/state'
2
2
 
3
3
  import { Editor } from './Editor.js'
4
4
  import { createChainableState } from './helpers/createChainableState.js'
5
- import {
6
- AnyCommands, CanCommands, ChainedCommands, CommandProps, SingleCommands,
7
- } from './types.js'
5
+ import { AnyCommands, CanCommands, ChainedCommands, CommandProps, SingleCommands } from './types.js'
8
6
 
9
7
  export class CommandManager {
10
8
  editor: Editor
@@ -66,12 +64,7 @@ export class CommandManager {
66
64
  const tr = startTr || state.tr
67
65
 
68
66
  const run = () => {
69
- if (
70
- !hasStartTransaction
71
- && shouldDispatch
72
- && !tr.getMeta('preventDispatch')
73
- && !this.hasCustomState
74
- ) {
67
+ if (!hasStartTransaction && shouldDispatch && !tr.getMeta('preventDispatch') && !this.hasCustomState) {
75
68
  view.dispatch(tr)
76
69
  }
77
70
 
package/src/Editor.ts CHANGED
@@ -1,19 +1,19 @@
1
- import {
2
- MarkType,
3
- Node as ProseMirrorNode,
4
- NodeType,
5
- Schema,
6
- } from '@tiptap/pm/model'
7
- import {
8
- EditorState, Plugin, PluginKey, Transaction,
9
- } from '@tiptap/pm/state'
1
+ /* eslint-disable @typescript-eslint/no-empty-object-type */
2
+ import { MarkType, Node as ProseMirrorNode, NodeType, Schema } from '@tiptap/pm/model'
3
+ import { EditorState, Plugin, PluginKey, Transaction } from '@tiptap/pm/state'
10
4
  import { EditorView } from '@tiptap/pm/view'
11
5
 
12
6
  import { CommandManager } from './CommandManager.js'
13
7
  import { EventEmitter } from './EventEmitter.js'
14
8
  import { ExtensionManager } from './ExtensionManager.js'
15
9
  import {
16
- ClipboardTextSerializer, Commands, Drop, Editable, FocusEvents, Keymap, Paste,
10
+ ClipboardTextSerializer,
11
+ Commands,
12
+ Drop,
13
+ Editable,
14
+ FocusEvents,
15
+ Keymap,
16
+ Paste,
17
17
  Tabindex,
18
18
  } from './extensions/index.js'
19
19
  import { createDocument } from './helpers/createDocument.js'
@@ -29,11 +29,13 @@ import { style } from './style.js'
29
29
  import {
30
30
  CanCommands,
31
31
  ChainedCommands,
32
+ DocumentType,
32
33
  EditorEvents,
33
34
  EditorOptions,
34
- JSONContent,
35
+ NodeType as TNodeType,
35
36
  SingleCommands,
36
37
  TextSerializer,
38
+ TextType as TTextType,
37
39
  } from './types.js'
38
40
  import { createStyleTag } from './utilities/createStyleTag.js'
39
41
  import { isFunction } from './utilities/isFunction.js'
@@ -93,7 +95,9 @@ export class Editor extends EventEmitter<EditorEvents> {
93
95
  onFocus: () => null,
94
96
  onBlur: () => null,
95
97
  onDestroy: () => null,
96
- onContentError: ({ error }) => { throw error },
98
+ onContentError: ({ error }) => {
99
+ throw error
100
+ },
97
101
  onPaste: () => null,
98
102
  onDrop: () => null,
99
103
  }
@@ -196,7 +200,7 @@ export class Editor extends EventEmitter<EditorEvents> {
196
200
  this.setOptions({ editable })
197
201
 
198
202
  if (emitUpdate) {
199
- this.emit('update', { editor: this, transaction: this.state.tr })
203
+ this.emit('update', { editor: this, transaction: this.state.tr, appendedTransactions: [] })
200
204
  }
201
205
  }
202
206
 
@@ -245,15 +249,17 @@ export class Editor extends EventEmitter<EditorEvents> {
245
249
  * @param nameOrPluginKeyToRemove The plugins name
246
250
  * @returns The new editor state or undefined if the editor is destroyed
247
251
  */
248
- public unregisterPlugin(nameOrPluginKeyToRemove: string | PluginKey | (string | PluginKey)[]): EditorState | undefined {
252
+ public unregisterPlugin(
253
+ nameOrPluginKeyToRemove: string | PluginKey | (string | PluginKey)[],
254
+ ): EditorState | undefined {
249
255
  if (this.isDestroyed) {
250
256
  return undefined
251
257
  }
252
258
 
253
259
  const prevPlugins = this.state.plugins
254
- let plugins = prevPlugins;
260
+ let plugins = prevPlugins
255
261
 
256
- ([] as (string | PluginKey)[]).concat(nameOrPluginKeyToRemove).forEach(nameOrPluginKey => {
262
+ ;([] as (string | PluginKey)[]).concat(nameOrPluginKeyToRemove).forEach(nameOrPluginKey => {
257
263
  // @ts-ignore
258
264
  const name = typeof nameOrPluginKey === 'string' ? `${nameOrPluginKey}$` : nameOrPluginKey.key
259
265
 
@@ -279,24 +285,27 @@ export class Editor extends EventEmitter<EditorEvents> {
279
285
  * Creates an extension manager.
280
286
  */
281
287
  private createExtensionManager(): void {
282
-
283
- const coreExtensions = this.options.enableCoreExtensions ? [
284
- Editable,
285
- ClipboardTextSerializer.configure({
286
- blockSeparator: this.options.coreExtensionOptions?.clipboardTextSerializer?.blockSeparator,
287
- }),
288
- Commands,
289
- FocusEvents,
290
- Keymap,
291
- Tabindex,
292
- Drop,
293
- Paste,
294
- ].filter(ext => {
295
- if (typeof this.options.enableCoreExtensions === 'object') {
296
- return this.options.enableCoreExtensions[ext.name as keyof typeof this.options.enableCoreExtensions] !== false
297
- }
298
- return true
299
- }) : []
288
+ const coreExtensions = this.options.enableCoreExtensions
289
+ ? [
290
+ Editable,
291
+ ClipboardTextSerializer.configure({
292
+ blockSeparator: this.options.coreExtensionOptions?.clipboardTextSerializer?.blockSeparator,
293
+ }),
294
+ Commands,
295
+ FocusEvents,
296
+ Keymap,
297
+ Tabindex,
298
+ Drop,
299
+ Paste,
300
+ ].filter(ext => {
301
+ if (typeof this.options.enableCoreExtensions === 'object') {
302
+ return (
303
+ this.options.enableCoreExtensions[ext.name as keyof typeof this.options.enableCoreExtensions] !== false
304
+ )
305
+ }
306
+ return true
307
+ })
308
+ : []
300
309
  const allExtensions = [...coreExtensions, ...this.options.extensions].filter(extension => {
301
310
  return ['extension', 'node', 'mark'].includes(extension?.type)
302
311
  })
@@ -327,14 +336,14 @@ export class Editor extends EventEmitter<EditorEvents> {
327
336
  let doc: ProseMirrorNode
328
337
 
329
338
  try {
330
- doc = createDocument(
331
- this.options.content,
332
- this.schema,
333
- this.options.parseOptions,
334
- { errorOnInvalidContent: this.options.enableContentCheck },
335
- )
339
+ doc = createDocument(this.options.content, this.schema, this.options.parseOptions, {
340
+ errorOnInvalidContent: this.options.enableContentCheck,
341
+ })
336
342
  } catch (e) {
337
- if (!(e instanceof Error) || !['[tiptap error]: Invalid JSON content', '[tiptap error]: Invalid HTML content'].includes(e.message)) {
343
+ if (
344
+ !(e instanceof Error) ||
345
+ !['[tiptap error]: Invalid JSON content', '[tiptap error]: Invalid HTML content'].includes(e.message)
346
+ ) {
338
347
  // Not the content error we were expecting
339
348
  throw e
340
349
  }
@@ -354,12 +363,9 @@ export class Editor extends EventEmitter<EditorEvents> {
354
363
  })
355
364
 
356
365
  // Content is invalid, but attempt to create it anyway, stripping out the invalid parts
357
- doc = createDocument(
358
- this.options.content,
359
- this.schema,
360
- this.options.parseOptions,
361
- { errorOnInvalidContent: false },
362
- )
366
+ doc = createDocument(this.options.content, this.schema, this.options.parseOptions, {
367
+ errorOnInvalidContent: false,
368
+ })
363
369
  }
364
370
  const selection = resolveFocusPosition(doc, this.options.autofocus)
365
371
 
@@ -420,7 +426,7 @@ export class Editor extends EventEmitter<EditorEvents> {
420
426
 
421
427
  private capturedTransaction: Transaction | null = null
422
428
 
423
- public captureTransaction(fn: Function) {
429
+ public captureTransaction(fn: () => void) {
424
430
  this.isCapturingTransaction = true
425
431
  fn()
426
432
  this.isCapturingTransaction = false
@@ -456,18 +462,30 @@ export class Editor extends EventEmitter<EditorEvents> {
456
462
  return
457
463
  }
458
464
 
459
- const state = this.state.apply(transaction)
465
+ // Apply transaction and get resulting state and transactions
466
+ const { state, transactions } = this.state.applyTransaction(transaction)
460
467
  const selectionHasChanged = !this.state.selection.eq(state.selection)
468
+ const rootTrWasApplied = transactions.includes(transaction)
469
+ const prevState = this.state
461
470
 
462
471
  this.emit('beforeTransaction', {
463
472
  editor: this,
464
473
  transaction,
465
474
  nextState: state,
466
475
  })
476
+
477
+ // If transaction was filtered out, we can return early
478
+ if (!rootTrWasApplied) {
479
+ return
480
+ }
481
+
467
482
  this.view.updateState(state)
483
+
484
+ // Emit transaction event with appended transactions info
468
485
  this.emit('transaction', {
469
486
  editor: this,
470
487
  transaction,
488
+ appendedTransactions: transactions.slice(1),
471
489
  })
472
490
 
473
491
  if (selectionHasChanged) {
@@ -477,14 +495,17 @@ export class Editor extends EventEmitter<EditorEvents> {
477
495
  })
478
496
  }
479
497
 
480
- const focus = transaction.getMeta('focus')
481
- const blur = transaction.getMeta('blur')
498
+ // Only emit the latest between focus and blur events
499
+ const mostRecentFocusTr = transactions.findLast(tr => tr.getMeta('focus') || tr.getMeta('blur'))
500
+ const focus = mostRecentFocusTr?.getMeta('focus')
501
+ const blur = mostRecentFocusTr?.getMeta('blur')
482
502
 
483
503
  if (focus) {
484
504
  this.emit('focus', {
485
505
  editor: this,
486
506
  event: focus.event,
487
- transaction,
507
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
508
+ transaction: mostRecentFocusTr!,
488
509
  })
489
510
  }
490
511
 
@@ -492,17 +513,24 @@ export class Editor extends EventEmitter<EditorEvents> {
492
513
  this.emit('blur', {
493
514
  editor: this,
494
515
  event: blur.event,
495
- transaction,
516
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
517
+ transaction: mostRecentFocusTr!,
496
518
  })
497
519
  }
498
520
 
499
- if (!transaction.docChanged || transaction.getMeta('preventUpdate')) {
521
+ // Compare states for update event
522
+ if (
523
+ transaction.getMeta('preventUpdate') ||
524
+ !transactions.some(tr => tr.docChanged) ||
525
+ prevState.doc.eq(state.doc)
526
+ ) {
500
527
  return
501
528
  }
502
529
 
503
530
  this.emit('update', {
504
531
  editor: this,
505
532
  transaction,
533
+ appendedTransactions: transactions.slice(1),
506
534
  })
507
535
  }
508
536
 
@@ -532,7 +560,10 @@ export class Editor extends EventEmitter<EditorEvents> {
532
560
  /**
533
561
  * Get the document as JSON.
534
562
  */
535
- public getJSON(): JSONContent {
563
+ public getJSON(): DocumentType<
564
+ Record<string, any> | undefined,
565
+ TNodeType<string, undefined | Record<string, any>, any, (TNodeType | TTextType)[]>[]
566
+ > {
536
567
  return this.state.doc.toJSON()
537
568
  }
538
569
 
@@ -546,10 +577,7 @@ export class Editor extends EventEmitter<EditorEvents> {
546
577
  /**
547
578
  * Get the document as text.
548
579
  */
549
- public getText(options?: {
550
- blockSeparator?: string
551
- textSerializers?: Record<string, TextSerializer>
552
- }): string {
580
+ public getText(options?: { blockSeparator?: string; textSerializers?: Record<string, TextSerializer> }): string {
553
581
  const { blockSeparator = '\n\n', textSerializers = {} } = options || {}
554
582
 
555
583
  return getText(this.state.doc, {
@@ -568,19 +596,6 @@ export class Editor extends EventEmitter<EditorEvents> {
568
596
  return isNodeEmpty(this.state.doc)
569
597
  }
570
598
 
571
- /**
572
- * Get the number of characters for the current document.
573
- *
574
- * @deprecated
575
- */
576
- public getCharacterCount(): number {
577
- console.warn(
578
- '[tiptap warn]: "editor.getCharacterCount()" is deprecated. Please use "editor.storage.characterCount.characters()" instead.',
579
- )
580
-
581
- return this.state.doc.content.size - 2
582
- }
583
-
584
599
  /**
585
600
  * Destroy the editor.
586
601
  */
@@ -1,16 +1,13 @@
1
1
  type StringKeyOf<T> = Extract<keyof T, string>
2
- type CallbackType<
3
- T extends Record<string, any>,
4
- EventName extends StringKeyOf<T>,
5
- > = T[EventName] extends any[] ? T[EventName] : [T[EventName]]
6
- type CallbackFunction<
7
- T extends Record<string, any>,
8
- EventName extends StringKeyOf<T>,
9
- > = (...props: CallbackType<T, EventName>) => any
2
+ type CallbackType<T extends Record<string, any>, EventName extends StringKeyOf<T>> = T[EventName] extends any[]
3
+ ? T[EventName]
4
+ : [T[EventName]]
5
+ type CallbackFunction<T extends Record<string, any>, EventName extends StringKeyOf<T>> = (
6
+ ...props: CallbackType<T, EventName>
7
+ ) => any
10
8
 
11
9
  export class EventEmitter<T extends Record<string, any>> {
12
-
13
- private callbacks: { [key: string]: Function[] } = {}
10
+ private callbacks: { [key: string]: Array<(...args: any[]) => void> } = {}
14
11
 
15
12
  public on<EventName extends StringKeyOf<T>>(event: EventName, fn: CallbackFunction<T, EventName>): this {
16
13
  if (!this.callbacks[event]) {
package/src/Extension.ts CHANGED
@@ -7,14 +7,7 @@ import { InputRule } from './InputRule.js'
7
7
  import { Mark } from './Mark.js'
8
8
  import { Node } from './Node.js'
9
9
  import { PasteRule } from './PasteRule.js'
10
- import {
11
- AnyConfig,
12
- Extensions,
13
- GlobalAttributes,
14
- KeyboardShortcutCommand,
15
- ParentConfig,
16
- RawCommands,
17
- } from './types.js'
10
+ import { AnyConfig, Extensions, GlobalAttributes, KeyboardShortcutCommand, ParentConfig, RawCommands } from './types.js'
18
11
  import { callOrReturn } from './utilities/callOrReturn.js'
19
12
  import { mergeDeep } from './utilities/mergeDeep.js'
20
13
 
@@ -443,12 +436,13 @@ export class Extension<Options = any, Storage = any> {
443
436
  )
444
437
  }
445
438
 
446
- this.storage = callOrReturn(
447
- getExtensionField<AnyConfig['addStorage']>(this, 'addStorage', {
448
- name: this.name,
449
- options: this.options,
450
- }),
451
- ) || {}
439
+ this.storage =
440
+ callOrReturn(
441
+ getExtensionField<AnyConfig['addStorage']>(this, 'addStorage', {
442
+ name: this.name,
443
+ options: this.options,
444
+ }),
445
+ ) || {}
452
446
  }
453
447
 
454
448
  static create<O = any, S = any>(config: Partial<ExtensionConfig<O, S>> = {}) {
@@ -4,21 +4,25 @@ import { Plugin } from '@tiptap/pm/state'
4
4
  import { NodeViewConstructor } from '@tiptap/pm/view'
5
5
 
6
6
  import type { Editor } from './Editor.js'
7
- import { getAttributesFromExtensions } from './helpers/getAttributesFromExtensions.js'
8
- import { getExtensionField } from './helpers/getExtensionField.js'
9
- import { getNodeType } from './helpers/getNodeType.js'
10
- import { getRenderedAttributes } from './helpers/getRenderedAttributes.js'
11
- import { getSchemaByResolvedExtensions } from './helpers/getSchemaByResolvedExtensions.js'
12
- import { getSchemaTypeByName } from './helpers/getSchemaTypeByName.js'
13
- import { isExtensionRulesEnabled } from './helpers/isExtensionRulesEnabled.js'
14
- import { splitExtensions } from './helpers/splitExtensions.js'
7
+ import {
8
+ flattenExtensions,
9
+ getAttributesFromExtensions,
10
+ getExtensionField,
11
+ getNodeType,
12
+ getRenderedAttributes,
13
+ getSchemaByResolvedExtensions,
14
+ getSchemaTypeByName,
15
+ isExtensionRulesEnabled,
16
+ resolveExtensions,
17
+ sortExtensions,
18
+ splitExtensions,
19
+ } from './helpers/index.js'
15
20
  import type { NodeConfig } from './index.js'
16
21
  import { InputRule, inputRulesPlugin } from './InputRule.js'
17
22
  import { Mark } from './Mark.js'
18
23
  import { PasteRule, pasteRulesPlugin } from './PasteRule.js'
19
24
  import { AnyConfig, Extensions, RawCommands } from './types.js'
20
25
  import { callOrReturn } from './utilities/callOrReturn.js'
21
- import { findDuplicates } from './utilities/findDuplicates.js'
22
26
 
23
27
  export class ExtensionManager {
24
28
  editor: Editor
@@ -31,87 +35,16 @@ export class ExtensionManager {
31
35
 
32
36
  constructor(extensions: Extensions, editor: Editor) {
33
37
  this.editor = editor
34
- this.extensions = ExtensionManager.resolve(extensions)
38
+ this.extensions = resolveExtensions(extensions)
35
39
  this.schema = getSchemaByResolvedExtensions(this.extensions, editor)
36
40
  this.setupExtensions()
37
41
  }
38
42
 
39
- /**
40
- * Returns a flattened and sorted extension list while
41
- * also checking for duplicated extensions and warns the user.
42
- * @param extensions An array of Tiptap extensions
43
- * @returns An flattened and sorted array of Tiptap extensions
44
- */
45
- static resolve(extensions: Extensions): Extensions {
46
- const resolvedExtensions = ExtensionManager.sort(ExtensionManager.flatten(extensions))
47
- const duplicatedNames = findDuplicates(resolvedExtensions.map(extension => extension.name))
48
-
49
- if (duplicatedNames.length) {
50
- console.warn(
51
- `[tiptap warn]: Duplicate extension names found: [${duplicatedNames
52
- .map(item => `'${item}'`)
53
- .join(', ')}]. This can lead to issues.`,
54
- )
55
- }
56
-
57
- return resolvedExtensions
58
- }
59
-
60
- /**
61
- * Create a flattened array of extensions by traversing the `addExtensions` field.
62
- * @param extensions An array of Tiptap extensions
63
- * @returns A flattened array of Tiptap extensions
64
- */
65
- static flatten(extensions: Extensions): Extensions {
66
- return (
67
- extensions
68
- .map(extension => {
69
- const context = {
70
- name: extension.name,
71
- options: extension.options,
72
- storage: extension.storage,
73
- }
74
-
75
- const addExtensions = getExtensionField<AnyConfig['addExtensions']>(
76
- extension,
77
- 'addExtensions',
78
- context,
79
- )
43
+ static resolve = resolveExtensions
80
44
 
81
- if (addExtensions) {
82
- return [extension, ...this.flatten(addExtensions())]
83
- }
45
+ static sort = sortExtensions
84
46
 
85
- return extension
86
- })
87
- // `Infinity` will break TypeScript so we set a number that is probably high enough
88
- .flat(10)
89
- )
90
- }
91
-
92
- /**
93
- * Sort extensions by priority.
94
- * @param extensions An array of Tiptap extensions
95
- * @returns A sorted array of Tiptap extensions by priority
96
- */
97
- static sort(extensions: Extensions): Extensions {
98
- const defaultPriority = 100
99
-
100
- return extensions.sort((a, b) => {
101
- const priorityA = getExtensionField<AnyConfig['priority']>(a, 'priority') || defaultPriority
102
- const priorityB = getExtensionField<AnyConfig['priority']>(b, 'priority') || defaultPriority
103
-
104
- if (priorityA > priorityB) {
105
- return -1
106
- }
107
-
108
- if (priorityA < priorityB) {
109
- return 1
110
- }
111
-
112
- return 0
113
- })
114
- }
47
+ static flatten = flattenExtensions
115
48
 
116
49
  /**
117
50
  * Get all commands from the extensions.
@@ -127,11 +60,7 @@ export class ExtensionManager {
127
60
  type: getSchemaTypeByName(extension.name, this.schema),
128
61
  }
129
62
 
130
- const addCommands = getExtensionField<AnyConfig['addCommands']>(
131
- extension,
132
- 'addCommands',
133
- context,
134
- )
63
+ const addCommands = getExtensionField<AnyConfig['addCommands']>(extension, 'addCommands', context)
135
64
 
136
65
  if (!addCommands) {
137
66
  return commands
@@ -156,7 +85,7 @@ export class ExtensionManager {
156
85
  // so it feels more natural to run plugins at the end of an array first.
157
86
  // That’s why we have to reverse the `extensions` array and sort again
158
87
  // based on the `priority` option.
159
- const extensions = ExtensionManager.sort([...this.extensions].reverse())
88
+ const extensions = sortExtensions([...this.extensions].reverse())
160
89
 
161
90
  const inputRules: InputRule[] = []
162
91
  const pasteRules: PasteRule[] = []
@@ -200,21 +129,13 @@ export class ExtensionManager {
200
129
 
201
130
  plugins.push(keyMapPlugin)
202
131
 
203
- const addInputRules = getExtensionField<AnyConfig['addInputRules']>(
204
- extension,
205
- 'addInputRules',
206
- context,
207
- )
132
+ const addInputRules = getExtensionField<AnyConfig['addInputRules']>(extension, 'addInputRules', context)
208
133
 
209
134
  if (isExtensionRulesEnabled(extension, editor.options.enableInputRules) && addInputRules) {
210
135
  inputRules.push(...addInputRules())
211
136
  }
212
137
 
213
- const addPasteRules = getExtensionField<AnyConfig['addPasteRules']>(
214
- extension,
215
- 'addPasteRules',
216
- context,
217
- )
138
+ const addPasteRules = getExtensionField<AnyConfig['addPasteRules']>(extension, 'addPasteRules', context)
218
139
 
219
140
  if (isExtensionRulesEnabled(extension, editor.options.enablePasteRules) && addPasteRules) {
220
141
  pasteRules.push(...addPasteRules())
@@ -269,9 +190,7 @@ export class ExtensionManager {
269
190
  nodeExtensions
270
191
  .filter(extension => !!getExtensionField(extension, 'addNodeView'))
271
192
  .map(extension => {
272
- const extensionAttributes = this.attributes.filter(
273
- attribute => attribute.type === extension.name,
274
- )
193
+ const extensionAttributes = this.attributes.filter(attribute => attribute.type === extension.name)
275
194
  const context = {
276
195
  name: extension.name,
277
196
  options: extension.options,
@@ -279,23 +198,13 @@ export class ExtensionManager {
279
198
  editor,
280
199
  type: getNodeType(extension.name, this.schema),
281
200
  }
282
- const addNodeView = getExtensionField<NodeConfig['addNodeView']>(
283
- extension,
284
- 'addNodeView',
285
- context,
286
- )
201
+ const addNodeView = getExtensionField<NodeConfig['addNodeView']>(extension, 'addNodeView', context)
287
202
 
288
203
  if (!addNodeView) {
289
204
  return []
290
205
  }
291
206
 
292
- const nodeview: NodeViewConstructor = (
293
- node,
294
- view,
295
- getPos,
296
- decorations,
297
- innerDecorations,
298
- ) => {
207
+ const nodeview: NodeViewConstructor = (node, view, getPos, decorations, innerDecorations) => {
299
208
  const HTMLAttributes = getRenderedAttributes(node, extensionAttributes)
300
209
 
301
210
  return addNodeView()({
@@ -342,11 +251,7 @@ export class ExtensionManager {
342
251
  }
343
252
  }
344
253
 
345
- const onBeforeCreate = getExtensionField<AnyConfig['onBeforeCreate']>(
346
- extension,
347
- 'onBeforeCreate',
348
- context,
349
- )
254
+ const onBeforeCreate = getExtensionField<AnyConfig['onBeforeCreate']>(extension, 'onBeforeCreate', context)
350
255
  const onCreate = getExtensionField<AnyConfig['onCreate']>(extension, 'onCreate', context)
351
256
  const onUpdate = getExtensionField<AnyConfig['onUpdate']>(extension, 'onUpdate', context)
352
257
  const onSelectionUpdate = getExtensionField<AnyConfig['onSelectionUpdate']>(
@@ -354,11 +259,7 @@ export class ExtensionManager {
354
259
  'onSelectionUpdate',
355
260
  context,
356
261
  )
357
- const onTransaction = getExtensionField<AnyConfig['onTransaction']>(
358
- extension,
359
- 'onTransaction',
360
- context,
361
- )
262
+ const onTransaction = getExtensionField<AnyConfig['onTransaction']>(extension, 'onTransaction', context)
362
263
  const onFocus = getExtensionField<AnyConfig['onFocus']>(extension, 'onFocus', context)
363
264
  const onBlur = getExtensionField<AnyConfig['onBlur']>(extension, 'onBlur', context)
364
265
  const onDestroy = getExtensionField<AnyConfig['onDestroy']>(extension, 'onDestroy', context)