@prosekit/extensions 0.12.2 → 0.14.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.
Files changed (324) hide show
  1. package/dist/{drop-indicator-B1QHFb5m.js → drop-indicator-B_oMfeVP.js} +11 -10
  2. package/dist/drop-indicator-B_oMfeVP.js.map +1 -0
  3. package/dist/{enter-rule-CzWOZF_Z.js → enter-rule-D-p4ykfv.js} +1 -1
  4. package/dist/enter-rule-D-p4ykfv.js.map +1 -0
  5. package/dist/{file-DrfcSid-.js → file-iLVR0eM0.js} +3 -3
  6. package/dist/file-iLVR0eM0.js.map +1 -0
  7. package/dist/{index-oIc1a2f2.d.ts → index-cp1u4e0e.d.ts} +1 -1
  8. package/dist/index-cp1u4e0e.d.ts.map +1 -0
  9. package/dist/{input-rule-dmsb3j6w.js → input-rule-COGr_GBb.js} +1 -1
  10. package/dist/input-rule-COGr_GBb.js.map +1 -0
  11. package/dist/list/style.css +5 -5
  12. package/dist/list/style.css.map +1 -1
  13. package/dist/{mark-rule-BcLB4Uv2.js → mark-rule-CYe8zk4q.js} +6 -6
  14. package/dist/mark-rule-CYe8zk4q.js.map +1 -0
  15. package/dist/{paste-rule-pVb4sqvJ.js → paste-rule-BaDghcaU.js} +7 -7
  16. package/dist/paste-rule-BaDghcaU.js.map +1 -0
  17. package/dist/prosekit-extensions-autocomplete.d.ts +11 -3
  18. package/dist/prosekit-extensions-autocomplete.d.ts.map +1 -1
  19. package/dist/prosekit-extensions-autocomplete.js +171 -60
  20. package/dist/prosekit-extensions-autocomplete.js.map +1 -1
  21. package/dist/prosekit-extensions-background-color.d.ts +62 -0
  22. package/dist/prosekit-extensions-background-color.d.ts.map +1 -0
  23. package/dist/prosekit-extensions-background-color.js +76 -0
  24. package/dist/prosekit-extensions-background-color.js.map +1 -0
  25. package/dist/prosekit-extensions-blockquote.d.ts.map +1 -1
  26. package/dist/prosekit-extensions-blockquote.js +2 -2
  27. package/dist/prosekit-extensions-blockquote.js.map +1 -1
  28. package/dist/prosekit-extensions-bold.d.ts.map +1 -1
  29. package/dist/prosekit-extensions-bold.js +1 -1
  30. package/dist/prosekit-extensions-bold.js.map +1 -1
  31. package/dist/prosekit-extensions-code-block.d.ts +1 -1
  32. package/dist/prosekit-extensions-code-block.d.ts.map +1 -1
  33. package/dist/prosekit-extensions-code-block.js +4 -4
  34. package/dist/prosekit-extensions-code-block.js.map +1 -1
  35. package/dist/prosekit-extensions-code.d.ts.map +1 -1
  36. package/dist/prosekit-extensions-code.js +1 -1
  37. package/dist/prosekit-extensions-code.js.map +1 -1
  38. package/dist/prosekit-extensions-commit.d.ts +0 -1
  39. package/dist/prosekit-extensions-commit.d.ts.map +1 -1
  40. package/dist/prosekit-extensions-commit.js +1 -1
  41. package/dist/prosekit-extensions-commit.js.map +1 -1
  42. package/dist/prosekit-extensions-doc.d.ts +0 -1
  43. package/dist/prosekit-extensions-doc.d.ts.map +1 -1
  44. package/dist/prosekit-extensions-doc.js.map +1 -1
  45. package/dist/prosekit-extensions-drop-cursor.d.ts.map +1 -1
  46. package/dist/prosekit-extensions-drop-cursor.js.map +1 -1
  47. package/dist/prosekit-extensions-drop-indicator.d.ts +0 -1
  48. package/dist/prosekit-extensions-drop-indicator.d.ts.map +1 -1
  49. package/dist/prosekit-extensions-drop-indicator.js +1 -1
  50. package/dist/prosekit-extensions-enter-rule.d.ts +0 -1
  51. package/dist/prosekit-extensions-enter-rule.d.ts.map +1 -1
  52. package/dist/prosekit-extensions-enter-rule.js +1 -1
  53. package/dist/prosekit-extensions-file.d.ts +1 -1
  54. package/dist/prosekit-extensions-file.js +1 -1
  55. package/dist/prosekit-extensions-gap-cursor.d.ts +0 -1
  56. package/dist/prosekit-extensions-gap-cursor.d.ts.map +1 -1
  57. package/dist/prosekit-extensions-gap-cursor.js.map +1 -1
  58. package/dist/prosekit-extensions-hard-break.d.ts.map +1 -1
  59. package/dist/prosekit-extensions-hard-break.js.map +1 -1
  60. package/dist/prosekit-extensions-heading.d.ts.map +1 -1
  61. package/dist/prosekit-extensions-heading.js +7 -7
  62. package/dist/prosekit-extensions-heading.js.map +1 -1
  63. package/dist/prosekit-extensions-horizontal-rule.d.ts.map +1 -1
  64. package/dist/prosekit-extensions-horizontal-rule.js +1 -1
  65. package/dist/prosekit-extensions-horizontal-rule.js.map +1 -1
  66. package/dist/prosekit-extensions-image.d.ts +9 -2
  67. package/dist/prosekit-extensions-image.d.ts.map +1 -1
  68. package/dist/prosekit-extensions-image.js +17 -4
  69. package/dist/prosekit-extensions-image.js.map +1 -1
  70. package/dist/prosekit-extensions-input-rule.d.ts +0 -1
  71. package/dist/prosekit-extensions-input-rule.d.ts.map +1 -1
  72. package/dist/prosekit-extensions-input-rule.js +1 -1
  73. package/dist/prosekit-extensions-italic.d.ts.map +1 -1
  74. package/dist/prosekit-extensions-italic.js +1 -1
  75. package/dist/prosekit-extensions-italic.js.map +1 -1
  76. package/dist/prosekit-extensions-link.d.ts +0 -1
  77. package/dist/prosekit-extensions-link.d.ts.map +1 -1
  78. package/dist/prosekit-extensions-link.js +4 -4
  79. package/dist/prosekit-extensions-link.js.map +1 -1
  80. package/dist/prosekit-extensions-list.d.ts +0 -1
  81. package/dist/prosekit-extensions-list.d.ts.map +1 -1
  82. package/dist/prosekit-extensions-list.js +3 -3
  83. package/dist/prosekit-extensions-list.js.map +1 -1
  84. package/dist/prosekit-extensions-loro.d.ts +16 -17
  85. package/dist/prosekit-extensions-loro.d.ts.map +1 -1
  86. package/dist/prosekit-extensions-loro.js +14 -7
  87. package/dist/prosekit-extensions-loro.js.map +1 -1
  88. package/dist/prosekit-extensions-mark-rule.d.ts +0 -1
  89. package/dist/prosekit-extensions-mark-rule.d.ts.map +1 -1
  90. package/dist/prosekit-extensions-mark-rule.js +1 -1
  91. package/dist/prosekit-extensions-mention.d.ts.map +1 -1
  92. package/dist/prosekit-extensions-mention.js.map +1 -1
  93. package/dist/prosekit-extensions-mod-click-prevention.d.ts +0 -1
  94. package/dist/prosekit-extensions-mod-click-prevention.d.ts.map +1 -1
  95. package/dist/prosekit-extensions-mod-click-prevention.js.map +1 -1
  96. package/dist/prosekit-extensions-paragraph.d.ts.map +1 -1
  97. package/dist/prosekit-extensions-paragraph.js +1 -1
  98. package/dist/prosekit-extensions-paragraph.js.map +1 -1
  99. package/dist/prosekit-extensions-paste-rule.d.ts +0 -1
  100. package/dist/prosekit-extensions-paste-rule.d.ts.map +1 -1
  101. package/dist/prosekit-extensions-paste-rule.js +1 -1
  102. package/dist/prosekit-extensions-placeholder.d.ts.map +1 -1
  103. package/dist/prosekit-extensions-placeholder.js +3 -4
  104. package/dist/prosekit-extensions-placeholder.js.map +1 -1
  105. package/dist/prosekit-extensions-readonly.d.ts +0 -1
  106. package/dist/prosekit-extensions-readonly.d.ts.map +1 -1
  107. package/dist/prosekit-extensions-readonly.js.map +1 -1
  108. package/dist/prosekit-extensions-search.d.ts +0 -1
  109. package/dist/prosekit-extensions-search.d.ts.map +1 -1
  110. package/dist/prosekit-extensions-search.js.map +1 -1
  111. package/dist/prosekit-extensions-strike.d.ts +0 -1
  112. package/dist/prosekit-extensions-strike.d.ts.map +1 -1
  113. package/dist/prosekit-extensions-strike.js +3 -3
  114. package/dist/prosekit-extensions-strike.js.map +1 -1
  115. package/dist/prosekit-extensions-table.d.ts.map +1 -1
  116. package/dist/prosekit-extensions-table.js +1 -2
  117. package/dist/prosekit-extensions-text-align.d.ts +0 -1
  118. package/dist/prosekit-extensions-text-align.d.ts.map +1 -1
  119. package/dist/prosekit-extensions-text-align.js +6 -6
  120. package/dist/prosekit-extensions-text-align.js.map +1 -1
  121. package/dist/prosekit-extensions-text-color.d.ts +62 -0
  122. package/dist/prosekit-extensions-text-color.d.ts.map +1 -0
  123. package/dist/prosekit-extensions-text-color.js +76 -0
  124. package/dist/prosekit-extensions-text-color.js.map +1 -0
  125. package/dist/prosekit-extensions-text.d.ts +0 -1
  126. package/dist/prosekit-extensions-text.d.ts.map +1 -1
  127. package/dist/prosekit-extensions-text.js.map +1 -1
  128. package/dist/prosekit-extensions-underline.d.ts +0 -1
  129. package/dist/prosekit-extensions-underline.d.ts.map +1 -1
  130. package/dist/prosekit-extensions-virtual-selection.d.ts +0 -1
  131. package/dist/prosekit-extensions-virtual-selection.d.ts.map +1 -1
  132. package/dist/prosekit-extensions-virtual-selection.js.map +1 -1
  133. package/dist/prosekit-extensions-yjs.d.ts +9 -2
  134. package/dist/prosekit-extensions-yjs.d.ts.map +1 -1
  135. package/dist/prosekit-extensions-yjs.js +1 -1
  136. package/dist/prosekit-extensions-yjs.js.map +1 -1
  137. package/dist/{shiki-highlighter-chunk-rkzofy4z.d.ts → shiki-highlighter-chunk-DNNm2Vow.d.ts} +1 -1
  138. package/dist/shiki-highlighter-chunk-DNNm2Vow.d.ts.map +1 -0
  139. package/dist/shiki-highlighter-chunk.d.ts +1 -1
  140. package/dist/shiki-highlighter-chunk.js.map +1 -1
  141. package/dist/{table-BRDh_9mG.js → table-4oHfV-Ql.js} +2 -2
  142. package/dist/table-4oHfV-Ql.js.map +1 -0
  143. package/package.json +33 -18
  144. package/src/autocomplete/autocomplete-helpers.ts +19 -14
  145. package/src/autocomplete/autocomplete-plugin.ts +260 -126
  146. package/src/autocomplete/autocomplete-rule.ts +3 -3
  147. package/src/autocomplete/autocomplete.spec.ts +244 -40
  148. package/src/autocomplete/autocomplete.ts +9 -7
  149. package/src/background-color/background-color-commands.spec.ts +71 -0
  150. package/src/background-color/background-color-commands.ts +35 -0
  151. package/src/background-color/background-color-spec.spec.ts +286 -0
  152. package/src/background-color/background-color-spec.ts +58 -0
  153. package/src/background-color/background-color.ts +21 -0
  154. package/src/background-color/index.ts +8 -0
  155. package/src/blockquote/blockquote-commands.ts +1 -7
  156. package/src/blockquote/blockquote-keymap.spec.ts +5 -9
  157. package/src/blockquote/blockquote-keymap.ts +2 -7
  158. package/src/blockquote/blockquote-spec.ts +1 -4
  159. package/src/blockquote/blockquote.ts +3 -12
  160. package/src/blockquote/index.ts +3 -12
  161. package/src/bold/bold-commands.ts +1 -5
  162. package/src/bold/bold-input-rule.spec.ts +1 -5
  163. package/src/bold/bold-input-rule.ts +1 -4
  164. package/src/bold/bold-keymap.ts +1 -5
  165. package/src/bold/bold-spec.ts +1 -4
  166. package/src/bold/bold.ts +3 -12
  167. package/src/bold/index.ts +3 -12
  168. package/src/code/code-commands.ts +1 -5
  169. package/src/code/code-input-rule.ts +1 -4
  170. package/src/code/code-keymap.ts +1 -5
  171. package/src/code/code-spec.ts +1 -4
  172. package/src/code/code.ts +3 -12
  173. package/src/code/index.ts +3 -12
  174. package/src/code-block/code-block-commands.ts +1 -8
  175. package/src/code-block/code-block-highlight.ts +2 -8
  176. package/src/code-block/code-block-keymap.ts +2 -9
  177. package/src/code-block/code-block-shiki.ts +1 -4
  178. package/src/code-block/code-block-spec.spec.ts +4 -11
  179. package/src/code-block/code-block-spec.ts +1 -4
  180. package/src/code-block/code-block.ts +4 -16
  181. package/src/code-block/index.ts +5 -21
  182. package/src/code-block/shiki-highlighter-chunk.ts +1 -7
  183. package/src/code-block/shiki-highlighter.ts +1 -4
  184. package/src/code-block/shiki-parser.ts +1 -4
  185. package/src/commit/index.ts +7 -36
  186. package/src/doc/index.ts +1 -4
  187. package/src/drop-cursor/drop-cursor.ts +1 -4
  188. package/src/drop-cursor/index.ts +1 -5
  189. package/src/drop-indicator/drop-indicator-facet.ts +12 -21
  190. package/src/drop-indicator/index.ts +1 -5
  191. package/src/enter-rule/index.ts +2 -11
  192. package/src/file/file-paste-handler.spec.ts +3 -16
  193. package/src/file/index.ts +3 -16
  194. package/src/gap-cursor/gap-cursor.ts +1 -4
  195. package/src/gap-cursor/index.ts +1 -4
  196. package/src/hard-break/hard-break-commands.ts +1 -5
  197. package/src/hard-break/hard-break-keymap.spec.ts +6 -12
  198. package/src/hard-break/hard-break-keymap.ts +1 -4
  199. package/src/hard-break/hard-break-spec.ts +1 -4
  200. package/src/hard-break/hard-break.ts +3 -12
  201. package/src/hard-break/index.ts +3 -12
  202. package/src/heading/heading-commands.ts +1 -7
  203. package/src/heading/heading-keymap.spec.ts +8 -12
  204. package/src/heading/heading-keymap.ts +7 -14
  205. package/src/heading/heading-spec.ts +1 -4
  206. package/src/heading/heading.ts +3 -12
  207. package/src/heading/index.ts +3 -12
  208. package/src/horizontal-rule/horizontal-rule-commands.spec.ts +1 -5
  209. package/src/horizontal-rule/horizontal-rule-commands.ts +2 -9
  210. package/src/horizontal-rule/horizontal-rule-input-rule.spec.ts +5 -9
  211. package/src/horizontal-rule/horizontal-rule-input-rule.ts +1 -5
  212. package/src/horizontal-rule/horizontal-rule-spec.ts +1 -4
  213. package/src/horizontal-rule/horizontal-rule.ts +3 -12
  214. package/src/horizontal-rule/index.ts +3 -13
  215. package/src/image/image-commands/upload-image.spec.ts +245 -0
  216. package/src/image/image-commands/upload-image.ts +46 -11
  217. package/src/image/image-commands.ts +2 -8
  218. package/src/image/image-spec.ts +1 -4
  219. package/src/image/image-upload-handler.ts +2 -8
  220. package/src/image/image.ts +3 -12
  221. package/src/image/index.ts +3 -13
  222. package/src/input-rule/index.ts +2 -13
  223. package/src/italic/index.ts +3 -12
  224. package/src/italic/italic-commands.spec.ts +2 -10
  225. package/src/italic/italic-commands.ts +1 -5
  226. package/src/italic/italic-input-rule.spec.ts +1 -5
  227. package/src/italic/italic-input-rule.ts +1 -4
  228. package/src/italic/italic-keymap.ts +1 -5
  229. package/src/italic/italic-spec.ts +1 -4
  230. package/src/italic/italic.ts +3 -12
  231. package/src/link/index.spec.ts +10 -13
  232. package/src/link/index.ts +1 -5
  233. package/src/link/link-paste-rule.spec.ts +2 -9
  234. package/src/link/link-regex.spec.ts +1 -5
  235. package/src/list/index.ts +3 -12
  236. package/src/list/list-commands.ts +1 -5
  237. package/src/list/list-input-rules.ts +1 -4
  238. package/src/list/list-keymap.spec.ts +6 -10
  239. package/src/list/list-keymap.ts +2 -8
  240. package/src/list/list-plugins.ts +1 -4
  241. package/src/list/list-serializer.ts +2 -9
  242. package/src/list/list-spec.ts +3 -13
  243. package/src/list/list.spec.ts +10 -21
  244. package/src/list/list.ts +3 -12
  245. package/src/list/style.css +5 -5
  246. package/src/loro/index.ts +3 -13
  247. package/src/loro/loro-commands.ts +2 -8
  248. package/src/loro/loro-cursor-plugin.ts +21 -21
  249. package/src/loro/loro-keymap.ts +3 -11
  250. package/src/loro/loro-sync-plugin.ts +2 -8
  251. package/src/loro/loro-undo-plugin.ts +2 -8
  252. package/src/loro/loro.ts +16 -21
  253. package/src/mark-rule/apply.ts +3 -13
  254. package/src/mark-rule/mark-rule.spec.ts +2 -13
  255. package/src/mark-rule/mark-rule.ts +2 -13
  256. package/src/mark-rule/range.ts +2 -8
  257. package/src/mark-rule/types.ts +1 -4
  258. package/src/mention/index.ts +1 -8
  259. package/src/mod-click-prevention/index.ts +2 -9
  260. package/src/paragraph/paragraph-commands.ts +1 -5
  261. package/src/paragraph/paragraph-keymap.ts +2 -5
  262. package/src/paragraph/paragraph-spec.ts +1 -4
  263. package/src/paragraph/paragraph.ts +3 -14
  264. package/src/paste-rule/index.ts +2 -10
  265. package/src/paste-rule/mark-paste-rule.spec.ts +3 -13
  266. package/src/paste-rule/mark-paste-rule.ts +4 -14
  267. package/src/paste-rule/paste-rule-plugin.ts +2 -11
  268. package/src/paste-rule/paste-rule.spec.ts +4 -19
  269. package/src/paste-rule/split-text-by-regex.spec.ts +1 -5
  270. package/src/placeholder/index.ts +4 -16
  271. package/src/readonly/index.ts +2 -8
  272. package/src/search/index.ts +1 -6
  273. package/src/strike/index.ts +2 -2
  274. package/src/table/index.ts +10 -40
  275. package/src/table/table-commands/delete-cell-selection.spec.ts +1 -5
  276. package/src/table/table-commands/exit-table.spec.ts +1 -5
  277. package/src/table/table-commands/insert-table.spec.ts +1 -5
  278. package/src/table/table-commands/insert-table.ts +1 -4
  279. package/src/table/table-commands/move-table-column.spec.ts +1 -5
  280. package/src/table/table-commands/move-table-column.ts +1 -4
  281. package/src/table/table-commands/move-table-row.spec.ts +1 -5
  282. package/src/table/table-commands/move-table-row.ts +1 -4
  283. package/src/table/table-commands/select-table-cell.spec.ts +1 -5
  284. package/src/table/table-commands/select-table-column.spec.ts +1 -5
  285. package/src/table/table-commands/select-table-row.spec.ts +1 -5
  286. package/src/table/table-commands/select-table.spec.ts +1 -5
  287. package/src/table/table-commands/select-table.ts +1 -4
  288. package/src/table/table-commands.ts +8 -32
  289. package/src/table/table-plugins.ts +2 -8
  290. package/src/table/table-spec.spec.ts +2 -11
  291. package/src/table/table-spec.ts +2 -8
  292. package/src/table/table-utils.ts +2 -6
  293. package/src/table/table.ts +2 -8
  294. package/src/table/test-utils.ts +1 -4
  295. package/src/testing/clipboard.ts +1 -2
  296. package/src/testing/index.ts +9 -14
  297. package/src/testing/keyboard.ts +0 -30
  298. package/src/text/index.ts +1 -4
  299. package/src/text-align/index.ts +6 -6
  300. package/src/text-color/index.ts +3 -0
  301. package/src/text-color/text-color-commands.spec.ts +71 -0
  302. package/src/text-color/text-color-commands.ts +35 -0
  303. package/src/text-color/text-color-spec.spec.ts +297 -0
  304. package/src/text-color/text-color-spec.ts +58 -0
  305. package/src/text-color/text-color.ts +21 -0
  306. package/src/virtual-selection/index.ts +3 -14
  307. package/src/yjs/index.ts +5 -20
  308. package/src/yjs/yjs-commands.ts +2 -8
  309. package/src/yjs/yjs-cursor-plugin.ts +3 -5
  310. package/src/yjs/yjs-keymap.ts +3 -11
  311. package/src/yjs/yjs-sync-plugin.ts +1 -4
  312. package/src/yjs/yjs-types.ts +10 -0
  313. package/src/yjs/yjs-undo-plugin.ts +2 -8
  314. package/src/yjs/yjs.ts +6 -24
  315. package/dist/drop-indicator-B1QHFb5m.js.map +0 -1
  316. package/dist/enter-rule-CzWOZF_Z.js.map +0 -1
  317. package/dist/file-DrfcSid-.js.map +0 -1
  318. package/dist/index-oIc1a2f2.d.ts.map +0 -1
  319. package/dist/input-rule-dmsb3j6w.js.map +0 -1
  320. package/dist/mark-rule-BcLB4Uv2.js.map +0 -1
  321. package/dist/paste-rule-pVb4sqvJ.js.map +0 -1
  322. package/dist/shiki-highlighter-chunk-rkzofy4z.d.ts.map +0 -1
  323. package/dist/table-BRDh_9mG.js.map +0 -1
  324. package/src/testing/format-html.ts +0 -5
@@ -1,13 +1,9 @@
1
1
  import { OBJECT_REPLACEMENT_CHARACTER } from '@prosekit/core'
2
- import {
3
- Plugin,
4
- type EditorState,
5
- type Transaction,
6
- } from '@prosekit/pm/state'
7
- import {
8
- Decoration,
9
- DecorationSet,
10
- } from '@prosekit/pm/view'
2
+ import type { ProseMirrorNode, ResolvedPos } from '@prosekit/pm/model'
3
+ import { Plugin, type EditorState, type Transaction } from '@prosekit/pm/state'
4
+ import type { Mapping } from '@prosekit/pm/transform'
5
+ import type { EditorView } from '@prosekit/pm/view'
6
+ import { Decoration, DecorationSet } from '@prosekit/pm/view'
11
7
 
12
8
  import {
13
9
  getPluginState,
@@ -16,9 +12,27 @@ import {
16
12
  setTrMeta,
17
13
  type PredictionPluginMatching,
18
14
  type PredictionPluginState,
15
+ type PredictionTransactionMeta,
19
16
  } from './autocomplete-helpers'
20
17
  import type { AutocompleteRule } from './autocomplete-rule'
21
18
 
19
+ /**
20
+ * Creates a plugin that handles autocomplete functionality.
21
+ *
22
+ * Workflow:
23
+ *
24
+ * 1. {@link handleTextInput}: called when text is going to be input, but the
25
+ * transaction is not yet created. Injects a new matching as a transaction
26
+ * meta if applicable. This is the only place to create a new matching if
27
+ * there is no existing matching.
28
+ * 2. {@link handleTransaction}: called when a transaction is going to be
29
+ * applied. Updates the plugin state based on the transaction. This step
30
+ * determines if a matching should be created, updated or removed.
31
+ * 3. {@link handleUpdate}: called when the editor state is updated. This is the
32
+ * place to call `onMatch` and register `deleteMatch` and `ignoreMatch`
33
+ * callbacks.
34
+ * 4. {@link getDecorations}: creates the decorations for the current matching.
35
+ */
22
36
  export function createAutocompletePlugin({
23
37
  getRules,
24
38
  }: {
@@ -31,139 +45,252 @@ export function createAutocompletePlugin({
31
45
  init: (): PredictionPluginState => {
32
46
  return { ignores: [], matching: null }
33
47
  },
34
- apply: (
35
- tr: Transaction,
36
- prevValue: PredictionPluginState,
37
- oldState: EditorState,
38
- newState: EditorState,
39
- ): PredictionPluginState => {
40
- const meta = getTrMeta(tr)
41
-
42
- // No changes
43
- if (
44
- !tr.docChanged
45
- && oldState.selection.eq(newState.selection)
46
- && !meta
47
- ) {
48
- return prevValue
49
- }
50
-
51
- // Receiving a meta means that we are ignoring a match
52
- if (meta) {
53
- let ignores = prevValue.ignores
54
- if (!ignores.includes(meta.ignore)) {
55
- ignores = [...ignores, meta.ignore]
56
- }
57
- return { matching: null, ignores }
58
- }
59
-
60
- // Calculate the new ignores
61
- const ignoreSet = new Set(prevValue.ignores.map(pos => tr.mapping.map(pos)))
62
-
63
- // Calculate the new matching
64
- let matching = calcPluginStateMatching(newState, getRules())
65
-
66
- // Check if the matching should be ignored
67
- if (matching && ignoreSet.has(matching.from)) {
68
- matching = null
69
- }
70
-
71
- // Return the new matching and ignores
72
- return { matching, ignores: Array.from(ignoreSet) }
48
+ apply: (tr, prevValue, oldState, newState): PredictionPluginState => {
49
+ return handleTransaction(tr, prevValue, oldState, newState, getRules)
73
50
  },
74
51
  },
75
52
 
76
53
  view: () => ({
77
- update: (view, prevState) => {
78
- const prevValue = getPluginState(prevState)
79
- const currValue = getPluginState(view.state)
80
-
81
- if (
82
- prevValue?.matching
83
- && prevValue.matching.rule !== currValue?.matching?.rule
84
- ) {
85
- // Deactivate the previous rule
86
- prevValue.matching.rule.onLeave?.()
87
- }
88
-
89
- if (
90
- currValue?.matching
91
- && !currValue.ignores.includes(currValue.matching.from)
92
- ) {
93
- // Activate the current rule
94
-
95
- const { from, to, match, rule } = currValue.matching
96
-
97
- const textContent = view.state.doc.textBetween(
98
- from,
99
- to,
100
- null,
101
- OBJECT_REPLACEMENT_CHARACTER,
102
- )
103
-
104
- const deleteMatch = () => {
105
- if (
106
- view.state.doc.textBetween(
107
- from,
108
- to,
109
- null,
110
- OBJECT_REPLACEMENT_CHARACTER,
111
- ) === textContent
112
- ) {
113
- view.dispatch(view.state.tr.delete(from, to))
114
- }
115
- }
116
-
117
- const ignoreMatch = () => {
118
- view.dispatch(
119
- setTrMeta(view.state.tr, { ignore: from }),
120
- )
121
- }
122
-
123
- rule.onMatch({
124
- state: view.state,
125
- match,
126
- from,
127
- to,
128
- deleteMatch,
129
- ignoreMatch,
130
- })
131
- }
132
- },
54
+ update: handleUpdate,
133
55
  }),
134
56
 
135
57
  props: {
136
- decorations: (state: EditorState) => {
137
- const pluginState = getPluginState(state)
138
- if (pluginState?.matching) {
139
- const { from, to } = pluginState.matching
140
- const deco = Decoration.inline(from, to, {
141
- class: 'prosemirror-prediction-match',
142
- })
143
- return DecorationSet.create(state.doc, [deco])
58
+ handleTextInput: (view, from, to, textAdded, getTr) => {
59
+ const meta = handleTextInput(view, from, to, textAdded, getRules)
60
+ if (meta) {
61
+ const tr = getTr()
62
+ setTrMeta(tr, meta)
63
+ view.dispatch(tr)
64
+ return true
144
65
  }
145
- return null
66
+ return false
146
67
  },
68
+ decorations: getDecorations,
147
69
  },
148
70
  })
149
71
  }
150
72
 
151
- const MAX_MATCH = 200
73
+ function handleTextInput(
74
+ view: EditorView,
75
+ from: number,
76
+ to: number,
77
+ textAdded: string,
78
+ getRules: () => AutocompleteRule[],
79
+ ): PredictionTransactionMeta | undefined {
80
+ // Only handle insertions
81
+ if (from !== to) {
82
+ return
83
+ }
152
84
 
153
- function calcPluginStateMatching(
154
- state: EditorState,
155
- rules: AutocompleteRule[],
156
- ): PredictionPluginMatching | null {
157
- const $pos = state.selection.$from
85
+ const textBackward = getTextBackward(view.state.doc.resolve(from))
86
+ const textFull = textBackward + textAdded
87
+ const textTo = to + textAdded.length
88
+ const textFrom = textTo - textFull.length
158
89
 
159
- const parentOffset: number = $pos.parentOffset
90
+ const pluginState = getPluginState(view.state)
91
+ const ignores = pluginState?.ignores ?? []
92
+
93
+ const currMatching = matchRule(
94
+ view.state,
95
+ getRules(),
96
+ textFull,
97
+ textFrom,
98
+ textTo,
99
+ ignores,
100
+ )
101
+
102
+ if (currMatching) {
103
+ return { type: 'enter', matching: currMatching }
104
+ }
105
+ }
106
+
107
+ function handleTransaction(
108
+ tr: Transaction,
109
+ prevValue: PredictionPluginState,
110
+ oldState: EditorState,
111
+ newState: EditorState,
112
+ getRules: () => AutocompleteRule[],
113
+ ): PredictionPluginState {
114
+ const meta = getTrMeta(tr)
115
+
116
+ if (
117
+ !meta
118
+ && !tr.docChanged
119
+ && oldState.selection.eq(newState.selection)
120
+ ) {
121
+ // No changes
122
+ return prevValue
123
+ }
124
+
125
+ // Handle position mapping changes
126
+ const ignoreSet = new Set<number>()
127
+ for (const ignore of prevValue.ignores) {
128
+ const result = tr.mapping.mapResult(ignore)
129
+ if (!result.deletedBefore && !result.deletedAfter) {
130
+ ignoreSet.add(result.pos)
131
+ }
132
+ }
133
+ const ignores = Array.from(ignoreSet)
134
+
135
+ const prevMatching = prevValue.matching && mapMatching(prevValue.matching, tr.mapping)
136
+
137
+ // If there is no new matching from `handleTextInput`
138
+ if (!meta) {
139
+ if (!prevMatching) {
140
+ return { matching: null, ignores }
141
+ }
142
+
143
+ const { selection } = newState
144
+ // If the text selection is before the matching or after the matching,
145
+ // we leave the matching
146
+ if (selection.to < prevMatching.from || selection.from > prevMatching.to) {
147
+ ignores.push(prevMatching.from)
148
+ return { matching: null, ignores }
149
+ }
150
+
151
+ // Get the text between the existing matching
152
+ const text = getTextBetween(newState.doc, prevMatching.from, prevMatching.to)
153
+ // Check the text again to see if it still matches the rule
154
+ const currMatching = matchRule(
155
+ newState,
156
+ getRules(),
157
+ text,
158
+ prevMatching.from,
159
+ prevMatching.to,
160
+ ignores,
161
+ )
162
+ return { matching: currMatching ?? null, ignores }
163
+ }
164
+
165
+ // If a new matching is being entered from `handleTextInput`
166
+ if (meta.type === 'enter') {
167
+ // Ignore the previous matching if it is not the same as the new matching
168
+ if (prevMatching && prevMatching.from !== meta.matching.from) {
169
+ ignores.push(prevMatching.from)
170
+ }
171
+
172
+ // Return the new matching
173
+ return { matching: meta.matching, ignores }
174
+ }
160
175
 
161
- const textBefore: string = $pos.parent.textBetween(
176
+ // If a matching is being exited
177
+ if (meta.type === 'leave') {
178
+ if (prevMatching) {
179
+ ignores.push(prevMatching.from)
180
+ }
181
+ return { matching: null, ignores }
182
+ }
183
+
184
+ throw new Error(`Invalid transaction meta: ${meta satisfies never}`)
185
+ }
186
+
187
+ function handleUpdate(view: EditorView, prevState: EditorState): void {
188
+ const prevValue = getPluginState(prevState)
189
+ const currValue = getPluginState(view.state)
190
+
191
+ if (!prevValue || !currValue) {
192
+ // Should not happen
193
+ return
194
+ }
195
+
196
+ const prevMatching = prevValue.matching
197
+ const currMatching = currValue.matching
198
+
199
+ // Deactivate the previous rule
200
+ if (prevMatching && prevMatching.rule !== currMatching?.rule) {
201
+ prevMatching.rule.onLeave?.()
202
+ }
203
+
204
+ // Activate the current rule
205
+ if (currMatching) {
206
+ const { from, to, match, rule } = currMatching
207
+
208
+ const textSnapshot = getTextBetween(view.state.doc, from, to)
209
+
210
+ const deleteMatch = () => {
211
+ if (getTextBetween(view.state.doc, from, to) === textSnapshot) {
212
+ view.dispatch(view.state.tr.delete(from, to))
213
+ }
214
+ }
215
+
216
+ const ignoreMatch = () => {
217
+ view.dispatch(
218
+ setTrMeta(view.state.tr, { type: 'leave' }),
219
+ )
220
+ }
221
+
222
+ rule.onMatch({
223
+ state: view.state,
224
+ match,
225
+ from,
226
+ to,
227
+ deleteMatch,
228
+ ignoreMatch,
229
+ })
230
+ }
231
+ }
232
+
233
+ function getDecorations(state: EditorState): DecorationSet | null {
234
+ const pluginState = getPluginState(state)
235
+ if (pluginState?.matching) {
236
+ const { from, to, match } = pluginState.matching
237
+ const deco = Decoration.inline(from, to, {
238
+ 'class': 'prosekit-autocomplete-match',
239
+ 'data-autocomplete-match-text': match[0],
240
+ })
241
+ return DecorationSet.create(state.doc, [deco])
242
+ }
243
+ return null
244
+ }
245
+
246
+ const MAX_MATCH = 200
247
+
248
+ /** Get the text before the given position at the current block. */
249
+ function getTextBackward($pos: ResolvedPos): string {
250
+ const parentOffset: number = $pos.parentOffset
251
+ return getTextBetween(
252
+ $pos.parent,
162
253
  Math.max(0, parentOffset - MAX_MATCH),
163
254
  parentOffset,
255
+ )
256
+ }
257
+
258
+ function getTextBetween(node: ProseMirrorNode, from: number, to: number): string {
259
+ return node.textBetween(
260
+ from,
261
+ to,
164
262
  null,
165
263
  OBJECT_REPLACEMENT_CHARACTER,
166
264
  )
265
+ }
266
+
267
+ function matchRule(
268
+ state: EditorState,
269
+ rules: AutocompleteRule[],
270
+ text: string,
271
+ textFrom: number,
272
+ textTo: number,
273
+ ignores: Array<number>,
274
+ ): PredictionPluginMatching | undefined {
275
+ // Find the rightmost ignore point within the text range
276
+ let maxIgnore = -1
277
+ for (const ignore of ignores) {
278
+ if (ignore >= textFrom && ignore < textTo && ignore > maxIgnore) {
279
+ maxIgnore = ignore
280
+ }
281
+ }
282
+
283
+ // If an ignore point is within the text range, we ignore the text to the left
284
+ // of the ignore point (including the character right after the ignore point).
285
+ if (maxIgnore >= 0) {
286
+ const cut = maxIgnore + 1 - textFrom
287
+ text = text.slice(cut)
288
+ textFrom += cut
289
+ }
290
+
291
+ if (textFrom >= textTo || !text) {
292
+ return
293
+ }
167
294
 
168
295
  for (const rule of rules) {
169
296
  if (!rule.canMatch({ state })) {
@@ -171,16 +298,23 @@ function calcPluginStateMatching(
171
298
  }
172
299
 
173
300
  rule.regex.lastIndex = 0
174
- const match = rule.regex.exec(textBefore)
301
+ const match = rule.regex.exec(text)
175
302
  if (!match) {
176
303
  continue
177
304
  }
178
305
 
179
- const to = $pos.pos
180
- const from = to - textBefore.length + match.index
306
+ const matchTo = textTo
307
+ const matchFrom = textFrom + match.index
181
308
 
182
- return { rule, match, from, to }
309
+ return { rule, match, from: matchFrom, to: matchTo }
183
310
  }
311
+ }
184
312
 
185
- return null
313
+ function mapMatching(matching: PredictionPluginMatching, mapping: Mapping): PredictionPluginMatching {
314
+ return {
315
+ rule: matching.rule,
316
+ match: matching.match,
317
+ from: mapping.map(matching.from),
318
+ to: mapping.map(matching.to, -1),
319
+ }
186
320
  }
@@ -69,7 +69,7 @@ export interface AutocompleteRuleOptions {
69
69
  * The regular expression to match against the text before the cursor. The
70
70
  * last match before the cursor is used.
71
71
  *
72
- * For a slash menu, you might use `/(?<!\S)\/(|\S.*)$/u`.
72
+ * For a slash menu, you might use `/(?<!\S)\/(\S.*)?$/u`.
73
73
  * For a mention, you might use `/@\w*$/`
74
74
  */
75
75
  regex: RegExp
@@ -87,8 +87,8 @@ export interface AutocompleteRuleOptions {
87
87
 
88
88
  /**
89
89
  * A predicate to determine if the rule can be applied in the current editor
90
- * state. If not provided, it defaults to only allowing matches in empty
91
- * selections that are not inside a code block or code mark.
90
+ * state. If not provided, it defaults to only allowing matches that are not
91
+ * inside a code block or code mark.
92
92
  */
93
93
  canMatch?: CanMatchPredicate
94
94
  }