@tiptap/core 2.0.0-beta.21 → 2.0.0-beta.210

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 (289) hide show
  1. package/README.md +2 -2
  2. package/dist/index.cjs +4311 -0
  3. package/dist/index.d.ts +2330 -0
  4. package/dist/index.js +4311 -0
  5. package/package.json +39 -25
  6. package/src/CommandManager.ts +76 -86
  7. package/src/Editor.ts +120 -81
  8. package/src/EventEmitter.ts +14 -4
  9. package/src/Extension.ts +280 -108
  10. package/src/ExtensionManager.ts +254 -108
  11. package/src/InputRule.ts +260 -0
  12. package/src/Mark.ts +398 -147
  13. package/src/Node.ts +437 -171
  14. package/src/NodeView.ts +132 -63
  15. package/src/PasteRule.ts +240 -0
  16. package/src/Tracker.ts +38 -0
  17. package/src/commands/blur.ts +12 -6
  18. package/src/commands/clearContent.ts +3 -3
  19. package/src/commands/clearNodes.ts +31 -19
  20. package/src/commands/command.ts +2 -2
  21. package/src/commands/createParagraphNear.ts +5 -4
  22. package/src/commands/deleteCurrentNode.ts +41 -0
  23. package/src/commands/deleteNode.ts +37 -0
  24. package/src/commands/deleteRange.ts +3 -3
  25. package/src/commands/deleteSelection.ts +5 -4
  26. package/src/commands/enter.ts +3 -3
  27. package/src/commands/exitCode.ts +5 -4
  28. package/src/commands/extendMarkRange.ts +16 -12
  29. package/src/commands/first.ts +3 -3
  30. package/src/commands/focus.ts +47 -44
  31. package/src/commands/forEach.ts +24 -0
  32. package/src/commands/index.ts +50 -0
  33. package/src/commands/insertContent.ts +17 -24
  34. package/src/commands/insertContentAt.ts +94 -0
  35. package/src/commands/join.ts +53 -0
  36. package/src/commands/keyboardShortcut.ts +6 -6
  37. package/src/commands/lift.ts +8 -7
  38. package/src/commands/liftEmptyBlock.ts +5 -4
  39. package/src/commands/liftListItem.ts +7 -6
  40. package/src/commands/newlineInCode.ts +5 -4
  41. package/src/commands/resetAttributes.ts +18 -12
  42. package/src/commands/scrollIntoView.ts +3 -3
  43. package/src/commands/selectAll.ts +8 -6
  44. package/src/commands/selectNodeBackward.ts +5 -4
  45. package/src/commands/selectNodeForward.ts +5 -4
  46. package/src/commands/selectParentNode.ts +5 -4
  47. package/src/commands/selectTextblockEnd.ts +20 -0
  48. package/src/commands/selectTextblockStart.ts +20 -0
  49. package/src/commands/setContent.ts +9 -16
  50. package/src/commands/setMark.ts +90 -14
  51. package/src/commands/setMeta.ts +18 -0
  52. package/src/commands/setNode.ts +32 -8
  53. package/src/commands/setNodeSelection.ts +27 -0
  54. package/src/commands/setTextSelection.ts +31 -0
  55. package/src/commands/sinkListItem.ts +7 -6
  56. package/src/commands/splitBlock.ts +31 -41
  57. package/src/commands/splitListItem.ts +46 -29
  58. package/src/commands/toggleList.ts +88 -20
  59. package/src/commands/toggleMark.ts +19 -8
  60. package/src/commands/toggleNode.ts +11 -6
  61. package/src/commands/toggleWrap.ts +10 -10
  62. package/src/commands/undoInputRule.ts +34 -5
  63. package/src/commands/unsetAllMarks.ts +7 -11
  64. package/src/commands/unsetMark.ts +36 -23
  65. package/src/commands/updateAttributes.ts +27 -15
  66. package/src/commands/wrapIn.ts +7 -12
  67. package/src/commands/wrapInList.ts +7 -6
  68. package/src/extensions/clipboardTextSerializer.ts +15 -36
  69. package/src/extensions/commands.ts +3 -147
  70. package/src/extensions/editable.ts +2 -1
  71. package/src/extensions/focusEvents.ts +4 -6
  72. package/src/extensions/index.ts +1 -0
  73. package/src/extensions/keymap.ts +106 -13
  74. package/src/extensions/tabindex.ts +18 -0
  75. package/src/helpers/combineTransactionSteps.ts +21 -0
  76. package/src/helpers/createChainableState.ts +38 -0
  77. package/src/helpers/createDocument.ts +5 -6
  78. package/src/helpers/createNodeFromContent.ts +20 -28
  79. package/src/helpers/defaultBlockAt.ts +13 -0
  80. package/src/helpers/findChildren.ts +18 -0
  81. package/src/helpers/findChildrenInRange.ts +36 -0
  82. package/src/helpers/findParentNode.ts +4 -3
  83. package/src/helpers/findParentNodeClosestToPos.ts +13 -7
  84. package/src/helpers/generateHTML.ts +7 -6
  85. package/src/helpers/generateJSON.ts +12 -0
  86. package/src/helpers/generateText.ts +27 -0
  87. package/src/helpers/getAttributes.ts +26 -0
  88. package/src/helpers/getAttributesFromExtensions.ts +42 -14
  89. package/src/helpers/getChangedRanges.ts +83 -0
  90. package/src/helpers/getDebugJSON.ts +54 -0
  91. package/src/helpers/getExtensionField.ts +25 -0
  92. package/src/helpers/getHTMLFromFragment.ts +5 -6
  93. package/src/helpers/getMarkAttributes.ts +18 -11
  94. package/src/helpers/getMarkRange.ts +41 -8
  95. package/src/helpers/getMarkType.ts +8 -2
  96. package/src/helpers/getMarksBetween.ts +34 -10
  97. package/src/helpers/getNodeAttributes.ts +14 -13
  98. package/src/helpers/getNodeType.ts +8 -2
  99. package/src/helpers/getRenderedAttributes.ts +9 -7
  100. package/src/helpers/getSchema.ts +7 -133
  101. package/src/helpers/getSchemaByResolvedExtensions.ts +192 -0
  102. package/src/helpers/getSchemaTypeByName.ts +3 -11
  103. package/src/helpers/getSchemaTypeNameByName.ts +2 -2
  104. package/src/helpers/getSplittedAttributes.ts +4 -4
  105. package/src/helpers/getText.ts +19 -0
  106. package/src/helpers/getTextBetween.ts +46 -0
  107. package/src/helpers/getTextContentFromNodes.ts +26 -0
  108. package/src/helpers/getTextSerializersFromSchema.ts +11 -0
  109. package/src/helpers/index.ts +33 -0
  110. package/src/helpers/injectExtensionAttributesToParseRule.ts +22 -23
  111. package/src/helpers/isActive.ts +10 -6
  112. package/src/helpers/isExtensionRulesEnabled.ts +15 -0
  113. package/src/helpers/isList.ts +14 -7
  114. package/src/helpers/isMarkActive.ts +49 -27
  115. package/src/helpers/isNodeActive.ts +29 -39
  116. package/src/helpers/isNodeEmpty.ts +2 -2
  117. package/src/helpers/isNodeSelection.ts +3 -4
  118. package/src/helpers/isTextSelection.ts +3 -4
  119. package/src/helpers/posToDOMRect.ts +35 -0
  120. package/src/helpers/resolveFocusPosition.ts +42 -0
  121. package/src/helpers/selectionToInsertionEnd.ts +3 -3
  122. package/src/helpers/splitExtensions.ts +3 -3
  123. package/src/index.ts +15 -24
  124. package/src/inputRules/index.ts +5 -0
  125. package/src/inputRules/markInputRule.ts +59 -40
  126. package/src/inputRules/nodeInputRule.ts +45 -12
  127. package/src/inputRules/textInputRule.ts +35 -0
  128. package/src/inputRules/textblockTypeInputRule.ts +37 -0
  129. package/src/inputRules/wrappingInputRule.ts +59 -0
  130. package/src/pasteRules/index.ts +3 -0
  131. package/src/pasteRules/markPasteRule.ts +61 -53
  132. package/src/pasteRules/nodePasteRule.ts +37 -0
  133. package/src/pasteRules/textPasteRule.ts +35 -0
  134. package/src/style.ts +16 -3
  135. package/src/types.ts +170 -97
  136. package/src/utilities/callOrReturn.ts +6 -3
  137. package/src/utilities/createStyleTag.ts +12 -1
  138. package/src/utilities/deleteProps.ts +2 -4
  139. package/src/utilities/elementFromString.ts +4 -5
  140. package/src/utilities/escapeForRegEx.ts +4 -0
  141. package/src/utilities/findDuplicates.ts +5 -0
  142. package/src/utilities/fromString.ts +2 -2
  143. package/src/utilities/index.ts +20 -0
  144. package/src/utilities/isEmptyObject.ts +2 -2
  145. package/src/utilities/isFunction.ts +3 -0
  146. package/src/utilities/isMacOS.ts +5 -0
  147. package/src/utilities/isNumber.ts +3 -0
  148. package/src/utilities/isPlainObject.ts +8 -5
  149. package/src/utilities/isRegExp.ts +3 -0
  150. package/src/utilities/isString.ts +3 -0
  151. package/src/utilities/isiOS.ts +12 -0
  152. package/src/utilities/mergeAttributes.ts +2 -3
  153. package/src/utilities/mergeDeep.ts +2 -3
  154. package/src/utilities/minMax.ts +1 -1
  155. package/src/utilities/objectIncludes.ts +17 -5
  156. package/src/utilities/removeDuplicates.ts +15 -0
  157. package/CHANGELOG.md +0 -365
  158. package/LICENSE.md +0 -21
  159. package/dist/packages/core/src/CommandManager.d.ts +0 -13
  160. package/dist/packages/core/src/Editor.d.ts +0 -142
  161. package/dist/packages/core/src/EventEmitter.d.ts +0 -7
  162. package/dist/packages/core/src/Extension.d.ts +0 -148
  163. package/dist/packages/core/src/ExtensionManager.d.ts +0 -24
  164. package/dist/packages/core/src/Mark.d.ts +0 -211
  165. package/dist/packages/core/src/Node.d.ts +0 -265
  166. package/dist/packages/core/src/NodeView.d.ts +0 -31
  167. package/dist/packages/core/src/commands/blur.d.ts +0 -12
  168. package/dist/packages/core/src/commands/clearContent.d.ts +0 -12
  169. package/dist/packages/core/src/commands/clearNodes.d.ts +0 -12
  170. package/dist/packages/core/src/commands/command.d.ts +0 -12
  171. package/dist/packages/core/src/commands/createParagraphNear.d.ts +0 -12
  172. package/dist/packages/core/src/commands/deleteRange.d.ts +0 -12
  173. package/dist/packages/core/src/commands/deleteSelection.d.ts +0 -12
  174. package/dist/packages/core/src/commands/enter.d.ts +0 -12
  175. package/dist/packages/core/src/commands/exitCode.d.ts +0 -12
  176. package/dist/packages/core/src/commands/extendMarkRange.d.ts +0 -13
  177. package/dist/packages/core/src/commands/first.d.ts +0 -12
  178. package/dist/packages/core/src/commands/focus.d.ts +0 -12
  179. package/dist/packages/core/src/commands/insertContent.d.ts +0 -12
  180. package/dist/packages/core/src/commands/insertHTML.d.ts +0 -12
  181. package/dist/packages/core/src/commands/insertNode.d.ts +0 -13
  182. package/dist/packages/core/src/commands/insertText.d.ts +0 -12
  183. package/dist/packages/core/src/commands/joinBackward.d.ts +0 -12
  184. package/dist/packages/core/src/commands/joinForward.d.ts +0 -12
  185. package/dist/packages/core/src/commands/keyboardShortcut.d.ts +0 -12
  186. package/dist/packages/core/src/commands/lift.d.ts +0 -13
  187. package/dist/packages/core/src/commands/liftEmptyBlock.d.ts +0 -12
  188. package/dist/packages/core/src/commands/liftListItem.d.ts +0 -13
  189. package/dist/packages/core/src/commands/newlineInCode.d.ts +0 -12
  190. package/dist/packages/core/src/commands/replace.d.ts +0 -13
  191. package/dist/packages/core/src/commands/replaceRange.d.ts +0 -13
  192. package/dist/packages/core/src/commands/resetAttributes.d.ts +0 -13
  193. package/dist/packages/core/src/commands/resetNodeAttributes.d.ts +0 -13
  194. package/dist/packages/core/src/commands/scrollIntoView.d.ts +0 -12
  195. package/dist/packages/core/src/commands/selectAll.d.ts +0 -12
  196. package/dist/packages/core/src/commands/selectNodeBackward.d.ts +0 -12
  197. package/dist/packages/core/src/commands/selectNodeForward.d.ts +0 -12
  198. package/dist/packages/core/src/commands/selectParentNode.d.ts +0 -12
  199. package/dist/packages/core/src/commands/setContent.d.ts +0 -12
  200. package/dist/packages/core/src/commands/setMark.d.ts +0 -13
  201. package/dist/packages/core/src/commands/setNode.d.ts +0 -13
  202. package/dist/packages/core/src/commands/sinkListItem.d.ts +0 -13
  203. package/dist/packages/core/src/commands/splitBlock.d.ts +0 -14
  204. package/dist/packages/core/src/commands/splitListItem.d.ts +0 -13
  205. package/dist/packages/core/src/commands/toggleList.d.ts +0 -13
  206. package/dist/packages/core/src/commands/toggleMark.d.ts +0 -13
  207. package/dist/packages/core/src/commands/toggleNode.d.ts +0 -13
  208. package/dist/packages/core/src/commands/toggleWrap.d.ts +0 -13
  209. package/dist/packages/core/src/commands/undoInputRule.d.ts +0 -12
  210. package/dist/packages/core/src/commands/unsetAllMarks.d.ts +0 -12
  211. package/dist/packages/core/src/commands/unsetMark.d.ts +0 -13
  212. package/dist/packages/core/src/commands/updateAttributes.d.ts +0 -13
  213. package/dist/packages/core/src/commands/updateNodeAttributes.d.ts +0 -13
  214. package/dist/packages/core/src/commands/wrapIn.d.ts +0 -13
  215. package/dist/packages/core/src/commands/wrapInList.d.ts +0 -13
  216. package/dist/packages/core/src/extensions/clipboardTextSerializer.d.ts +0 -2
  217. package/dist/packages/core/src/extensions/commands.d.ts +0 -100
  218. package/dist/packages/core/src/extensions/editable.d.ts +0 -2
  219. package/dist/packages/core/src/extensions/focusEvents.d.ts +0 -2
  220. package/dist/packages/core/src/extensions/index.d.ts +0 -5
  221. package/dist/packages/core/src/extensions/keymap.d.ts +0 -2
  222. package/dist/packages/core/src/helpers/createDocument.d.ts +0 -4
  223. package/dist/packages/core/src/helpers/createNodeFromContent.d.ts +0 -8
  224. package/dist/packages/core/src/helpers/findParentNode.d.ts +0 -9
  225. package/dist/packages/core/src/helpers/findParentNodeClosestToPos.d.ts +0 -8
  226. package/dist/packages/core/src/helpers/generateHTML.d.ts +0 -2
  227. package/dist/packages/core/src/helpers/getAttributesFromExtensions.d.ts +0 -6
  228. package/dist/packages/core/src/helpers/getHTMLFromFragment.d.ts +0 -2
  229. package/dist/packages/core/src/helpers/getMarkAttributes.d.ts +0 -4
  230. package/dist/packages/core/src/helpers/getMarkRange.d.ts +0 -3
  231. package/dist/packages/core/src/helpers/getMarkType.d.ts +0 -2
  232. package/dist/packages/core/src/helpers/getMarksBetween.d.ts +0 -3
  233. package/dist/packages/core/src/helpers/getNodeAttributes.d.ts +0 -4
  234. package/dist/packages/core/src/helpers/getNodeType.d.ts +0 -2
  235. package/dist/packages/core/src/helpers/getRenderedAttributes.d.ts +0 -3
  236. package/dist/packages/core/src/helpers/getSchema.d.ts +0 -3
  237. package/dist/packages/core/src/helpers/getSchemaTypeByName.d.ts +0 -2
  238. package/dist/packages/core/src/helpers/getSchemaTypeNameByName.d.ts +0 -2
  239. package/dist/packages/core/src/helpers/getSplittedAttributes.d.ts +0 -2
  240. package/dist/packages/core/src/helpers/injectExtensionAttributesToParseRule.d.ts +0 -9
  241. package/dist/packages/core/src/helpers/isActive.d.ts +0 -3
  242. package/dist/packages/core/src/helpers/isList.d.ts +0 -2
  243. package/dist/packages/core/src/helpers/isMarkActive.d.ts +0 -4
  244. package/dist/packages/core/src/helpers/isNodeActive.d.ts +0 -4
  245. package/dist/packages/core/src/helpers/isNodeEmpty.d.ts +0 -2
  246. package/dist/packages/core/src/helpers/isNodeSelection.d.ts +0 -2
  247. package/dist/packages/core/src/helpers/isTextSelection.d.ts +0 -2
  248. package/dist/packages/core/src/helpers/selectionToInsertionEnd.d.ts +0 -2
  249. package/dist/packages/core/src/helpers/splitExtensions.d.ts +0 -9
  250. package/dist/packages/core/src/index.d.ts +0 -30
  251. package/dist/packages/core/src/inputRules/markInputRule.d.ts +0 -3
  252. package/dist/packages/core/src/inputRules/nodeInputRule.d.ts +0 -3
  253. package/dist/packages/core/src/pasteRules/markPasteRule.d.ts +0 -3
  254. package/dist/packages/core/src/style.d.ts +0 -2
  255. package/dist/packages/core/src/types.d.ts +0 -154
  256. package/dist/packages/core/src/utilities/callOrReturn.d.ts +0 -8
  257. package/dist/packages/core/src/utilities/createStyleTag.d.ts +0 -1
  258. package/dist/packages/core/src/utilities/deleteProps.d.ts +0 -7
  259. package/dist/packages/core/src/utilities/elementFromString.d.ts +0 -1
  260. package/dist/packages/core/src/utilities/fromString.d.ts +0 -1
  261. package/dist/packages/core/src/utilities/isClass.d.ts +0 -1
  262. package/dist/packages/core/src/utilities/isEmptyObject.d.ts +0 -1
  263. package/dist/packages/core/src/utilities/isObject.d.ts +0 -1
  264. package/dist/packages/core/src/utilities/isPlainObject.d.ts +0 -1
  265. package/dist/packages/core/src/utilities/mergeAttributes.d.ts +0 -2
  266. package/dist/packages/core/src/utilities/mergeDeep.d.ts +0 -2
  267. package/dist/packages/core/src/utilities/minMax.d.ts +0 -1
  268. package/dist/packages/core/src/utilities/objectIncludes.d.ts +0 -7
  269. package/dist/packages/core/src/utilities/removeElement.d.ts +0 -1
  270. package/dist/tiptap-core.bundle.umd.min.js +0 -17
  271. package/dist/tiptap-core.bundle.umd.min.js.map +0 -1
  272. package/dist/tiptap-core.cjs.js +0 -3027
  273. package/dist/tiptap-core.cjs.js.map +0 -1
  274. package/dist/tiptap-core.esm.js +0 -3002
  275. package/dist/tiptap-core.esm.js.map +0 -1
  276. package/dist/tiptap-core.umd.js +0 -3024
  277. package/dist/tiptap-core.umd.js.map +0 -1
  278. package/src/commands/insertHTML.ts +0 -30
  279. package/src/commands/insertNode.ts +0 -33
  280. package/src/commands/insertText.ts +0 -22
  281. package/src/commands/joinBackward.ts +0 -17
  282. package/src/commands/joinForward.ts +0 -17
  283. package/src/commands/replace.ts +0 -20
  284. package/src/commands/replaceRange.ts +0 -36
  285. package/src/commands/resetNodeAttributes.ts +0 -33
  286. package/src/commands/updateNodeAttributes.ts +0 -35
  287. package/src/utilities/isClass.ts +0 -7
  288. package/src/utilities/isObject.ts +0 -10
  289. package/src/utilities/removeElement.ts +0 -5
package/src/NodeView.ts CHANGED
@@ -1,21 +1,23 @@
1
- import { Decoration, NodeView as ProseMirrorNodeView } from 'prosemirror-view'
2
- import { NodeSelection } from 'prosemirror-state'
3
- import { Node as ProseMirrorNode } from 'prosemirror-model'
1
+ import { Node as ProseMirrorNode } from '@tiptap/pm/model'
2
+ import { NodeSelection } from '@tiptap/pm/state'
3
+ import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
4
+
4
5
  import { Editor as CoreEditor } from './Editor'
5
6
  import { Node } from './Node'
6
- import { NodeViewRendererProps } from './types'
7
-
8
- interface NodeViewRendererOptions {
9
- stopEvent: ((event: Event) => boolean) | null,
10
- update: ((node: ProseMirrorNode, decorations: Decoration[]) => boolean) | null,
11
- }
12
-
13
- export class NodeView<Component, Editor extends CoreEditor = CoreEditor> implements ProseMirrorNodeView {
14
-
7
+ import { NodeViewRendererOptions, NodeViewRendererProps } from './types'
8
+ import { isiOS } from './utilities/isiOS'
9
+
10
+ export class NodeView<
11
+ Component,
12
+ Editor extends CoreEditor = CoreEditor,
13
+ Options extends NodeViewRendererOptions = NodeViewRendererOptions,
14
+ > implements ProseMirrorNodeView {
15
15
  component: Component
16
16
 
17
17
  editor: Editor
18
18
 
19
+ options: Options
20
+
19
21
  extension: Node
20
22
 
21
23
  node: ProseMirrorNode
@@ -26,15 +28,14 @@ export class NodeView<Component, Editor extends CoreEditor = CoreEditor> impleme
26
28
 
27
29
  isDragging = false
28
30
 
29
- options: NodeViewRendererOptions = {
30
- stopEvent: null,
31
- update: null,
32
- }
33
-
34
- constructor(component: Component, props: NodeViewRendererProps, options?: Partial<NodeViewRendererOptions>) {
31
+ constructor(component: Component, props: NodeViewRendererProps, options?: Partial<Options>) {
35
32
  this.component = component
36
- this.options = { ...this.options, ...options }
37
33
  this.editor = props.editor as Editor
34
+ this.options = {
35
+ stopEvent: null,
36
+ ignoreMutation: null,
37
+ ...options,
38
+ } as Options
38
39
  this.extension = props.extension
39
40
  this.node = props.node
40
41
  this.decorations = props.decorations
@@ -47,29 +48,48 @@ export class NodeView<Component, Editor extends CoreEditor = CoreEditor> impleme
47
48
  return
48
49
  }
49
50
 
50
- get dom(): Element | null {
51
- return null
51
+ get dom(): HTMLElement {
52
+ return this.editor.view.dom as HTMLElement
52
53
  }
53
54
 
54
- get contentDOM(): Element | null {
55
+ get contentDOM(): HTMLElement | null {
55
56
  return null
56
57
  }
57
58
 
58
59
  onDragStart(event: DragEvent) {
59
- if (!this.dom) {
60
+ const { view } = this.editor
61
+ const target = event.target as HTMLElement
62
+
63
+ // get the drag handle element
64
+ // `closest` is not available for text nodes so we may have to use its parent
65
+ const dragHandle = target.nodeType === 3
66
+ ? target.parentElement?.closest('[data-drag-handle]')
67
+ : target.closest('[data-drag-handle]')
68
+
69
+ if (!this.dom || this.contentDOM?.contains(target) || !dragHandle) {
60
70
  return
61
71
  }
62
72
 
63
- const { view } = this.editor
64
- const target = (event.target as HTMLElement)
73
+ let x = 0
74
+ let y = 0
65
75
 
66
- if (this.contentDOM?.contains(target)) {
67
- return
76
+ // calculate offset for drag element if we use a different drag handle element
77
+ if (this.dom !== dragHandle) {
78
+ const domBox = this.dom.getBoundingClientRect()
79
+ const handleBox = dragHandle.getBoundingClientRect()
80
+
81
+ // In React, we have to go through nativeEvent to reach offsetX/offsetY.
82
+ const offsetX = event.offsetX ?? (event as any).nativeEvent?.offsetX
83
+ const offsetY = event.offsetY ?? (event as any).nativeEvent?.offsetY
84
+
85
+ x = handleBox.x - domBox.x + offsetX
86
+ y = handleBox.y - domBox.y + offsetY
68
87
  }
69
88
 
70
- // sometimes `event.target` is not the `dom` element
71
- event.dataTransfer?.setDragImage(this.dom, 0, 0)
89
+ event.dataTransfer?.setDragImage(this.dom, x, y)
72
90
 
91
+ // we need to tell ProseMirror that we want to move the whole node
92
+ // so we create a NodeSelection
73
93
  const selection = NodeSelection.create(view.state.doc, this.getPos())
74
94
  const transaction = view.state.tr.setSelection(selection)
75
95
 
@@ -82,10 +102,10 @@ export class NodeView<Component, Editor extends CoreEditor = CoreEditor> impleme
82
102
  }
83
103
 
84
104
  if (typeof this.options.stopEvent === 'function') {
85
- return this.options.stopEvent(event)
105
+ return this.options.stopEvent({ event })
86
106
  }
87
107
 
88
- const target = (event.target as HTMLElement)
108
+ const target = event.target as HTMLElement
89
109
  const isInElement = this.dom.contains(target) && !this.contentDOM?.contains(target)
90
110
 
91
111
  // any event from child nodes should be handled by ProseMirror
@@ -93,11 +113,11 @@ export class NodeView<Component, Editor extends CoreEditor = CoreEditor> impleme
93
113
  return false
94
114
  }
95
115
 
96
- const isInput = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'].includes(target.tagName)
97
- || target.isContentEditable
116
+ const isDropEvent = event.type === 'drop'
117
+ const isInput = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'].includes(target.tagName) || target.isContentEditable
98
118
 
99
119
  // any input event within node views should be ignored by ProseMirror
100
- if (isInput) {
120
+ if (isInput && !isDropEvent) {
101
121
  return true
102
122
  }
103
123
 
@@ -109,7 +129,7 @@ export class NodeView<Component, Editor extends CoreEditor = CoreEditor> impleme
109
129
  const isPasteEvent = event.type === 'paste'
110
130
  const isCutEvent = event.type === 'cut'
111
131
  const isClickEvent = event.type === 'mousedown'
112
- const isDragEvent = event.type.startsWith('drag') || event.type === 'drop'
132
+ const isDragEvent = event.type.startsWith('drag')
113
133
 
114
134
  // ProseMirror tries to drag selectable nodes
115
135
  // even if `draggable` is set to `false`
@@ -126,25 +146,33 @@ export class NodeView<Component, Editor extends CoreEditor = CoreEditor> impleme
126
146
  // we have to store that dragging started
127
147
  if (isDraggable && isEditable && !isDragging && isClickEvent) {
128
148
  const dragHandle = target.closest('[data-drag-handle]')
129
- const isValidDragHandle = dragHandle
130
- && (this.dom === dragHandle || (this.dom.contains(dragHandle)))
149
+ const isValidDragHandle = dragHandle && (this.dom === dragHandle || this.dom.contains(dragHandle))
131
150
 
132
151
  if (isValidDragHandle) {
133
152
  this.isDragging = true
134
153
 
135
- document.addEventListener('dragend', () => {
136
- this.isDragging = false
137
- }, { once: true })
138
-
139
- document.addEventListener('mouseup', () => {
140
- this.isDragging = false
141
- }, { once: true })
154
+ document.addEventListener(
155
+ 'dragend',
156
+ () => {
157
+ this.isDragging = false
158
+ },
159
+ { once: true },
160
+ )
161
+
162
+ document.addEventListener(
163
+ 'mouseup',
164
+ () => {
165
+ this.isDragging = false
166
+ },
167
+ { once: true },
168
+ )
142
169
  }
143
170
  }
144
171
 
145
172
  // these events are handled by prosemirror
146
173
  if (
147
174
  isDragging
175
+ || isDropEvent
148
176
  || isCopyEvent
149
177
  || isPasteEvent
150
178
  || isCutEvent
@@ -156,38 +184,79 @@ export class NodeView<Component, Editor extends CoreEditor = CoreEditor> impleme
156
184
  return true
157
185
  }
158
186
 
159
- ignoreMutation(mutation: MutationRecord | { type: 'selection', target: Element }) {
160
- if (mutation.type === 'selection') {
161
- if (this.node.isLeaf) {
162
- return true
163
- }
187
+ ignoreMutation(mutation: MutationRecord | { type: 'selection'; target: Element }) {
188
+ if (!this.dom || !this.contentDOM) {
189
+ return true
190
+ }
164
191
 
192
+ if (typeof this.options.ignoreMutation === 'function') {
193
+ return this.options.ignoreMutation({ mutation })
194
+ }
195
+
196
+ // a leaf/atom node is like a black box for ProseMirror
197
+ // and should be fully handled by the node view
198
+ if (this.node.isLeaf || this.node.isAtom) {
199
+ return true
200
+ }
201
+
202
+ // ProseMirror should handle any selections
203
+ if (mutation.type === 'selection') {
165
204
  return false
166
205
  }
167
206
 
168
- if (!this.contentDOM) {
207
+ // try to prevent a bug on iOS that will break node views on enter
208
+ // this is because ProseMirror can’t preventDispatch on enter
209
+ // this will lead to a re-render of the node view on enter
210
+ // see: https://github.com/ueberdosis/tiptap/issues/1214
211
+ if (
212
+ this.dom.contains(mutation.target)
213
+ && mutation.type === 'childList'
214
+ && isiOS()
215
+ && this.editor.isFocused
216
+ ) {
217
+ const changedNodes = [
218
+ ...Array.from(mutation.addedNodes),
219
+ ...Array.from(mutation.removedNodes),
220
+ ] as HTMLElement[]
221
+
222
+ // we’ll check if every changed node is contentEditable
223
+ // to make sure it’s probably mutated by ProseMirror
224
+ if (changedNodes.every(node => node.isContentEditable)) {
225
+ return false
226
+ }
227
+ }
228
+
229
+ // we will allow mutation contentDOM with attributes
230
+ // so we can for example adding classes within our node view
231
+ if (this.contentDOM === mutation.target && mutation.type === 'attributes') {
169
232
  return true
170
233
  }
171
234
 
172
- const contentDOMHasChanged = !this.contentDOM.contains(mutation.target)
173
- || this.contentDOM === mutation.target
235
+ // ProseMirror should handle any changes within contentDOM
236
+ if (this.contentDOM.contains(mutation.target)) {
237
+ return false
238
+ }
174
239
 
175
- return contentDOMHasChanged
240
+ return true
176
241
  }
177
242
 
178
243
  updateAttributes(attributes: {}) {
179
- if (!this.editor.view.editable) {
180
- return
181
- }
244
+ this.editor.commands.command(({ tr }) => {
245
+ const pos = this.getPos()
182
246
 
183
- const { state } = this.editor.view
184
- const pos = this.getPos()
185
- const transaction = state.tr.setNodeMarkup(pos, undefined, {
186
- ...this.node.attrs,
187
- ...attributes,
188
- })
247
+ tr.setNodeMarkup(pos, undefined, {
248
+ ...this.node.attrs,
249
+ ...attributes,
250
+ })
189
251
 
190
- this.editor.view.dispatch(transaction)
252
+ return true
253
+ })
191
254
  }
192
255
 
256
+ deleteNode(): void {
257
+ const from = this.getPos()
258
+ const to = from + this.node.nodeSize
259
+
260
+ this.editor.commands.deleteRange({ from, to })
261
+ }
193
262
  }
@@ -0,0 +1,240 @@
1
+ import { EditorState, Plugin } from '@tiptap/pm/state'
2
+
3
+ import { CommandManager } from './CommandManager'
4
+ import { Editor } from './Editor'
5
+ import { createChainableState } from './helpers/createChainableState'
6
+ import {
7
+ CanCommands,
8
+ ChainedCommands,
9
+ ExtendedRegExpMatchArray,
10
+ Range,
11
+ SingleCommands,
12
+ } from './types'
13
+ import { isNumber } from './utilities/isNumber'
14
+ import { isRegExp } from './utilities/isRegExp'
15
+
16
+ export type PasteRuleMatch = {
17
+ index: number
18
+ text: string
19
+ replaceWith?: string
20
+ match?: RegExpMatchArray
21
+ data?: Record<string, any>
22
+ }
23
+
24
+ export type PasteRuleFinder = RegExp | ((text: string) => PasteRuleMatch[] | null | undefined)
25
+
26
+ export class PasteRule {
27
+ find: PasteRuleFinder
28
+
29
+ handler: (props: {
30
+ state: EditorState
31
+ range: Range
32
+ match: ExtendedRegExpMatchArray
33
+ commands: SingleCommands
34
+ chain: () => ChainedCommands
35
+ can: () => CanCommands
36
+ }) => void | null
37
+
38
+ constructor(config: {
39
+ find: PasteRuleFinder
40
+ handler: (props: {
41
+ state: EditorState
42
+ range: Range
43
+ match: ExtendedRegExpMatchArray
44
+ commands: SingleCommands
45
+ chain: () => ChainedCommands
46
+ can: () => CanCommands
47
+ }) => void | null
48
+ }) {
49
+ this.find = config.find
50
+ this.handler = config.handler
51
+ }
52
+ }
53
+
54
+ const pasteRuleMatcherHandler = (
55
+ text: string,
56
+ find: PasteRuleFinder,
57
+ ): ExtendedRegExpMatchArray[] => {
58
+ if (isRegExp(find)) {
59
+ return [...text.matchAll(find)]
60
+ }
61
+
62
+ const matches = find(text)
63
+
64
+ if (!matches) {
65
+ return []
66
+ }
67
+
68
+ return matches.map(pasteRuleMatch => {
69
+ const result: ExtendedRegExpMatchArray = [pasteRuleMatch.text]
70
+
71
+ result.index = pasteRuleMatch.index
72
+ result.input = text
73
+ result.data = pasteRuleMatch.data
74
+
75
+ if (pasteRuleMatch.replaceWith) {
76
+ if (!pasteRuleMatch.text.includes(pasteRuleMatch.replaceWith)) {
77
+ console.warn(
78
+ '[tiptap warn]: "pasteRuleMatch.replaceWith" must be part of "pasteRuleMatch.text".',
79
+ )
80
+ }
81
+
82
+ result.push(pasteRuleMatch.replaceWith)
83
+ }
84
+
85
+ return result
86
+ })
87
+ }
88
+
89
+ function run(config: {
90
+ editor: Editor
91
+ state: EditorState
92
+ from: number
93
+ to: number
94
+ rule: PasteRule
95
+ }): boolean {
96
+ const {
97
+ editor, state, from, to, rule,
98
+ } = config
99
+
100
+ const { commands, chain, can } = new CommandManager({
101
+ editor,
102
+ state,
103
+ })
104
+
105
+ const handlers: (void | null)[] = []
106
+
107
+ state.doc.nodesBetween(from, to, (node, pos) => {
108
+ if (!node.isTextblock || node.type.spec.code) {
109
+ return
110
+ }
111
+
112
+ const resolvedFrom = Math.max(from, pos)
113
+ const resolvedTo = Math.min(to, pos + node.content.size)
114
+ const textToMatch = node.textBetween(resolvedFrom - pos, resolvedTo - pos, undefined, '\ufffc')
115
+
116
+ const matches = pasteRuleMatcherHandler(textToMatch, rule.find)
117
+
118
+ matches.forEach(match => {
119
+ if (match.index === undefined) {
120
+ return
121
+ }
122
+
123
+ const start = resolvedFrom + match.index + 1
124
+ const end = start + match[0].length
125
+ const range = {
126
+ from: state.tr.mapping.map(start),
127
+ to: state.tr.mapping.map(end),
128
+ }
129
+
130
+ const handler = rule.handler({
131
+ state,
132
+ range,
133
+ match,
134
+ commands,
135
+ chain,
136
+ can,
137
+ })
138
+
139
+ handlers.push(handler)
140
+ })
141
+ })
142
+
143
+ const success = handlers.every(handler => handler !== null)
144
+
145
+ return success
146
+ }
147
+
148
+ /**
149
+ * Create an paste rules plugin. When enabled, it will cause pasted
150
+ * text that matches any of the given rules to trigger the rule’s
151
+ * action.
152
+ */
153
+ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }): Plugin[] {
154
+ const { editor, rules } = props
155
+ let dragSourceElement: Element | null = null
156
+ let isPastedFromProseMirror = false
157
+ let isDroppedFromProseMirror = false
158
+
159
+ const plugins = rules.map(rule => {
160
+ return new Plugin({
161
+ // we register a global drag handler to track the current drag source element
162
+ view(view) {
163
+ const handleDragstart = (event: DragEvent) => {
164
+ dragSourceElement = view.dom.parentElement?.contains(event.target as Element)
165
+ ? view.dom.parentElement
166
+ : null
167
+ }
168
+
169
+ window.addEventListener('dragstart', handleDragstart)
170
+
171
+ return {
172
+ destroy() {
173
+ window.removeEventListener('dragstart', handleDragstart)
174
+ },
175
+ }
176
+ },
177
+
178
+ props: {
179
+ handleDOMEvents: {
180
+ drop: view => {
181
+ isDroppedFromProseMirror = dragSourceElement === view.dom.parentElement
182
+
183
+ return false
184
+ },
185
+
186
+ paste: (view, event: Event) => {
187
+ const html = (event as ClipboardEvent).clipboardData?.getData('text/html')
188
+
189
+ isPastedFromProseMirror = !!html?.includes('data-pm-slice')
190
+
191
+ return false
192
+ },
193
+ },
194
+ },
195
+
196
+ appendTransaction: (transactions, oldState, state) => {
197
+ const transaction = transactions[0]
198
+ const isPaste = transaction.getMeta('uiEvent') === 'paste' && !isPastedFromProseMirror
199
+ const isDrop = transaction.getMeta('uiEvent') === 'drop' && !isDroppedFromProseMirror
200
+
201
+ if (!isPaste && !isDrop) {
202
+ return
203
+ }
204
+
205
+ // stop if there is no changed range
206
+ const from = oldState.doc.content.findDiffStart(state.doc.content)
207
+ const to = oldState.doc.content.findDiffEnd(state.doc.content)
208
+
209
+ if (!isNumber(from) || !to || from === to.b) {
210
+ return
211
+ }
212
+
213
+ // build a chainable state
214
+ // so we can use a single transaction for all paste rules
215
+ const tr = state.tr
216
+ const chainableState = createChainableState({
217
+ state,
218
+ transaction: tr,
219
+ })
220
+
221
+ const handler = run({
222
+ editor,
223
+ state: chainableState,
224
+ from: Math.max(from - 1, 0),
225
+ to: to.b - 1,
226
+ rule,
227
+ })
228
+
229
+ // stop if there are no changes
230
+ if (!handler || !tr.steps.length) {
231
+ return
232
+ }
233
+
234
+ return tr
235
+ },
236
+ })
237
+ })
238
+
239
+ return plugins
240
+ }
package/src/Tracker.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { Transaction } from '@tiptap/pm/state'
2
+
3
+ export interface TrackerResult {
4
+ position: number
5
+ deleted: boolean
6
+ }
7
+
8
+ export class Tracker {
9
+ transaction: Transaction
10
+
11
+ currentStep: number
12
+
13
+ constructor(transaction: Transaction) {
14
+ this.transaction = transaction
15
+ this.currentStep = this.transaction.steps.length
16
+ }
17
+
18
+ map(position: number): TrackerResult {
19
+ let deleted = false
20
+
21
+ const mappedPosition = this.transaction.steps
22
+ .slice(this.currentStep)
23
+ .reduce((newPosition, step) => {
24
+ const mapResult = step.getMap().mapResult(newPosition)
25
+
26
+ if (mapResult.deleted) {
27
+ deleted = true
28
+ }
29
+
30
+ return mapResult.pos
31
+ }, position)
32
+
33
+ return {
34
+ position: mappedPosition,
35
+ deleted,
36
+ }
37
+ }
38
+ }
@@ -1,20 +1,26 @@
1
- import { Command, RawCommands } from '../types'
1
+ import { RawCommands } from '../types'
2
2
 
3
3
  declare module '@tiptap/core' {
4
- interface Commands {
4
+ interface Commands<ReturnType> {
5
5
  blur: {
6
6
  /**
7
7
  * Removes focus from the editor.
8
8
  */
9
- blur: () => Command,
9
+ blur: () => ReturnType,
10
10
  }
11
11
  }
12
12
  }
13
13
 
14
- export const blur: RawCommands['blur'] = () => ({ view }) => {
15
- const element = view.dom as HTMLElement
14
+ export const blur: RawCommands['blur'] = () => ({ editor, view }) => {
15
+ requestAnimationFrame(() => {
16
+ if (!editor.isDestroyed) {
17
+ (view.dom as HTMLElement).blur()
16
18
 
17
- element.blur()
19
+ // Browsers should remove the caret on blur but safari does not.
20
+ // See: https://github.com/ueberdosis/tiptap/issues/2405
21
+ window?.getSelection()?.removeAllRanges()
22
+ }
23
+ })
18
24
 
19
25
  return true
20
26
  }
@@ -1,12 +1,12 @@
1
- import { Command, RawCommands } from '../types'
1
+ import { RawCommands } from '../types'
2
2
 
3
3
  declare module '@tiptap/core' {
4
- interface Commands {
4
+ interface Commands<ReturnType> {
5
5
  clearContent: {
6
6
  /**
7
7
  * Clear the whole document.
8
8
  */
9
- clearContent: (emitUpdate: Boolean) => Command,
9
+ clearContent: (emitUpdate?: boolean) => ReturnType,
10
10
  }
11
11
  }
12
12
  }
@@ -1,13 +1,14 @@
1
- import { liftTarget } from 'prosemirror-transform'
2
- import { Command, RawCommands } from '../types'
1
+ import { liftTarget } from '@tiptap/pm/transform'
2
+
3
+ import { RawCommands } from '../types'
3
4
 
4
5
  declare module '@tiptap/core' {
5
- interface Commands {
6
+ interface Commands<ReturnType> {
6
7
  clearNodes: {
7
8
  /**
8
9
  * Normalize nodes to a simple paragraph.
9
10
  */
10
- clearNodes: () => Command,
11
+ clearNodes: () => ReturnType,
11
12
  }
12
13
  }
13
14
  }
@@ -16,24 +17,35 @@ export const clearNodes: RawCommands['clearNodes'] = () => ({ state, tr, dispatc
16
17
  const { selection } = tr
17
18
  const { ranges } = selection
18
19
 
19
- ranges.forEach(range => {
20
- state.doc.nodesBetween(range.$from.pos, range.$to.pos, (node, pos) => {
21
- if (!node.type.isText) {
22
- const fromPos = tr.doc.resolve(tr.mapping.map(pos + 1))
23
- const toPos = tr.doc.resolve(tr.mapping.map(pos + node.nodeSize - 1))
24
- const nodeRange = fromPos.blockRange(toPos)
20
+ if (!dispatch) {
21
+ return true
22
+ }
25
23
 
26
- if (nodeRange) {
27
- const targetLiftDepth = liftTarget(nodeRange)
24
+ ranges.forEach(({ $from, $to }) => {
25
+ state.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
26
+ if (node.type.isText) {
27
+ return
28
+ }
28
29
 
29
- if (node.type.isTextblock && dispatch) {
30
- tr.setNodeMarkup(nodeRange.start, state.doc.type.contentMatch.defaultType)
31
- }
30
+ const { doc, mapping } = tr
31
+ const $mappedFrom = doc.resolve(mapping.map(pos))
32
+ const $mappedTo = doc.resolve(mapping.map(pos + node.nodeSize))
33
+ const nodeRange = $mappedFrom.blockRange($mappedTo)
34
+
35
+ if (!nodeRange) {
36
+ return
37
+ }
38
+
39
+ const targetLiftDepth = liftTarget(nodeRange)
40
+
41
+ if (node.type.isTextblock) {
42
+ const { defaultType } = $mappedFrom.parent.contentMatchAt($mappedFrom.index())
43
+
44
+ tr.setNodeMarkup(nodeRange.start, defaultType)
45
+ }
32
46
 
33
- if ((targetLiftDepth || targetLiftDepth === 0) && dispatch) {
34
- tr.lift(nodeRange, targetLiftDepth)
35
- }
36
- }
47
+ if (targetLiftDepth || targetLiftDepth === 0) {
48
+ tr.lift(nodeRange, targetLiftDepth)
37
49
  }
38
50
  })
39
51
  })