@prosekit/extensions 0.12.2 → 0.13.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 (55) hide show
  1. package/dist/list/style.css +5 -5
  2. package/dist/list/style.css.map +1 -1
  3. package/dist/prosekit-extensions-autocomplete.d.ts +11 -3
  4. package/dist/prosekit-extensions-autocomplete.d.ts.map +1 -1
  5. package/dist/prosekit-extensions-autocomplete.js +171 -60
  6. package/dist/prosekit-extensions-autocomplete.js.map +1 -1
  7. package/dist/prosekit-extensions-blockquote.js +1 -1
  8. package/dist/prosekit-extensions-blockquote.js.map +1 -1
  9. package/dist/prosekit-extensions-code.d.ts.map +1 -1
  10. package/dist/prosekit-extensions-commit.js +1 -1
  11. package/dist/prosekit-extensions-commit.js.map +1 -1
  12. package/dist/prosekit-extensions-heading.d.ts.map +1 -1
  13. package/dist/prosekit-extensions-heading.js +6 -6
  14. package/dist/prosekit-extensions-heading.js.map +1 -1
  15. package/dist/prosekit-extensions-loro.d.ts +16 -17
  16. package/dist/prosekit-extensions-loro.d.ts.map +1 -1
  17. package/dist/prosekit-extensions-loro.js +13 -6
  18. package/dist/prosekit-extensions-loro.js.map +1 -1
  19. package/dist/prosekit-extensions-paragraph.js +1 -1
  20. package/dist/prosekit-extensions-paragraph.js.map +1 -1
  21. package/dist/prosekit-extensions-placeholder.d.ts.map +1 -1
  22. package/dist/prosekit-extensions-placeholder.js +2 -3
  23. package/dist/prosekit-extensions-placeholder.js.map +1 -1
  24. package/dist/prosekit-extensions-strike.js +2 -2
  25. package/dist/prosekit-extensions-strike.js.map +1 -1
  26. package/dist/prosekit-extensions-table.js +0 -1
  27. package/dist/prosekit-extensions-text-align.js +4 -4
  28. package/dist/prosekit-extensions-text-align.js.map +1 -1
  29. package/dist/prosekit-extensions-yjs.js +1 -1
  30. package/dist/prosekit-extensions-yjs.js.map +1 -1
  31. package/package.json +15 -14
  32. package/src/autocomplete/autocomplete-helpers.ts +18 -9
  33. package/src/autocomplete/autocomplete-plugin.ts +261 -117
  34. package/src/autocomplete/autocomplete-rule.ts +3 -3
  35. package/src/autocomplete/autocomplete.spec.ts +239 -20
  36. package/src/autocomplete/autocomplete.ts +8 -0
  37. package/src/blockquote/blockquote-keymap.spec.ts +4 -4
  38. package/src/blockquote/blockquote-keymap.ts +1 -1
  39. package/src/commit/index.ts +1 -1
  40. package/src/hard-break/hard-break-keymap.spec.ts +5 -7
  41. package/src/heading/heading-keymap.spec.ts +7 -7
  42. package/src/heading/heading-keymap.ts +6 -6
  43. package/src/link/index.spec.ts +9 -8
  44. package/src/list/list-keymap.spec.ts +5 -5
  45. package/src/list/style.css +5 -5
  46. package/src/loro/loro-cursor-plugin.ts +23 -13
  47. package/src/loro/loro-keymap.ts +1 -1
  48. package/src/loro/loro.ts +14 -10
  49. package/src/paragraph/paragraph-keymap.ts +1 -1
  50. package/src/placeholder/index.ts +2 -1
  51. package/src/strike/index.ts +2 -2
  52. package/src/testing/index.ts +2 -2
  53. package/src/testing/keyboard.ts +0 -30
  54. package/src/text-align/index.ts +4 -4
  55. package/src/yjs/yjs-keymap.ts +1 -1
@@ -14,17 +14,17 @@
14
14
 
15
15
  & > .list-marker {
16
16
  position: absolute;
17
- left: 0;
18
17
  width: 1.5em;
19
18
  width: 1lh;
20
19
  height: 1.5em;
21
20
  height: 1lh;
21
+ inset-inline-start: 0;
22
22
  text-align: center;
23
23
  }
24
24
 
25
25
  & > .list-content {
26
- margin-left: 1.5em;
27
- margin-left: 1lh;
26
+ margin-inline-start: 1.5em;
27
+ margin-inline-start: 1lh;
28
28
  }
29
29
 
30
30
  &[data-list-kind="bullet"] > .list-marker,
@@ -64,8 +64,8 @@
64
64
 
65
65
  &::before {
66
66
  position: absolute;
67
- right: calc(100% - 1.5em);
68
- right: calc(100% - 1lh);
67
+ inset-inline-end: calc(100% - 1.5em);
68
+ inset-inline-end: calc(100% - 1lh);
69
69
  content: counter(prosemirror-flat-list-counter, decimal) ". ";
70
70
  font-variant-numeric: tabular-nums;
71
71
  }
@@ -1 +1 @@
1
- {"version":3,"file":"style.css","names":[],"sources":["../../src/list/style.css"],"sourcesContent":[":root {\n --prosekit-list-bullet-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='2.5' fill='currentColor'/%3E%3C/svg%3E\");\n --prosekit-list-toggle-open-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpolygon points='8,10 12,14 16,10' fill='currentColor'/%3E%3C/svg%3E\");\n --prosekit-list-toggle-closed-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpolygon points='10,8 14,12 10,16' fill='currentColor'/%3E%3C/svg%3E\");\n}\n\n.prosemirror-flat-list {\n & {\n position: relative;\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n & > .list-marker {\n position: absolute;\n left: 0;\n width: 1.5em;\n width: 1lh;\n height: 1.5em;\n height: 1lh;\n text-align: center;\n }\n\n & > .list-content {\n margin-left: 1.5em;\n margin-left: 1lh;\n }\n\n &[data-list-kind=\"bullet\"] > .list-marker,\n &[data-list-kind=\"toggle\"] > .list-marker {\n background-color: currentColor;\n mask-position: center;\n mask-repeat: no-repeat;\n mask-size: contain;\n }\n\n &[data-list-kind=\"bullet\"] {\n & > .list-marker {\n mask-image: var(--prosekit-list-bullet-icon);\n }\n }\n\n &[data-list-kind=\"toggle\"] {\n & > .list-marker {\n mask-image: var(--prosekit-list-toggle-open-icon);\n }\n\n &[data-list-collapsable][data-list-collapsed] > .list-marker {\n mask-image: var(--prosekit-list-toggle-closed-icon);\n }\n }\n\n &[data-list-kind=\"ordered\"] {\n /*\n Ensure that the counters in children don't escape, so that the sub lists\n won't affect the counter of the parent list.\n \n See also https://github.com/ocavue/prosemirror-flat-list/issues/23\n */\n & > * {\n contain: style;\n }\n\n &::before {\n position: absolute;\n right: calc(100% - 1.5em);\n right: calc(100% - 1lh);\n content: counter(prosemirror-flat-list-counter, decimal) \". \";\n font-variant-numeric: tabular-nums;\n }\n counter-increment: prosemirror-flat-list-counter;\n\n /* \n Reset the counter for the first list node in the sequence.\n */\n &:first-child,\n :not(&) + & {\n counter-reset: prosemirror-flat-list-counter;\n\n /* \n If the first list node has a custom order number, set the counter to that value.\n */\n &[data-list-order] {\n @supports (counter-set: prosemirror-flat-list-counter 1) {\n counter-set: prosemirror-flat-list-counter var(--prosemirror-flat-list-order);\n }\n\n /* \n Safari older than version 17.2 doesn't support `counter-set` \n */\n @supports not (counter-set: prosemirror-flat-list-counter 1) {\n counter-increment: prosemirror-flat-list-counter var(--prosemirror-flat-list-order);\n }\n }\n }\n }\n\n &[data-list-kind=\"task\"] {\n & > .list-marker {\n &,\n & * {\n /* Make sure that the checkbox is at the center */\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0;\n padding: 0;\n cursor: pointer;\n }\n }\n }\n\n &[data-list-kind=\"toggle\"] {\n &[data-list-collapsable] > .list-marker {\n cursor: pointer;\n }\n &:not([data-list-collapsable]) > .list-marker {\n opacity: 40%;\n pointer-events: none;\n }\n\n /* If collapsed, hide the second and futher children */\n &[data-list-collapsable][data-list-collapsed] > .list-content > *:nth-child(n+2) {\n display: none;\n }\n }\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA"}
1
+ {"version":3,"file":"style.css","names":[],"sources":["../../src/list/style.css"],"sourcesContent":[":root {\n --prosekit-list-bullet-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='2.5' fill='currentColor'/%3E%3C/svg%3E\");\n --prosekit-list-toggle-open-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpolygon points='8,10 12,14 16,10' fill='currentColor'/%3E%3C/svg%3E\");\n --prosekit-list-toggle-closed-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpolygon points='10,8 14,12 10,16' fill='currentColor'/%3E%3C/svg%3E\");\n}\n\n.prosemirror-flat-list {\n & {\n position: relative;\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n & > .list-marker {\n position: absolute;\n width: 1.5em;\n width: 1lh;\n height: 1.5em;\n height: 1lh;\n inset-inline-start: 0;\n text-align: center;\n }\n\n & > .list-content {\n margin-inline-start: 1.5em;\n margin-inline-start: 1lh;\n }\n\n &[data-list-kind=\"bullet\"] > .list-marker,\n &[data-list-kind=\"toggle\"] > .list-marker {\n background-color: currentColor;\n mask-position: center;\n mask-repeat: no-repeat;\n mask-size: contain;\n }\n\n &[data-list-kind=\"bullet\"] {\n & > .list-marker {\n mask-image: var(--prosekit-list-bullet-icon);\n }\n }\n\n &[data-list-kind=\"toggle\"] {\n & > .list-marker {\n mask-image: var(--prosekit-list-toggle-open-icon);\n }\n\n &[data-list-collapsable][data-list-collapsed] > .list-marker {\n mask-image: var(--prosekit-list-toggle-closed-icon);\n }\n }\n\n &[data-list-kind=\"ordered\"] {\n /*\n Ensure that the counters in children don't escape, so that the sub lists\n won't affect the counter of the parent list.\n \n See also https://github.com/ocavue/prosemirror-flat-list/issues/23\n */\n & > * {\n contain: style;\n }\n\n &::before {\n position: absolute;\n inset-inline-end: calc(100% - 1.5em);\n inset-inline-end: calc(100% - 1lh);\n content: counter(prosemirror-flat-list-counter, decimal) \". \";\n font-variant-numeric: tabular-nums;\n }\n counter-increment: prosemirror-flat-list-counter;\n\n /* \n Reset the counter for the first list node in the sequence.\n */\n &:first-child,\n :not(&) + & {\n counter-reset: prosemirror-flat-list-counter;\n\n /* \n If the first list node has a custom order number, set the counter to that value.\n */\n &[data-list-order] {\n @supports (counter-set: prosemirror-flat-list-counter 1) {\n counter-set: prosemirror-flat-list-counter var(--prosemirror-flat-list-order);\n }\n\n /* \n Safari older than version 17.2 doesn't support `counter-set` \n */\n @supports not (counter-set: prosemirror-flat-list-counter 1) {\n counter-increment: prosemirror-flat-list-counter var(--prosemirror-flat-list-order);\n }\n }\n }\n }\n\n &[data-list-kind=\"task\"] {\n & > .list-marker {\n &,\n & * {\n /* Make sure that the checkbox is at the center */\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0;\n padding: 0;\n cursor: pointer;\n }\n }\n }\n\n &[data-list-kind=\"toggle\"] {\n &[data-list-collapsable] > .list-marker {\n cursor: pointer;\n }\n &:not([data-list-collapsable]) > .list-marker {\n opacity: 40%;\n pointer-events: none;\n }\n\n /* If collapsed, hide the second and futher children */\n &[data-list-collapsable][data-list-collapsed] > .list-content > *:nth-child(n+2) {\n display: none;\n }\n }\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA"}
@@ -60,7 +60,7 @@ interface AutocompleteRuleOptions {
60
60
  * The regular expression to match against the text before the cursor. The
61
61
  * last match before the cursor is used.
62
62
  *
63
- * For a slash menu, you might use `/(?<!\S)\/(|\S.*)$/u`.
63
+ * For a slash menu, you might use `/(?<!\S)\/(\S.*)?$/u`.
64
64
  * For a mention, you might use `/@\w*$/`
65
65
  */
66
66
  regex: RegExp;
@@ -75,8 +75,8 @@ interface AutocompleteRuleOptions {
75
75
  onLeave?: VoidFunction;
76
76
  /**
77
77
  * A predicate to determine if the rule can be applied in the current editor
78
- * state. If not provided, it defaults to only allowing matches in empty
79
- * selections that are not inside a code block or code mark.
78
+ * state. If not provided, it defaults to only allowing matches that are not
79
+ * inside a code block or code mark.
80
80
  */
81
81
  canMatch?: CanMatchPredicate;
82
82
  }
@@ -100,6 +100,14 @@ declare class AutocompleteRule {
100
100
  }
101
101
  //#endregion
102
102
  //#region src/autocomplete/autocomplete.d.ts
103
+ /**
104
+ * Defines an autocomplete extension that executes logic when the text before
105
+ * the cursor matches the given regular expression.
106
+ *
107
+ * When a match is found, an inline decoration is applied to the matched text
108
+ * with the class `prosekit-autocomplete-match` and a `data-autocomplete-match-text`
109
+ * attribute containing the full matched string.
110
+ */
103
111
  declare function defineAutocomplete(rule: AutocompleteRule): Extension;
104
112
  //#endregion
105
113
  export { AutocompleteRule, type AutocompleteRuleOptions, type CanMatchOptions, type CanMatchPredicate, type MatchHandler, type MatchHandlerOptions, defineAutocomplete };
@@ -1 +1 @@
1
- {"version":3,"file":"prosekit-extensions-autocomplete.d.ts","names":[],"sources":["../src/autocomplete/autocomplete-rule.ts","../src/autocomplete/autocomplete.ts"],"sourcesContent":[],"mappings":";;;;;;;AAOiB,UAAA,mBAAA,CAAmB;EAuCxB;AAKZ;AAUA;EAKiB,KAAA,EAvDR,WAuDQ;EAQR;;;EAkBI,KAAA,EA5EJ,eA4EI;EAAiB;AAQ9B;;EAIoB,IAAA,EAAA,MAAA;EAEC;;;EAIyB,EAAA,EAAA,MAAA;;;;ACnG9C;;;;;;;;;;;;;KDmCY,YAAA,aAAyB;;;;UAKpB,eAAA;;;;SAIR;;;;;KAMG,iBAAA,aAA8B;;;;UAKzB,uBAAA;;;;;;;;SAQR;;;;;WAME;;;;YAKC;;;;;;aAOC;;;;;;;cAQA,gBAAA;;kBAEK;;oBAEE;;qBAEC;;;WAEmB;;uBAEjB;;;;iBCnGP,kBAAA,OAAyB,mBAAmB"}
1
+ {"version":3,"file":"prosekit-extensions-autocomplete.d.ts","names":[],"sources":["../src/autocomplete/autocomplete-rule.ts","../src/autocomplete/autocomplete.ts"],"sourcesContent":[],"mappings":";;;;;;;AAOiB,UAAA,mBAAA,CAAmB;EAuCxB;AAKZ;AAUA;EAKiB,KAAA,EAvDR,WAuDQ;EAQR;;;EAkBI,KAAA,EA5EJ,eA4EI;EAAiB;AAQ9B;;EAIoB,IAAA,EAAA,MAAA;EAEC;;;EAIyB,EAAA,EAAA,MAAA;;;;AC3F9C;;;;;;;;;;;;;KD2BY,YAAA,aAAyB;;;;UAKpB,eAAA;;;;SAIR;;;;;KAMG,iBAAA,aAA8B;;;;UAKzB,uBAAA;;;;;;;;SAQR;;;;;WAME;;;;YAKC;;;;;;aAOC;;;;;;;cAQA,gBAAA;;kBAEK;;oBAEE;;qBAEC;;;WAEmB;;uBAEjB;;;;;;AAvGvB;AAuCA;AAKA;AAUA;AAKA;;AAcW,iBC7DK,kBAAA,CD6DL,IAAA,EC7D8B,gBD6D9B,CAAA,EC7DiD,SD6DjD"}
@@ -4,11 +4,16 @@ import { Decoration, DecorationSet } from "@prosekit/pm/view";
4
4
 
5
5
  //#region src/autocomplete/autocomplete-helpers.ts
6
6
  function defaultCanMatch({ state }) {
7
- return state.selection.empty && !isInsideCode(state.selection.$from);
7
+ const $pos = state.selection.$from;
8
+ return !isInsideCodeBlock($pos) && !isInsideCodeMark($pos);
8
9
  }
9
- function isInsideCode($pos) {
10
+ function isInsideCodeBlock($pos) {
10
11
  for (let d = $pos.depth; d > 0; d--) if ($pos.node(d).type.spec.code) return true;
11
- return $pos.marks().some((mark) => mark.type.name === "code");
12
+ return false;
13
+ }
14
+ function isInsideCodeMark($pos) {
15
+ for (const mark of $pos.marks()) if (mark.type.spec.code) return true;
16
+ return false;
12
17
  }
13
18
  function getPluginState(state) {
14
19
  return pluginKey.getState(state);
@@ -23,6 +28,23 @@ const pluginKey = new PluginKey("prosekit-autocomplete");
23
28
 
24
29
  //#endregion
25
30
  //#region src/autocomplete/autocomplete-plugin.ts
31
+ /**
32
+ * Creates a plugin that handles autocomplete functionality.
33
+ *
34
+ * Workflow:
35
+ *
36
+ * 1. {@link handleTextInput}: called when text is going to be input, but the
37
+ * transaction is not yet created. Injects a new matching as a transaction
38
+ * meta if applicable. This is the only place to create a new matching if
39
+ * there is no existing matching.
40
+ * 2. {@link handleTransaction}: called when a transaction is going to be
41
+ * applied. Updates the plugin state based on the transaction. This step
42
+ * determines if a matching should be created, updated or removed.
43
+ * 3. {@link handleUpdate}: called when the editor state is updated. This is the
44
+ * place to call `onMatch` and register `deleteMatch` and `ignoreMatch`
45
+ * callbacks.
46
+ * 4. {@link getDecorations}: creates the decorations for the current matching.
47
+ */
26
48
  function createAutocompletePlugin({ getRules }) {
27
49
  return new Plugin({
28
50
  key: pluginKey,
@@ -34,82 +56,171 @@ function createAutocompletePlugin({ getRules }) {
34
56
  };
35
57
  },
36
58
  apply: (tr, prevValue, oldState, newState) => {
37
- const meta = getTrMeta(tr);
38
- if (!tr.docChanged && oldState.selection.eq(newState.selection) && !meta) return prevValue;
39
- if (meta) {
40
- let ignores = prevValue.ignores;
41
- if (!ignores.includes(meta.ignore)) ignores = [...ignores, meta.ignore];
42
- return {
43
- matching: null,
44
- ignores
45
- };
46
- }
47
- const ignoreSet = new Set(prevValue.ignores.map((pos) => tr.mapping.map(pos)));
48
- let matching = calcPluginStateMatching(newState, getRules());
49
- if (matching && ignoreSet.has(matching.from)) matching = null;
50
- return {
51
- matching,
52
- ignores: Array.from(ignoreSet)
53
- };
59
+ return handleTransaction(tr, prevValue, oldState, newState, getRules);
54
60
  }
55
61
  },
56
- view: () => ({ update: (view, prevState) => {
57
- const prevValue = getPluginState(prevState);
58
- const currValue = getPluginState(view.state);
59
- if (prevValue?.matching && prevValue.matching.rule !== currValue?.matching?.rule) prevValue.matching.rule.onLeave?.();
60
- if (currValue?.matching && !currValue.ignores.includes(currValue.matching.from)) {
61
- const { from, to, match, rule } = currValue.matching;
62
- const textContent = view.state.doc.textBetween(from, to, null, OBJECT_REPLACEMENT_CHARACTER);
63
- const deleteMatch = () => {
64
- if (view.state.doc.textBetween(from, to, null, OBJECT_REPLACEMENT_CHARACTER) === textContent) view.dispatch(view.state.tr.delete(from, to));
65
- };
66
- const ignoreMatch = () => {
67
- view.dispatch(setTrMeta(view.state.tr, { ignore: from }));
68
- };
69
- rule.onMatch({
70
- state: view.state,
71
- match,
72
- from,
73
- to,
74
- deleteMatch,
75
- ignoreMatch
76
- });
77
- }
78
- } }),
79
- props: { decorations: (state) => {
80
- const pluginState = getPluginState(state);
81
- if (pluginState?.matching) {
82
- const { from, to } = pluginState.matching;
83
- const deco = Decoration.inline(from, to, { class: "prosemirror-prediction-match" });
84
- return DecorationSet.create(state.doc, [deco]);
85
- }
86
- return null;
87
- } }
62
+ view: () => ({ update: handleUpdate }),
63
+ props: {
64
+ handleTextInput: (view, from, to, textAdded, getTr) => {
65
+ const meta = handleTextInput(view, from, to, textAdded, getRules);
66
+ if (meta) {
67
+ const tr = getTr();
68
+ setTrMeta(tr, meta);
69
+ view.dispatch(tr);
70
+ return true;
71
+ }
72
+ return false;
73
+ },
74
+ decorations: getDecorations
75
+ }
88
76
  });
89
77
  }
78
+ function handleTextInput(view, from, to, textAdded, getRules) {
79
+ if (from !== to) return;
80
+ const textFull = getTextBackward(view.state.doc.resolve(from)) + textAdded;
81
+ const textTo = to + textAdded.length;
82
+ const textFrom = textTo - textFull.length;
83
+ const ignores = getPluginState(view.state)?.ignores ?? [];
84
+ const currMatching = matchRule(view.state, getRules(), textFull, textFrom, textTo, ignores);
85
+ if (currMatching) return {
86
+ type: "enter",
87
+ matching: currMatching
88
+ };
89
+ }
90
+ function handleTransaction(tr, prevValue, oldState, newState, getRules) {
91
+ const meta = getTrMeta(tr);
92
+ if (!meta && !tr.docChanged && oldState.selection.eq(newState.selection)) return prevValue;
93
+ const ignoreSet = /* @__PURE__ */ new Set();
94
+ for (const ignore of prevValue.ignores) {
95
+ const result = tr.mapping.mapResult(ignore);
96
+ if (!result.deletedBefore && !result.deletedAfter) ignoreSet.add(result.pos);
97
+ }
98
+ const ignores = Array.from(ignoreSet);
99
+ const prevMatching = prevValue.matching && mapMatching(prevValue.matching, tr.mapping);
100
+ if (!meta) {
101
+ if (!prevMatching) return {
102
+ matching: null,
103
+ ignores
104
+ };
105
+ const { selection } = newState;
106
+ if (selection.to < prevMatching.from || selection.from > prevMatching.to) {
107
+ ignores.push(prevMatching.from);
108
+ return {
109
+ matching: null,
110
+ ignores
111
+ };
112
+ }
113
+ const text = getTextBetween(newState.doc, prevMatching.from, prevMatching.to);
114
+ return {
115
+ matching: matchRule(newState, getRules(), text, prevMatching.from, prevMatching.to, ignores) ?? null,
116
+ ignores
117
+ };
118
+ }
119
+ if (meta.type === "enter") {
120
+ if (prevMatching && prevMatching.from !== meta.matching.from) ignores.push(prevMatching.from);
121
+ return {
122
+ matching: meta.matching,
123
+ ignores
124
+ };
125
+ }
126
+ if (meta.type === "leave") {
127
+ if (prevMatching) ignores.push(prevMatching.from);
128
+ return {
129
+ matching: null,
130
+ ignores
131
+ };
132
+ }
133
+ throw new Error(`Invalid transaction meta: ${meta}`);
134
+ }
135
+ function handleUpdate(view, prevState) {
136
+ const prevValue = getPluginState(prevState);
137
+ const currValue = getPluginState(view.state);
138
+ if (!prevValue || !currValue) return;
139
+ const prevMatching = prevValue.matching;
140
+ const currMatching = currValue.matching;
141
+ if (prevMatching && prevMatching.rule !== currMatching?.rule) prevMatching.rule.onLeave?.();
142
+ if (currMatching) {
143
+ const { from, to, match, rule } = currMatching;
144
+ const textSnapshot = getTextBetween(view.state.doc, from, to);
145
+ const deleteMatch = () => {
146
+ if (getTextBetween(view.state.doc, from, to) === textSnapshot) view.dispatch(view.state.tr.delete(from, to));
147
+ };
148
+ const ignoreMatch = () => {
149
+ view.dispatch(setTrMeta(view.state.tr, { type: "leave" }));
150
+ };
151
+ rule.onMatch({
152
+ state: view.state,
153
+ match,
154
+ from,
155
+ to,
156
+ deleteMatch,
157
+ ignoreMatch
158
+ });
159
+ }
160
+ }
161
+ function getDecorations(state) {
162
+ const pluginState = getPluginState(state);
163
+ if (pluginState?.matching) {
164
+ const { from, to, match } = pluginState.matching;
165
+ const deco = Decoration.inline(from, to, {
166
+ "class": "prosekit-autocomplete-match",
167
+ "data-autocomplete-match-text": match[0]
168
+ });
169
+ return DecorationSet.create(state.doc, [deco]);
170
+ }
171
+ return null;
172
+ }
90
173
  const MAX_MATCH = 200;
91
- function calcPluginStateMatching(state, rules) {
92
- const $pos = state.selection.$from;
174
+ /** Get the text before the given position at the current block. */
175
+ function getTextBackward($pos) {
93
176
  const parentOffset = $pos.parentOffset;
94
- const textBefore = $pos.parent.textBetween(Math.max(0, parentOffset - MAX_MATCH), parentOffset, null, OBJECT_REPLACEMENT_CHARACTER);
177
+ return getTextBetween($pos.parent, Math.max(0, parentOffset - MAX_MATCH), parentOffset);
178
+ }
179
+ function getTextBetween(node, from, to) {
180
+ return node.textBetween(from, to, null, OBJECT_REPLACEMENT_CHARACTER);
181
+ }
182
+ function matchRule(state, rules, text, textFrom, textTo, ignores) {
183
+ let maxIgnore = -1;
184
+ for (const ignore of ignores) if (ignore >= textFrom && ignore < textTo && ignore > maxIgnore) maxIgnore = ignore;
185
+ if (maxIgnore >= 0) {
186
+ const cut = maxIgnore + 1 - textFrom;
187
+ text = text.slice(cut);
188
+ textFrom += cut;
189
+ }
190
+ if (textFrom >= textTo || !text) return;
95
191
  for (const rule of rules) {
96
192
  if (!rule.canMatch({ state })) continue;
97
193
  rule.regex.lastIndex = 0;
98
- const match = rule.regex.exec(textBefore);
194
+ const match = rule.regex.exec(text);
99
195
  if (!match) continue;
100
- const to = $pos.pos;
196
+ const matchTo = textTo;
101
197
  return {
102
198
  rule,
103
199
  match,
104
- from: to - textBefore.length + match.index,
105
- to
200
+ from: textFrom + match.index,
201
+ to: matchTo
106
202
  };
107
203
  }
108
- return null;
204
+ }
205
+ function mapMatching(matching, mapping) {
206
+ return {
207
+ rule: matching.rule,
208
+ match: matching.match,
209
+ from: mapping.map(matching.from),
210
+ to: mapping.map(matching.to, -1)
211
+ };
109
212
  }
110
213
 
111
214
  //#endregion
112
215
  //#region src/autocomplete/autocomplete.ts
216
+ /**
217
+ * Defines an autocomplete extension that executes logic when the text before
218
+ * the cursor matches the given regular expression.
219
+ *
220
+ * When a match is found, an inline decoration is applied to the matched text
221
+ * with the class `prosekit-autocomplete-match` and a `data-autocomplete-match-text`
222
+ * attribute containing the full matched string.
223
+ */
113
224
  function defineAutocomplete(rule) {
114
225
  return defineFacetPayload(autocompleteFacet, [rule]);
115
226
  }
@@ -1 +1 @@
1
- {"version":3,"file":"prosekit-extensions-autocomplete.js","names":["pluginKey: PluginKey<PredictionPluginState>","parentOffset: number","textBefore: string","rules: AutocompleteRule[]"],"sources":["../src/autocomplete/autocomplete-helpers.ts","../src/autocomplete/autocomplete-plugin.ts","../src/autocomplete/autocomplete.ts","../src/autocomplete/autocomplete-rule.ts"],"sourcesContent":["import type { ResolvedPos } from '@prosekit/pm/model'\nimport {\n PluginKey,\n type EditorState,\n type Transaction,\n} from '@prosekit/pm/state'\n\nimport type { AutocompleteRule } from './autocomplete-rule'\n\nexport function defaultCanMatch({ state }: { state: EditorState }): boolean {\n return state.selection.empty && !isInsideCode(state.selection.$from)\n}\n\nfunction isInsideCode($pos: ResolvedPos): boolean {\n for (let d = $pos.depth; d > 0; d--) {\n if ($pos.node(d).type.spec.code) {\n return true\n }\n }\n\n return $pos.marks().some((mark) => mark.type.name === 'code')\n}\n\n/**\n * @internal\n */\nexport interface PredictionPluginMatching {\n rule: AutocompleteRule\n from: number\n to: number\n match: RegExpExecArray\n}\n\n/**\n * @internal\n */\nexport interface PredictionPluginState {\n /**\n * The matching positions that should be ignored.\n */\n ignores: number[]\n\n /**\n * The current active matching.\n */\n matching: PredictionPluginMatching | null\n}\n\n/**\n * @internal\n */\ninterface PredictionTransactionMeta {\n /**\n * The from position that should be ignored.\n */\n ignore: number\n}\n\nexport function getPluginState(state: EditorState): PredictionPluginState | undefined {\n return pluginKey.getState(state)\n}\n\nexport function getTrMeta(tr: Transaction): PredictionTransactionMeta | undefined {\n return tr.getMeta(pluginKey) as PredictionTransactionMeta | undefined\n}\n\nexport function setTrMeta(\n tr: Transaction,\n meta: PredictionTransactionMeta,\n): Transaction {\n return tr.setMeta(pluginKey, meta)\n}\n\nexport const pluginKey: PluginKey<PredictionPluginState> = new PluginKey<PredictionPluginState>('prosekit-autocomplete')\n","import { OBJECT_REPLACEMENT_CHARACTER } from '@prosekit/core'\nimport {\n Plugin,\n type EditorState,\n type Transaction,\n} from '@prosekit/pm/state'\nimport {\n Decoration,\n DecorationSet,\n} from '@prosekit/pm/view'\n\nimport {\n getPluginState,\n getTrMeta,\n pluginKey,\n setTrMeta,\n type PredictionPluginMatching,\n type PredictionPluginState,\n} from './autocomplete-helpers'\nimport type { AutocompleteRule } from './autocomplete-rule'\n\nexport function createAutocompletePlugin({\n getRules,\n}: {\n getRules: () => AutocompleteRule[]\n}): Plugin {\n return new Plugin<PredictionPluginState>({\n key: pluginKey,\n\n state: {\n init: (): PredictionPluginState => {\n return { ignores: [], matching: null }\n },\n apply: (\n tr: Transaction,\n prevValue: PredictionPluginState,\n oldState: EditorState,\n newState: EditorState,\n ): PredictionPluginState => {\n const meta = getTrMeta(tr)\n\n // No changes\n if (\n !tr.docChanged\n && oldState.selection.eq(newState.selection)\n && !meta\n ) {\n return prevValue\n }\n\n // Receiving a meta means that we are ignoring a match\n if (meta) {\n let ignores = prevValue.ignores\n if (!ignores.includes(meta.ignore)) {\n ignores = [...ignores, meta.ignore]\n }\n return { matching: null, ignores }\n }\n\n // Calculate the new ignores\n const ignoreSet = new Set(prevValue.ignores.map(pos => tr.mapping.map(pos)))\n\n // Calculate the new matching\n let matching = calcPluginStateMatching(newState, getRules())\n\n // Check if the matching should be ignored\n if (matching && ignoreSet.has(matching.from)) {\n matching = null\n }\n\n // Return the new matching and ignores\n return { matching, ignores: Array.from(ignoreSet) }\n },\n },\n\n view: () => ({\n update: (view, prevState) => {\n const prevValue = getPluginState(prevState)\n const currValue = getPluginState(view.state)\n\n if (\n prevValue?.matching\n && prevValue.matching.rule !== currValue?.matching?.rule\n ) {\n // Deactivate the previous rule\n prevValue.matching.rule.onLeave?.()\n }\n\n if (\n currValue?.matching\n && !currValue.ignores.includes(currValue.matching.from)\n ) {\n // Activate the current rule\n\n const { from, to, match, rule } = currValue.matching\n\n const textContent = view.state.doc.textBetween(\n from,\n to,\n null,\n OBJECT_REPLACEMENT_CHARACTER,\n )\n\n const deleteMatch = () => {\n if (\n view.state.doc.textBetween(\n from,\n to,\n null,\n OBJECT_REPLACEMENT_CHARACTER,\n ) === textContent\n ) {\n view.dispatch(view.state.tr.delete(from, to))\n }\n }\n\n const ignoreMatch = () => {\n view.dispatch(\n setTrMeta(view.state.tr, { ignore: from }),\n )\n }\n\n rule.onMatch({\n state: view.state,\n match,\n from,\n to,\n deleteMatch,\n ignoreMatch,\n })\n }\n },\n }),\n\n props: {\n decorations: (state: EditorState) => {\n const pluginState = getPluginState(state)\n if (pluginState?.matching) {\n const { from, to } = pluginState.matching\n const deco = Decoration.inline(from, to, {\n class: 'prosemirror-prediction-match',\n })\n return DecorationSet.create(state.doc, [deco])\n }\n return null\n },\n },\n })\n}\n\nconst MAX_MATCH = 200\n\nfunction calcPluginStateMatching(\n state: EditorState,\n rules: AutocompleteRule[],\n): PredictionPluginMatching | null {\n const $pos = state.selection.$from\n\n const parentOffset: number = $pos.parentOffset\n\n const textBefore: string = $pos.parent.textBetween(\n Math.max(0, parentOffset - MAX_MATCH),\n parentOffset,\n null,\n OBJECT_REPLACEMENT_CHARACTER,\n )\n\n for (const rule of rules) {\n if (!rule.canMatch({ state })) {\n continue\n }\n\n rule.regex.lastIndex = 0\n const match = rule.regex.exec(textBefore)\n if (!match) {\n continue\n }\n\n const to = $pos.pos\n const from = to - textBefore.length + match.index\n\n return { rule, match, from, to }\n }\n\n return null\n}\n","import {\n defineFacet,\n defineFacetPayload,\n pluginFacet,\n type Extension,\n type PluginPayload,\n} from '@prosekit/core'\n\nimport { createAutocompletePlugin } from './autocomplete-plugin'\nimport type { AutocompleteRule } from './autocomplete-rule'\n\nexport function defineAutocomplete(rule: AutocompleteRule): Extension {\n return defineFacetPayload(autocompleteFacet, [rule])\n}\n\nconst autocompleteFacet = defineFacet<AutocompleteRule, PluginPayload>({\n reduce: () => {\n let rules: AutocompleteRule[] = []\n const getRules = () => rules\n const plugin = createAutocompletePlugin({ getRules })\n\n return function reducer(inputs) {\n rules = inputs\n return plugin\n }\n },\n parent: pluginFacet,\n singleton: true,\n})\n","import type { EditorState } from '@prosekit/pm/state'\n\nimport { defaultCanMatch } from './autocomplete-helpers'\n\n/**\n * Options for the {@link MatchHandler} callback.\n */\nexport interface MatchHandlerOptions {\n /**\n * The editor state.\n */\n state: EditorState\n\n /**\n * The result of `RegExp.exec`.\n */\n match: RegExpExecArray\n\n /**\n * The start position of the matched text.\n */\n from: number\n\n /**\n * The end position of the matched text.\n */\n to: number\n\n /**\n * Call this function to ignore the match. You probably want to call this\n * function when the user presses the `Escape` key.\n */\n ignoreMatch: () => void\n\n /**\n * Call this function to delete the matched text. For example, in a slash\n * menu, you might want to delete the matched text first then do something\n * else when the user presses the `Enter` key.\n */\n deleteMatch: () => void\n}\n\n/**\n * A callback that is called when the rule starts to match, and also on\n * subsequent updates while the rule continues to match.\n */\nexport type MatchHandler = (options: MatchHandlerOptions) => void\n\n/**\n * Options for the {@link CanMatchPredicate} callback.\n */\nexport interface CanMatchOptions {\n /**\n * The editor state.\n */\n state: EditorState\n}\n\n/**\n * A predicate to determine if the rule can be applied in the current editor state.\n */\nexport type CanMatchPredicate = (options: CanMatchOptions) => boolean\n\n/**\n * Options for creating an {@link AutocompleteRule}\n */\nexport interface AutocompleteRuleOptions {\n /**\n * The regular expression to match against the text before the cursor. The\n * last match before the cursor is used.\n *\n * For a slash menu, you might use `/(?<!\\S)\\/(|\\S.*)$/u`.\n * For a mention, you might use `/@\\w*$/`\n */\n regex: RegExp\n\n /**\n * A callback that is called when the rule starts to match, and also on\n * subsequent updates while the rule continues to match.\n */\n onEnter: MatchHandler\n\n /**\n * A callback that is called when the rule stops matching.\n */\n onLeave?: VoidFunction\n\n /**\n * A predicate to determine if the rule can be applied in the current editor\n * state. If not provided, it defaults to only allowing matches in empty\n * selections that are not inside a code block or code mark.\n */\n canMatch?: CanMatchPredicate\n}\n\n/**\n * An autocomplete rule that can be used to create an autocomplete extension.\n *\n * @public\n */\nexport class AutocompleteRule {\n /** @internal */\n readonly regex: RegExp\n /** @internal */\n readonly onMatch: MatchHandler\n /** @internal */\n readonly onLeave?: VoidFunction\n /** @internal */\n readonly canMatch: (options: { state: EditorState }) => boolean\n\n constructor(options: AutocompleteRuleOptions) {\n this.regex = options.regex\n this.onMatch = options.onEnter\n this.onLeave = options.onLeave\n this.canMatch = options.canMatch ?? defaultCanMatch\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"prosekit-extensions-autocomplete.js","names":["pluginKey: PluginKey<PredictionPluginState>","parentOffset: number","rules: AutocompleteRule[]"],"sources":["../src/autocomplete/autocomplete-helpers.ts","../src/autocomplete/autocomplete-plugin.ts","../src/autocomplete/autocomplete.ts","../src/autocomplete/autocomplete-rule.ts"],"sourcesContent":["import type { ResolvedPos } from '@prosekit/pm/model'\nimport {\n PluginKey,\n type EditorState,\n type Transaction,\n} from '@prosekit/pm/state'\n\nimport type { AutocompleteRule } from './autocomplete-rule'\n\nexport function defaultCanMatch({ state }: { state: EditorState }): boolean {\n const $pos = state.selection.$from\n return !isInsideCodeBlock($pos) && !isInsideCodeMark($pos)\n}\n\nfunction isInsideCodeBlock($pos: ResolvedPos): boolean {\n for (let d = $pos.depth; d > 0; d--) {\n if ($pos.node(d).type.spec.code) {\n return true\n }\n }\n return false\n}\n\nfunction isInsideCodeMark($pos: ResolvedPos): boolean {\n for (const mark of $pos.marks()) {\n if (mark.type.spec.code) {\n return true\n }\n }\n return false\n}\n\n/**\n * @internal\n */\nexport interface PredictionPluginMatching {\n rule: AutocompleteRule\n from: number\n to: number\n match: RegExpExecArray\n}\n\n/**\n * @internal\n */\nexport interface PredictionPluginState {\n /**\n * The matching positions that should be ignored.\n */\n ignores: Array<number>\n\n /**\n * The current active matching.\n */\n matching: PredictionPluginMatching | null\n}\n\n/**\n * @internal\n */\nexport type PredictionTransactionMeta = {\n type: 'enter'\n matching: PredictionPluginMatching\n} | {\n type: 'leave'\n}\n\nexport function getPluginState(state: EditorState): PredictionPluginState | undefined {\n return pluginKey.getState(state)\n}\n\nexport function getTrMeta(tr: Transaction): PredictionTransactionMeta | undefined {\n return tr.getMeta(pluginKey) as PredictionTransactionMeta | undefined\n}\n\nexport function setTrMeta(\n tr: Transaction,\n meta: PredictionTransactionMeta,\n): Transaction {\n return tr.setMeta(pluginKey, meta)\n}\n\nexport const pluginKey: PluginKey<PredictionPluginState> = new PluginKey<PredictionPluginState>('prosekit-autocomplete')\n","import { OBJECT_REPLACEMENT_CHARACTER } from '@prosekit/core'\nimport type {\n ProseMirrorNode,\n ResolvedPos,\n} from '@prosekit/pm/model'\nimport {\n Plugin,\n type EditorState,\n type Transaction,\n} from '@prosekit/pm/state'\nimport type { Mapping } from '@prosekit/pm/transform'\nimport type { EditorView } from '@prosekit/pm/view'\nimport {\n Decoration,\n DecorationSet,\n} from '@prosekit/pm/view'\n\nimport {\n getPluginState,\n getTrMeta,\n pluginKey,\n setTrMeta,\n type PredictionPluginMatching,\n type PredictionPluginState,\n type PredictionTransactionMeta,\n} from './autocomplete-helpers'\nimport type { AutocompleteRule } from './autocomplete-rule'\n\n/**\n * Creates a plugin that handles autocomplete functionality.\n *\n * Workflow:\n *\n * 1. {@link handleTextInput}: called when text is going to be input, but the\n * transaction is not yet created. Injects a new matching as a transaction\n * meta if applicable. This is the only place to create a new matching if\n * there is no existing matching.\n * 2. {@link handleTransaction}: called when a transaction is going to be\n * applied. Updates the plugin state based on the transaction. This step\n * determines if a matching should be created, updated or removed.\n * 3. {@link handleUpdate}: called when the editor state is updated. This is the\n * place to call `onMatch` and register `deleteMatch` and `ignoreMatch`\n * callbacks.\n * 4. {@link getDecorations}: creates the decorations for the current matching.\n */\nexport function createAutocompletePlugin({\n getRules,\n}: {\n getRules: () => AutocompleteRule[]\n}): Plugin {\n return new Plugin<PredictionPluginState>({\n key: pluginKey,\n\n state: {\n init: (): PredictionPluginState => {\n return { ignores: [], matching: null }\n },\n apply: (tr, prevValue, oldState, newState): PredictionPluginState => {\n return handleTransaction(tr, prevValue, oldState, newState, getRules)\n },\n },\n\n view: () => ({\n update: handleUpdate,\n }),\n\n props: {\n handleTextInput: (view, from, to, textAdded, getTr) => {\n const meta = handleTextInput(view, from, to, textAdded, getRules)\n if (meta) {\n const tr = getTr()\n setTrMeta(tr, meta)\n view.dispatch(tr)\n return true\n }\n return false\n },\n decorations: getDecorations,\n },\n })\n}\n\nfunction handleTextInput(\n view: EditorView,\n from: number,\n to: number,\n textAdded: string,\n getRules: () => AutocompleteRule[],\n): PredictionTransactionMeta | undefined {\n // Only handle insertions\n if (from !== to) {\n return\n }\n\n const textBackward = getTextBackward(view.state.doc.resolve(from))\n const textFull = textBackward + textAdded\n const textTo = to + textAdded.length\n const textFrom = textTo - textFull.length\n\n const pluginState = getPluginState(view.state)\n const ignores = pluginState?.ignores ?? []\n\n const currMatching = matchRule(\n view.state,\n getRules(),\n textFull,\n textFrom,\n textTo,\n ignores,\n )\n\n if (currMatching) {\n return { type: 'enter', matching: currMatching }\n }\n}\n\nfunction handleTransaction(\n tr: Transaction,\n prevValue: PredictionPluginState,\n oldState: EditorState,\n newState: EditorState,\n getRules: () => AutocompleteRule[],\n): PredictionPluginState {\n const meta = getTrMeta(tr)\n\n if (\n !meta\n && !tr.docChanged\n && oldState.selection.eq(newState.selection)\n ) {\n // No changes\n return prevValue\n }\n\n // Handle position mapping changes\n const ignoreSet = new Set<number>()\n for (const ignore of prevValue.ignores) {\n const result = tr.mapping.mapResult(ignore)\n if (!result.deletedBefore && !result.deletedAfter) {\n ignoreSet.add(result.pos)\n }\n }\n const ignores = Array.from(ignoreSet)\n\n const prevMatching = prevValue.matching && mapMatching(prevValue.matching, tr.mapping)\n\n // If there is no new matching from `handleTextInput`\n if (!meta) {\n if (!prevMatching) {\n return { matching: null, ignores }\n }\n\n const { selection } = newState\n // If the text selection is before the matching or after the matching,\n // we leave the matching\n if (selection.to < prevMatching.from || selection.from > prevMatching.to) {\n ignores.push(prevMatching.from)\n return { matching: null, ignores }\n }\n\n // Get the text between the existing matching\n const text = getTextBetween(newState.doc, prevMatching.from, prevMatching.to)\n // Check the text again to see if it still matches the rule\n const currMatching = matchRule(\n newState,\n getRules(),\n text,\n prevMatching.from,\n prevMatching.to,\n ignores,\n )\n return { matching: currMatching ?? null, ignores }\n }\n\n // If a new matching is being entered from `handleTextInput`\n if (meta.type === 'enter') {\n // Ignore the previous matching if it is not the same as the new matching\n if (prevMatching && prevMatching.from !== meta.matching.from) {\n ignores.push(prevMatching.from)\n }\n\n // Return the new matching\n return { matching: meta.matching, ignores }\n }\n\n // If a matching is being exited\n if (meta.type === 'leave') {\n if (prevMatching) {\n ignores.push(prevMatching.from)\n }\n return { matching: null, ignores }\n }\n\n throw new Error(`Invalid transaction meta: ${meta satisfies never}`)\n}\n\nfunction handleUpdate(view: EditorView, prevState: EditorState): void {\n const prevValue = getPluginState(prevState)\n const currValue = getPluginState(view.state)\n\n if (!prevValue || !currValue) {\n // Should not happen\n return\n }\n\n const prevMatching = prevValue.matching\n const currMatching = currValue.matching\n\n // Deactivate the previous rule\n if (prevMatching && prevMatching.rule !== currMatching?.rule) {\n prevMatching.rule.onLeave?.()\n }\n\n // Activate the current rule\n if (currMatching) {\n const { from, to, match, rule } = currMatching\n\n const textSnapshot = getTextBetween(view.state.doc, from, to)\n\n const deleteMatch = () => {\n if (getTextBetween(view.state.doc, from, to) === textSnapshot) {\n view.dispatch(view.state.tr.delete(from, to))\n }\n }\n\n const ignoreMatch = () => {\n view.dispatch(\n setTrMeta(view.state.tr, { type: 'leave' }),\n )\n }\n\n rule.onMatch({\n state: view.state,\n match,\n from,\n to,\n deleteMatch,\n ignoreMatch,\n })\n }\n}\n\nfunction getDecorations(state: EditorState): DecorationSet | null {\n const pluginState = getPluginState(state)\n if (pluginState?.matching) {\n const { from, to, match } = pluginState.matching\n const deco = Decoration.inline(from, to, {\n 'class': 'prosekit-autocomplete-match',\n 'data-autocomplete-match-text': match[0],\n })\n return DecorationSet.create(state.doc, [deco])\n }\n return null\n}\n\nconst MAX_MATCH = 200\n\n/** Get the text before the given position at the current block. */\nfunction getTextBackward($pos: ResolvedPos): string {\n const parentOffset: number = $pos.parentOffset\n return getTextBetween(\n $pos.parent,\n Math.max(0, parentOffset - MAX_MATCH),\n parentOffset,\n )\n}\n\nfunction getTextBetween(node: ProseMirrorNode, from: number, to: number): string {\n return node.textBetween(\n from,\n to,\n null,\n OBJECT_REPLACEMENT_CHARACTER,\n )\n}\n\nfunction matchRule(\n state: EditorState,\n rules: AutocompleteRule[],\n text: string,\n textFrom: number,\n textTo: number,\n ignores: Array<number>,\n): PredictionPluginMatching | undefined {\n // Find the rightmost ignore point within the text range\n let maxIgnore = -1\n for (const ignore of ignores) {\n if (ignore >= textFrom && ignore < textTo && ignore > maxIgnore) {\n maxIgnore = ignore\n }\n }\n\n // If an ignore point is within the text range, we ignore the text to the left\n // of the ignore point (including the character right after the ignore point).\n if (maxIgnore >= 0) {\n const cut = maxIgnore + 1 - textFrom\n text = text.slice(cut)\n textFrom += cut\n }\n\n if (textFrom >= textTo || !text) {\n return\n }\n\n for (const rule of rules) {\n if (!rule.canMatch({ state })) {\n continue\n }\n\n rule.regex.lastIndex = 0\n const match = rule.regex.exec(text)\n if (!match) {\n continue\n }\n\n const matchTo = textTo\n const matchFrom = textFrom + match.index\n\n return { rule, match, from: matchFrom, to: matchTo }\n }\n}\n\nfunction mapMatching(matching: PredictionPluginMatching, mapping: Mapping): PredictionPluginMatching {\n return {\n rule: matching.rule,\n match: matching.match,\n from: mapping.map(matching.from),\n to: mapping.map(matching.to, -1),\n }\n}\n","import {\n defineFacet,\n defineFacetPayload,\n pluginFacet,\n type Extension,\n type PluginPayload,\n} from '@prosekit/core'\n\nimport { createAutocompletePlugin } from './autocomplete-plugin'\nimport type { AutocompleteRule } from './autocomplete-rule'\n\n/**\n * Defines an autocomplete extension that executes logic when the text before\n * the cursor matches the given regular expression.\n *\n * When a match is found, an inline decoration is applied to the matched text\n * with the class `prosekit-autocomplete-match` and a `data-autocomplete-match-text`\n * attribute containing the full matched string.\n */\nexport function defineAutocomplete(rule: AutocompleteRule): Extension {\n return defineFacetPayload(autocompleteFacet, [rule])\n}\n\nconst autocompleteFacet = defineFacet<AutocompleteRule, PluginPayload>({\n reduce: () => {\n let rules: AutocompleteRule[] = []\n const getRules = () => rules\n const plugin = createAutocompletePlugin({ getRules })\n\n return function reducer(inputs) {\n rules = inputs\n return plugin\n }\n },\n parent: pluginFacet,\n singleton: true,\n})\n","import type { EditorState } from '@prosekit/pm/state'\n\nimport { defaultCanMatch } from './autocomplete-helpers'\n\n/**\n * Options for the {@link MatchHandler} callback.\n */\nexport interface MatchHandlerOptions {\n /**\n * The editor state.\n */\n state: EditorState\n\n /**\n * The result of `RegExp.exec`.\n */\n match: RegExpExecArray\n\n /**\n * The start position of the matched text.\n */\n from: number\n\n /**\n * The end position of the matched text.\n */\n to: number\n\n /**\n * Call this function to ignore the match. You probably want to call this\n * function when the user presses the `Escape` key.\n */\n ignoreMatch: () => void\n\n /**\n * Call this function to delete the matched text. For example, in a slash\n * menu, you might want to delete the matched text first then do something\n * else when the user presses the `Enter` key.\n */\n deleteMatch: () => void\n}\n\n/**\n * A callback that is called when the rule starts to match, and also on\n * subsequent updates while the rule continues to match.\n */\nexport type MatchHandler = (options: MatchHandlerOptions) => void\n\n/**\n * Options for the {@link CanMatchPredicate} callback.\n */\nexport interface CanMatchOptions {\n /**\n * The editor state.\n */\n state: EditorState\n}\n\n/**\n * A predicate to determine if the rule can be applied in the current editor state.\n */\nexport type CanMatchPredicate = (options: CanMatchOptions) => boolean\n\n/**\n * Options for creating an {@link AutocompleteRule}\n */\nexport interface AutocompleteRuleOptions {\n /**\n * The regular expression to match against the text before the cursor. The\n * last match before the cursor is used.\n *\n * For a slash menu, you might use `/(?<!\\S)\\/(\\S.*)?$/u`.\n * For a mention, you might use `/@\\w*$/`\n */\n regex: RegExp\n\n /**\n * A callback that is called when the rule starts to match, and also on\n * subsequent updates while the rule continues to match.\n */\n onEnter: MatchHandler\n\n /**\n * A callback that is called when the rule stops matching.\n */\n onLeave?: VoidFunction\n\n /**\n * A predicate to determine if the rule can be applied in the current editor\n * state. If not provided, it defaults to only allowing matches that are not\n * inside a code block or code mark.\n */\n canMatch?: CanMatchPredicate\n}\n\n/**\n * An autocomplete rule that can be used to create an autocomplete extension.\n *\n * @public\n */\nexport class AutocompleteRule {\n /** @internal */\n readonly regex: RegExp\n /** @internal */\n readonly onMatch: MatchHandler\n /** @internal */\n readonly onLeave?: VoidFunction\n /** @internal */\n readonly canMatch: (options: { state: EditorState }) => boolean\n\n constructor(options: AutocompleteRuleOptions) {\n this.regex = options.regex\n this.onMatch = options.onEnter\n this.onLeave = options.onLeave\n this.canMatch = options.canMatch ?? defaultCanMatch\n }\n}\n"],"mappings":"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"}
@@ -49,7 +49,7 @@ function backspaceUnsetBlockquote() {
49
49
  */
50
50
  function defineBlockquoteKeymap() {
51
51
  return defineKeymap({
52
- "mod-shift-b": toggleBlockquoteKeybinding(),
52
+ "Mod-B": toggleBlockquoteKeybinding(),
53
53
  "Backspace": backspaceUnsetBlockquote()
54
54
  });
55
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"prosekit-extensions-blockquote.js","names":[],"sources":["../src/blockquote/blockquote-commands.ts","../src/blockquote/blockquote-input-rule.ts","../src/blockquote/blockquote-keymap.ts","../src/blockquote/blockquote-spec.ts","../src/blockquote/blockquote.ts"],"sourcesContent":["import {\n defineCommands,\n insertNode,\n toggleWrap,\n wrap,\n type Extension,\n} from '@prosekit/core'\n\nexport type BlockquoteCommandsExtension = Extension<{\n Commands: {\n setBlockquote: []\n insertBlockquote: []\n toggleBlockquote: []\n }\n}>\n\n/**\n * @internal\n */\nexport function defineBlockquoteCommands(): BlockquoteCommandsExtension {\n return defineCommands({\n setBlockquote: () => {\n return wrap({ type: 'blockquote' })\n },\n insertBlockquote: () => {\n return insertNode({ type: 'blockquote' })\n },\n toggleBlockquote: () => {\n return toggleWrap({ type: 'blockquote' })\n },\n })\n}\n","import type { PlainExtension } from '@prosekit/core'\n\nimport { defineWrappingInputRule } from '../input-rule'\n\n/**\n * Wraps the text block in a blockquote when `>` is typed at the start of a new\n * line followed by a space.\n */\nexport function defineBlockquoteInputRule(): PlainExtension {\n return defineWrappingInputRule({\n regex: /^>\\s/,\n type: 'blockquote',\n })\n}\n","import {\n defineKeymap,\n isAtBlockStart,\n toggleWrap,\n type PlainExtension,\n} from '@prosekit/core'\nimport { joinBackward } from '@prosekit/pm/commands'\nimport type { Command } from '@prosekit/pm/state'\n\nfunction toggleBlockquoteKeybinding(): Command {\n return toggleWrap({ type: 'blockquote' })\n}\n\nfunction backspaceUnsetBlockquote(): Command {\n return (state, dispatch, view) => {\n const $pos = isAtBlockStart(state, view)\n if ($pos?.node(-1).type.name === 'blockquote') {\n return joinBackward(state, dispatch, view)\n }\n return false\n }\n}\n/**\n * @internal\n */\nexport function defineBlockquoteKeymap(): PlainExtension {\n return defineKeymap({\n 'mod-shift-b': toggleBlockquoteKeybinding(),\n 'Backspace': backspaceUnsetBlockquote(),\n })\n}\n","import {\n defineNodeSpec,\n type Extension,\n} from '@prosekit/core'\nimport type { Attrs } from '@prosekit/pm/model'\n\nexport type BlockquoteSpecExtension = Extension<{\n Nodes: {\n blockquote: Attrs\n }\n}>\n\nexport function defineBlockquoteSpec(): BlockquoteSpecExtension {\n return defineNodeSpec({\n name: 'blockquote',\n content: 'block+',\n group: 'block',\n defining: true,\n parseDOM: [{ tag: 'blockquote' }],\n toDOM() {\n return ['blockquote', 0]\n },\n })\n}\n","import {\n union,\n type Union,\n} from '@prosekit/core'\n\nimport {\n defineBlockquoteCommands,\n type BlockquoteCommandsExtension,\n} from './blockquote-commands'\nimport { defineBlockquoteInputRule } from './blockquote-input-rule'\nimport { defineBlockquoteKeymap } from './blockquote-keymap'\nimport {\n defineBlockquoteSpec,\n type BlockquoteSpecExtension,\n} from './blockquote-spec'\n\n/**\n * @internal\n */\nexport type BlockquoteExtension = Union<\n [BlockquoteSpecExtension, BlockquoteCommandsExtension]\n>\n\n/**\n * @public\n */\nexport function defineBlockquote(): BlockquoteExtension {\n return union(\n defineBlockquoteSpec(),\n defineBlockquoteInputRule(),\n defineBlockquoteCommands(),\n defineBlockquoteKeymap(),\n )\n}\n"],"mappings":";;;;;;;;AAmBA,SAAgB,2BAAwD;AACtE,QAAO,eAAe;EACpB,qBAAqB;AACnB,UAAO,KAAK,EAAE,MAAM,cAAc,CAAC;;EAErC,wBAAwB;AACtB,UAAO,WAAW,EAAE,MAAM,cAAc,CAAC;;EAE3C,wBAAwB;AACtB,UAAO,WAAW,EAAE,MAAM,cAAc,CAAC;;EAE5C,CAAC;;;;;;;;;ACtBJ,SAAgB,4BAA4C;AAC1D,QAAO,wBAAwB;EAC7B,OAAO;EACP,MAAM;EACP,CAAC;;;;;ACHJ,SAAS,6BAAsC;AAC7C,QAAO,WAAW,EAAE,MAAM,cAAc,CAAC;;AAG3C,SAAS,2BAAoC;AAC3C,SAAQ,OAAO,UAAU,SAAS;AAEhC,MADa,eAAe,OAAO,KAAK,EAC9B,KAAK,GAAG,CAAC,KAAK,SAAS,aAC/B,QAAO,aAAa,OAAO,UAAU,KAAK;AAE5C,SAAO;;;;;;AAMX,SAAgB,yBAAyC;AACvD,QAAO,aAAa;EAClB,eAAe,4BAA4B;EAC3C,aAAa,0BAA0B;EACxC,CAAC;;;;;ACjBJ,SAAgB,uBAAgD;AAC9D,QAAO,eAAe;EACpB,MAAM;EACN,SAAS;EACT,OAAO;EACP,UAAU;EACV,UAAU,CAAC,EAAE,KAAK,cAAc,CAAC;EACjC,QAAQ;AACN,UAAO,CAAC,cAAc,EAAE;;EAE3B,CAAC;;;;;;;;ACIJ,SAAgB,mBAAwC;AACtD,QAAO,MACL,sBAAsB,EACtB,2BAA2B,EAC3B,0BAA0B,EAC1B,wBAAwB,CACzB"}
1
+ {"version":3,"file":"prosekit-extensions-blockquote.js","names":[],"sources":["../src/blockquote/blockquote-commands.ts","../src/blockquote/blockquote-input-rule.ts","../src/blockquote/blockquote-keymap.ts","../src/blockquote/blockquote-spec.ts","../src/blockquote/blockquote.ts"],"sourcesContent":["import {\n defineCommands,\n insertNode,\n toggleWrap,\n wrap,\n type Extension,\n} from '@prosekit/core'\n\nexport type BlockquoteCommandsExtension = Extension<{\n Commands: {\n setBlockquote: []\n insertBlockquote: []\n toggleBlockquote: []\n }\n}>\n\n/**\n * @internal\n */\nexport function defineBlockquoteCommands(): BlockquoteCommandsExtension {\n return defineCommands({\n setBlockquote: () => {\n return wrap({ type: 'blockquote' })\n },\n insertBlockquote: () => {\n return insertNode({ type: 'blockquote' })\n },\n toggleBlockquote: () => {\n return toggleWrap({ type: 'blockquote' })\n },\n })\n}\n","import type { PlainExtension } from '@prosekit/core'\n\nimport { defineWrappingInputRule } from '../input-rule'\n\n/**\n * Wraps the text block in a blockquote when `>` is typed at the start of a new\n * line followed by a space.\n */\nexport function defineBlockquoteInputRule(): PlainExtension {\n return defineWrappingInputRule({\n regex: /^>\\s/,\n type: 'blockquote',\n })\n}\n","import {\n defineKeymap,\n isAtBlockStart,\n toggleWrap,\n type PlainExtension,\n} from '@prosekit/core'\nimport { joinBackward } from '@prosekit/pm/commands'\nimport type { Command } from '@prosekit/pm/state'\n\nfunction toggleBlockquoteKeybinding(): Command {\n return toggleWrap({ type: 'blockquote' })\n}\n\nfunction backspaceUnsetBlockquote(): Command {\n return (state, dispatch, view) => {\n const $pos = isAtBlockStart(state, view)\n if ($pos?.node(-1).type.name === 'blockquote') {\n return joinBackward(state, dispatch, view)\n }\n return false\n }\n}\n/**\n * @internal\n */\nexport function defineBlockquoteKeymap(): PlainExtension {\n return defineKeymap({\n 'Mod-B': toggleBlockquoteKeybinding(),\n 'Backspace': backspaceUnsetBlockquote(),\n })\n}\n","import {\n defineNodeSpec,\n type Extension,\n} from '@prosekit/core'\nimport type { Attrs } from '@prosekit/pm/model'\n\nexport type BlockquoteSpecExtension = Extension<{\n Nodes: {\n blockquote: Attrs\n }\n}>\n\nexport function defineBlockquoteSpec(): BlockquoteSpecExtension {\n return defineNodeSpec({\n name: 'blockquote',\n content: 'block+',\n group: 'block',\n defining: true,\n parseDOM: [{ tag: 'blockquote' }],\n toDOM() {\n return ['blockquote', 0]\n },\n })\n}\n","import {\n union,\n type Union,\n} from '@prosekit/core'\n\nimport {\n defineBlockquoteCommands,\n type BlockquoteCommandsExtension,\n} from './blockquote-commands'\nimport { defineBlockquoteInputRule } from './blockquote-input-rule'\nimport { defineBlockquoteKeymap } from './blockquote-keymap'\nimport {\n defineBlockquoteSpec,\n type BlockquoteSpecExtension,\n} from './blockquote-spec'\n\n/**\n * @internal\n */\nexport type BlockquoteExtension = Union<\n [BlockquoteSpecExtension, BlockquoteCommandsExtension]\n>\n\n/**\n * @public\n */\nexport function defineBlockquote(): BlockquoteExtension {\n return union(\n defineBlockquoteSpec(),\n defineBlockquoteInputRule(),\n defineBlockquoteCommands(),\n defineBlockquoteKeymap(),\n )\n}\n"],"mappings":";;;;;;;;AAmBA,SAAgB,2BAAwD;AACtE,QAAO,eAAe;EACpB,qBAAqB;AACnB,UAAO,KAAK,EAAE,MAAM,cAAc,CAAC;;EAErC,wBAAwB;AACtB,UAAO,WAAW,EAAE,MAAM,cAAc,CAAC;;EAE3C,wBAAwB;AACtB,UAAO,WAAW,EAAE,MAAM,cAAc,CAAC;;EAE5C,CAAC;;;;;;;;;ACtBJ,SAAgB,4BAA4C;AAC1D,QAAO,wBAAwB;EAC7B,OAAO;EACP,MAAM;EACP,CAAC;;;;;ACHJ,SAAS,6BAAsC;AAC7C,QAAO,WAAW,EAAE,MAAM,cAAc,CAAC;;AAG3C,SAAS,2BAAoC;AAC3C,SAAQ,OAAO,UAAU,SAAS;AAEhC,MADa,eAAe,OAAO,KAAK,EAC9B,KAAK,GAAG,CAAC,KAAK,SAAS,aAC/B,QAAO,aAAa,OAAO,UAAU,KAAK;AAE5C,SAAO;;;;;;AAMX,SAAgB,yBAAyC;AACvD,QAAO,aAAa;EAClB,SAAS,4BAA4B;EACrC,aAAa,0BAA0B;EACxC,CAAC;;;;;ACjBJ,SAAgB,uBAAgD;AAC9D,QAAO,eAAe;EACpB,MAAM;EACN,SAAS;EACT,OAAO;EACP,UAAU;EACV,UAAU,CAAC,EAAE,KAAK,cAAc,CAAC;EACjC,QAAQ;AACN,UAAO,CAAC,cAAc,EAAE;;EAE3B,CAAC;;;;;;;;ACIJ,SAAgB,mBAAwC;AACtD,QAAO,MACL,sBAAsB,EACtB,2BAA2B,EAC3B,0BAA0B,EAC1B,wBAAwB,CACzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"prosekit-extensions-code.d.ts","names":[],"sources":["../src/code/code-commands.ts","../src/code/code-spec.ts","../src/code/code.ts","../src/code/code-input-rule.ts","../src/code/code-keymap.ts"],"sourcesContent":[],"mappings":";;;;;;;AASY,KAAA,qBAAA,GAAwB,SAAA,CAAA;EASpB,QAAA,EAAA;;;;ACThB;AASA;;iBDAgB,kBAAA,CAAA,GAAsB;;;;;AATtC;AASgB,KCTJ,iBAAA,GAAoB,SDSM,CAAA;;UCP5B;;AAFV,CAAA,CAAA;AASA;;;iBAAgB,cAAA,CAAA,GAAkB;;;;ADTlC;AASA;KECY,aAAA,GAAgB,OAAO,mBAAmB;;;ADVtD;AASgB,iBCMA,UAAA,CAAA,CDNkB,ECMJ,aDNqB;;;;;;ADTvC,iBGCI,mBAAA,CAAA,CHDoB,EGCG,cHDM;;;;;;AAAjC,iBIAI,gBAAA,CAAA,CJAoB,EIAA,cJAS"}
1
+ {"version":3,"file":"prosekit-extensions-code.d.ts","names":[],"sources":["../src/code/code-commands.ts","../src/code/code-spec.ts","../src/code/code.ts","../src/code/code-input-rule.ts","../src/code/code-keymap.ts"],"sourcesContent":[],"mappings":";;;;;;;AASY,KAAA,qBAAA,GAAwB,SAAA,CAAA;EASpB,QAAA,EAAA;;;;ACThB;AASA;;iBDAgB,kBAAA,CAAA,GAAsB;;;;;AATtC;AASgB,KCTJ,iBAAA,GAAoB,SDSM,CAAA;;UCP5B;;AAFV,CAAA,CAAA;AASA;;;iBAAgB,cAAA,CAAA,GAAkB;;;;ADTlC;AASA;KECY,aAAA,GAAgB,OAAO,mBAAmB;;;ADVtD;AASgB,iBCMA,UAAA,CAAA,CDNkB,ECMJ,aDNI;;;;;;ADTtB,iBGCI,mBAAA,CAAA,CHDoB,EGCG,cHDM;;;;;;AAAjC,iBIAI,gBAAA,CAAA,CJAoB,EIAA,cJAS"}
@@ -99,7 +99,7 @@ function defineCommitDecoration(commit) {
99
99
  * Define an extension to display the changes from the given commit in the editor.
100
100
  */
101
101
  function defineCommitViewer(commit) {
102
- return union(defineDefaultState({ defaultDoc: commit.doc }), defineCommitDecoration(commit));
102
+ return union(defineDefaultState({ defaultContent: commit.doc }), defineCommitDecoration(commit));
103
103
  }
104
104
  var CommitRecorder = class {
105
105
  constructor() {
@@ -1 +1 @@
1
- {"version":3,"file":"prosekit-extensions-commit.js","names":["decorations: Decoration[]","commit: Commit"],"sources":["../src/commit/index.ts"],"sourcesContent":["import {\n defineDefaultState,\n definePlugin,\n jsonFromNode,\n union,\n type NodeJSON,\n type PlainExtension,\n type StepJSON,\n} from '@prosekit/core'\nimport {\n DOMSerializer,\n Fragment,\n Slice,\n type ProseMirrorNode,\n} from '@prosekit/pm/model'\nimport {\n PluginKey,\n ProseMirrorPlugin,\n type Transaction,\n} from '@prosekit/pm/state'\nimport { Step } from '@prosekit/pm/transform'\nimport {\n Decoration,\n DecorationSet,\n type EditorView,\n} from '@prosekit/pm/view'\nimport {\n ChangeSet,\n type Change,\n} from 'prosemirror-changeset'\n\n/**\n * A JSON representation of a commit.\n */\ninterface Commit {\n /**\n * The current doc node in the JSON format\n */\n doc: NodeJSON\n /**\n * The parent node in the JSON format\n */\n parent: NodeJSON\n /**\n * An array of steps in the JSON format that transform the parent node to the\n * current doc node.\n */\n steps: StepJSON[]\n}\n\nfunction getChanges(\n doc: ProseMirrorNode,\n parent: ProseMirrorNode,\n steps: Step[],\n): readonly Change[] {\n const initSet = ChangeSet.create(parent)\n const currSet = initSet.addSteps(\n doc,\n steps.map((step) => step.getMap()),\n null,\n )\n return currSet.changes\n}\n\nfunction renderDivWeight(view: EditorView): HTMLElement {\n const document = view.dom.ownerDocument\n return document.createElement('div')\n}\n\nfunction decorateDeletionSlice(\n slice: Slice,\n): Array<(view: EditorView) => HTMLElement> {\n // Get the fragment of the deleted content\n let { openStart, openEnd, content } = slice\n\n while (openStart > 0 && openEnd > 0 && content.childCount === 1) {\n openStart--\n openEnd--\n content = content.child(0).content\n }\n\n // Nothing to render\n if (content.childCount === 0) {\n return []\n }\n\n // For example, if the slice is\n // {\n // openStart: 1,\n // openEnd: 1,\n // content: <p>Hello</p><p>World</p>\n // }\n // We should render the following decorations:\n // <span>Hello</span>\n // <div></div>\n // <span>World</span>\n if (openStart > 0 && openEnd > 0 && content.childCount === 2) {\n const head = Fragment.from([content.child(0)])\n const tail = Fragment.from([content.child(1)])\n return [\n ...decorateDeletionSlice(new Slice(head, openStart, openStart)),\n renderDivWeight,\n ...decorateDeletionSlice(new Slice(tail, openEnd, openEnd)),\n ]\n }\n\n // For example, if the slice is\n // {\n // openStart: 1,\n // openEnd: 0,\n // content: <p>Hello</p><p>World</p>\n // }\n // We should render the following decorations:\n // <span>Hello</span>\n // <div><p>World</p></div>\n if (openStart > 0 && content.childCount >= 2) {\n const nodes = content.content\n const head = Fragment.from(nodes.slice(0, 1))\n const body = Fragment.from(nodes.slice(1))\n\n return [\n ...decorateDeletionSlice(new Slice(head, openStart, openStart)),\n ...decorateDeletionSlice(new Slice(body, 0, openEnd)),\n ]\n }\n\n // For example, if the slice is\n // {\n // openStart: 0,\n // openEnd: 1,\n // content: <p>Hello</p><p>World</p>\n // }\n // We should render the following decorations:\n // <div><p>Hello</p></div>\n // <span>World</span>\n if (openEnd > 0 && content.childCount >= 2) {\n const nodes = content.content\n const body = Fragment.from(nodes.slice(0, -1))\n const tail = Fragment.from(nodes.slice(-1))\n return [\n ...decorateDeletionSlice(new Slice(body, openStart, 0)),\n ...decorateDeletionSlice(new Slice(tail, openEnd, openEnd)),\n ]\n }\n\n const schema = content.child(0).type.schema\n const isInline = content.child(0).isInline\n\n const render = (view: EditorView): HTMLElement => {\n const document = view.dom.ownerDocument\n\n // Render the fragment to HTML\n const element = document.createElement(isInline ? 'span' : 'div')\n const serializer = DOMSerializer.fromSchema(schema)\n serializer.serializeFragment(content, { document }, element)\n\n // Add the class to the element\n element.classList.add('prosekit-commit-deletion')\n return element\n }\n\n return [render]\n}\n\nfunction decorateDeletion(\n /** The doc node before the deletion */\n doc: ProseMirrorNode,\n /** The start position of the deleted text in the doc node */\n from: number,\n /** The end position of the deleted text in the doc node */\n to: number,\n /** The insert position of the decoration in the doc node after the change */\n pos: number,\n): Decoration[] {\n const slice = doc.slice(from, to)\n\n const renders = decorateDeletionSlice(slice)\n const count = renders.length\n\n return renders.map((render, index) =>\n Decoration.widget(pos, render, {\n side: -20 - count + index,\n // Ensure the text in the decoration is able to be selected.\n ignoreSelection: true,\n })\n )\n}\n\nfunction decorateAddition(\n /** The start position of the inserted text in the doc node */\n from: number,\n /** The end position of the inserted text in the doc node */\n to: number,\n): Decoration {\n return Decoration.inline(from, to, { class: 'prosekit-commit-addition' })\n}\n\nfunction decorateChange(prev: ProseMirrorNode, change: Change): Decoration[] {\n const { fromA, toA, fromB, toB } = change\n const decorations: Decoration[] = []\n\n if (fromA < toA) {\n decorations.push(...decorateDeletion(prev, fromA, toA, fromB))\n }\n if (fromB < toB) {\n decorations.push(decorateAddition(fromB, toB))\n }\n\n return decorations\n}\n\nfunction decorateCommit(\n doc: ProseMirrorNode,\n parent: ProseMirrorNode,\n steps: Step[],\n): DecorationSet {\n const changes = getChanges(doc, parent, steps)\n const decorations = changes.flatMap((change) => decorateChange(parent, change))\n return DecorationSet.create(doc, decorations)\n}\n\nfunction defineCommitDecoration(commit: Commit): PlainExtension {\n const key = new PluginKey<DecorationSet>('prosekit-commit-decoration')\n\n return definePlugin(({ schema }): ProseMirrorPlugin => {\n const parent = schema.nodeFromJSON(commit.parent)\n const steps = commit.steps.map((step) => Step.fromJSON(schema, step))\n\n return new ProseMirrorPlugin({\n key,\n state: {\n init: (_, instance): DecorationSet => {\n return decorateCommit(instance.doc, parent, steps)\n },\n apply: (tr, deco: DecorationSet): DecorationSet => {\n return deco.map(tr.mapping, tr.doc)\n },\n },\n props: {\n decorations: (state): DecorationSet | undefined => {\n return key.getState(state)\n },\n },\n })\n })\n}\n\n/**\n * Define an extension to display the changes from the given commit in the editor.\n */\nfunction defineCommitViewer(commit: Commit): PlainExtension {\n return union(\n defineDefaultState({ defaultDoc: commit.doc }),\n defineCommitDecoration(commit),\n )\n}\n\nclass CommitRecorder {\n private parent: ProseMirrorNode | null = null\n private doc: ProseMirrorNode | null = null\n private steps: Step[] = []\n\n /**\n * Return a commit object including all changes since the last commit. `null`\n * will be returned if there is no change.\n */\n commit(): Commit | null {\n if (\n !this.parent\n || !this.doc\n || this.steps.length === 0\n || this.parent.eq(this.doc)\n ) {\n return null\n }\n\n const commit: Commit = {\n doc: jsonFromNode(this.doc),\n parent: jsonFromNode(this.parent),\n steps: this.steps.map((step) => step.toJSON() as StepJSON),\n }\n this.init(this.doc)\n return commit\n }\n\n /**\n * @internal\n */\n init(doc: ProseMirrorNode): void {\n this.doc = doc\n this.parent = doc\n this.steps = []\n }\n\n /**\n * @internal\n */\n apply(tr: Transaction): void {\n this.steps.push(...tr.steps)\n this.doc = tr.doc\n }\n}\n\n/**\n * Define an extension that can record the changes in the editor.\n */\nfunction defineCommitRecorder(commitRecorder: CommitRecorder): PlainExtension {\n const key = new PluginKey<DecorationSet>('prosekit-commit-recorder')\n\n return definePlugin(\n new ProseMirrorPlugin({\n key,\n state: {\n init: (_, state): void => {\n commitRecorder.init(state.doc)\n },\n apply: (tr): void => {\n commitRecorder.apply(tr)\n },\n },\n }),\n )\n}\n\nexport {\n CommitRecorder,\n defineCommitRecorder,\n defineCommitViewer,\n type Commit,\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"prosekit-extensions-commit.js","names":["decorations: Decoration[]","commit: Commit"],"sources":["../src/commit/index.ts"],"sourcesContent":["import {\n defineDefaultState,\n definePlugin,\n jsonFromNode,\n union,\n type NodeJSON,\n type PlainExtension,\n type StepJSON,\n} from '@prosekit/core'\nimport {\n DOMSerializer,\n Fragment,\n Slice,\n type ProseMirrorNode,\n} from '@prosekit/pm/model'\nimport {\n PluginKey,\n ProseMirrorPlugin,\n type Transaction,\n} from '@prosekit/pm/state'\nimport { Step } from '@prosekit/pm/transform'\nimport {\n Decoration,\n DecorationSet,\n type EditorView,\n} from '@prosekit/pm/view'\nimport {\n ChangeSet,\n type Change,\n} from 'prosemirror-changeset'\n\n/**\n * A JSON representation of a commit.\n */\ninterface Commit {\n /**\n * The current doc node in the JSON format\n */\n doc: NodeJSON\n /**\n * The parent node in the JSON format\n */\n parent: NodeJSON\n /**\n * An array of steps in the JSON format that transform the parent node to the\n * current doc node.\n */\n steps: StepJSON[]\n}\n\nfunction getChanges(\n doc: ProseMirrorNode,\n parent: ProseMirrorNode,\n steps: Step[],\n): readonly Change[] {\n const initSet = ChangeSet.create(parent)\n const currSet = initSet.addSteps(\n doc,\n steps.map((step) => step.getMap()),\n null,\n )\n return currSet.changes\n}\n\nfunction renderDivWeight(view: EditorView): HTMLElement {\n const document = view.dom.ownerDocument\n return document.createElement('div')\n}\n\nfunction decorateDeletionSlice(\n slice: Slice,\n): Array<(view: EditorView) => HTMLElement> {\n // Get the fragment of the deleted content\n let { openStart, openEnd, content } = slice\n\n while (openStart > 0 && openEnd > 0 && content.childCount === 1) {\n openStart--\n openEnd--\n content = content.child(0).content\n }\n\n // Nothing to render\n if (content.childCount === 0) {\n return []\n }\n\n // For example, if the slice is\n // {\n // openStart: 1,\n // openEnd: 1,\n // content: <p>Hello</p><p>World</p>\n // }\n // We should render the following decorations:\n // <span>Hello</span>\n // <div></div>\n // <span>World</span>\n if (openStart > 0 && openEnd > 0 && content.childCount === 2) {\n const head = Fragment.from([content.child(0)])\n const tail = Fragment.from([content.child(1)])\n return [\n ...decorateDeletionSlice(new Slice(head, openStart, openStart)),\n renderDivWeight,\n ...decorateDeletionSlice(new Slice(tail, openEnd, openEnd)),\n ]\n }\n\n // For example, if the slice is\n // {\n // openStart: 1,\n // openEnd: 0,\n // content: <p>Hello</p><p>World</p>\n // }\n // We should render the following decorations:\n // <span>Hello</span>\n // <div><p>World</p></div>\n if (openStart > 0 && content.childCount >= 2) {\n const nodes = content.content\n const head = Fragment.from(nodes.slice(0, 1))\n const body = Fragment.from(nodes.slice(1))\n\n return [\n ...decorateDeletionSlice(new Slice(head, openStart, openStart)),\n ...decorateDeletionSlice(new Slice(body, 0, openEnd)),\n ]\n }\n\n // For example, if the slice is\n // {\n // openStart: 0,\n // openEnd: 1,\n // content: <p>Hello</p><p>World</p>\n // }\n // We should render the following decorations:\n // <div><p>Hello</p></div>\n // <span>World</span>\n if (openEnd > 0 && content.childCount >= 2) {\n const nodes = content.content\n const body = Fragment.from(nodes.slice(0, -1))\n const tail = Fragment.from(nodes.slice(-1))\n return [\n ...decorateDeletionSlice(new Slice(body, openStart, 0)),\n ...decorateDeletionSlice(new Slice(tail, openEnd, openEnd)),\n ]\n }\n\n const schema = content.child(0).type.schema\n const isInline = content.child(0).isInline\n\n const render = (view: EditorView): HTMLElement => {\n const document = view.dom.ownerDocument\n\n // Render the fragment to HTML\n const element = document.createElement(isInline ? 'span' : 'div')\n const serializer = DOMSerializer.fromSchema(schema)\n serializer.serializeFragment(content, { document }, element)\n\n // Add the class to the element\n element.classList.add('prosekit-commit-deletion')\n return element\n }\n\n return [render]\n}\n\nfunction decorateDeletion(\n /** The doc node before the deletion */\n doc: ProseMirrorNode,\n /** The start position of the deleted text in the doc node */\n from: number,\n /** The end position of the deleted text in the doc node */\n to: number,\n /** The insert position of the decoration in the doc node after the change */\n pos: number,\n): Decoration[] {\n const slice = doc.slice(from, to)\n\n const renders = decorateDeletionSlice(slice)\n const count = renders.length\n\n return renders.map((render, index) =>\n Decoration.widget(pos, render, {\n side: -20 - count + index,\n // Ensure the text in the decoration is able to be selected.\n ignoreSelection: true,\n })\n )\n}\n\nfunction decorateAddition(\n /** The start position of the inserted text in the doc node */\n from: number,\n /** The end position of the inserted text in the doc node */\n to: number,\n): Decoration {\n return Decoration.inline(from, to, { class: 'prosekit-commit-addition' })\n}\n\nfunction decorateChange(prev: ProseMirrorNode, change: Change): Decoration[] {\n const { fromA, toA, fromB, toB } = change\n const decorations: Decoration[] = []\n\n if (fromA < toA) {\n decorations.push(...decorateDeletion(prev, fromA, toA, fromB))\n }\n if (fromB < toB) {\n decorations.push(decorateAddition(fromB, toB))\n }\n\n return decorations\n}\n\nfunction decorateCommit(\n doc: ProseMirrorNode,\n parent: ProseMirrorNode,\n steps: Step[],\n): DecorationSet {\n const changes = getChanges(doc, parent, steps)\n const decorations = changes.flatMap((change) => decorateChange(parent, change))\n return DecorationSet.create(doc, decorations)\n}\n\nfunction defineCommitDecoration(commit: Commit): PlainExtension {\n const key = new PluginKey<DecorationSet>('prosekit-commit-decoration')\n\n return definePlugin(({ schema }): ProseMirrorPlugin => {\n const parent = schema.nodeFromJSON(commit.parent)\n const steps = commit.steps.map((step) => Step.fromJSON(schema, step))\n\n return new ProseMirrorPlugin({\n key,\n state: {\n init: (_, instance): DecorationSet => {\n return decorateCommit(instance.doc, parent, steps)\n },\n apply: (tr, deco: DecorationSet): DecorationSet => {\n return deco.map(tr.mapping, tr.doc)\n },\n },\n props: {\n decorations: (state): DecorationSet | undefined => {\n return key.getState(state)\n },\n },\n })\n })\n}\n\n/**\n * Define an extension to display the changes from the given commit in the editor.\n */\nfunction defineCommitViewer(commit: Commit): PlainExtension {\n return union(\n defineDefaultState({ defaultContent: commit.doc }),\n defineCommitDecoration(commit),\n )\n}\n\nclass CommitRecorder {\n private parent: ProseMirrorNode | null = null\n private doc: ProseMirrorNode | null = null\n private steps: Step[] = []\n\n /**\n * Return a commit object including all changes since the last commit. `null`\n * will be returned if there is no change.\n */\n commit(): Commit | null {\n if (\n !this.parent\n || !this.doc\n || this.steps.length === 0\n || this.parent.eq(this.doc)\n ) {\n return null\n }\n\n const commit: Commit = {\n doc: jsonFromNode(this.doc),\n parent: jsonFromNode(this.parent),\n steps: this.steps.map((step) => step.toJSON() as StepJSON),\n }\n this.init(this.doc)\n return commit\n }\n\n /**\n * @internal\n */\n init(doc: ProseMirrorNode): void {\n this.doc = doc\n this.parent = doc\n this.steps = []\n }\n\n /**\n * @internal\n */\n apply(tr: Transaction): void {\n this.steps.push(...tr.steps)\n this.doc = tr.doc\n }\n}\n\n/**\n * Define an extension that can record the changes in the editor.\n */\nfunction defineCommitRecorder(commitRecorder: CommitRecorder): PlainExtension {\n const key = new PluginKey<DecorationSet>('prosekit-commit-recorder')\n\n return definePlugin(\n new ProseMirrorPlugin({\n key,\n state: {\n init: (_, state): void => {\n commitRecorder.init(state.doc)\n },\n apply: (tr): void => {\n commitRecorder.apply(tr)\n },\n },\n }),\n )\n}\n\nexport {\n CommitRecorder,\n defineCommitRecorder,\n defineCommitViewer,\n type Commit,\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"prosekit-extensions-heading.d.ts","names":[],"sources":["../src/heading/heading-types.ts","../src/heading/heading-commands.ts","../src/heading/heading-spec.ts","../src/heading/heading.ts","../src/heading/heading-input-rule.ts","../src/heading/heading-keymap.ts"],"sourcesContent":[],"mappings":";;;UAAiB,YAAA;;;;;;AAAjB;;KCaY,wBAAA,GAA2B;;IAA3B,UAAA,EAAA,CAAA,KAAA,GAEa,YAFW,GAAA,SAAA,CAAA;IAEX,aAAA,EAAA,CAAA,KAAA,GACG,YADH,GAAA,SAAA,CAAA;IACG,aAAA,EAAA,CAAA,KAAA,GACA,YADA,GAAA,SAAA,CAAA;EACA,CAAA;CAJW,CAAA;;AAWvC;;iBAAgB,qBAAA,CAAA,GAAyB;;;;ADxBzC;;KEUY,oBAAA,GAAuB;;IDGvB,OAAA,ECDC,YDCD;EAEa,CAAA;CACG,CAAA;;;;AAQZ,iBCLA,iBAAA,CAAA,CDKyB,ECLJ,oBDKI;;;ADxBzC;;;KGmBY,gBAAA,GAAmB,OAC5B,oBFPS,EEOa,wBFLA,CACG,CAAA;;;;AAQZ,iBEEA,aAAA,CAAA,CFFqB,EEEJ,gBFFQ;;;;;ADxBzC;;;;ACaY,iBGDI,sBAAA,CAAA,CHCoB,EGDM,cHCN;;;;;ADbpC;iBK6BgB,mBAAA,CAAA,GAAuB"}
1
+ {"version":3,"file":"prosekit-extensions-heading.d.ts","names":[],"sources":["../src/heading/heading-types.ts","../src/heading/heading-commands.ts","../src/heading/heading-spec.ts","../src/heading/heading.ts","../src/heading/heading-input-rule.ts","../src/heading/heading-keymap.ts"],"sourcesContent":[],"mappings":";;;UAAiB,YAAA;;;;;;AAAjB;;KCaY,wBAAA,GAA2B;;IAA3B,UAAA,EAAA,CAAA,KAAA,GAEa,YAFW,GAAA,SAAA,CAAA;IAEX,aAAA,EAAA,CAAA,KAAA,GACG,YADH,GAAA,SAAA,CAAA;IACG,aAAA,EAAA,CAAA,KAAA,GACA,YADA,GAAA,SAAA,CAAA;EACA,CAAA;CAJW,CAAA;;AAWvC;;iBAAgB,qBAAA,CAAA,GAAyB;;;;ADxBzC;;KEUY,oBAAA,GAAuB;;IDGvB,OAAA,ECDC,YDCD;EAEa,CAAA;CACG,CAAA;;;;AAQZ,iBCLA,iBAAA,CAAA,CDKyB,ECLJ,oBDK4B;;;ADxBjE;;;KGmBY,gBAAA,GAAmB,OAC5B,oBFPS,EEOa,wBFLA,CACG,CAAA;;;;AAQZ,iBEEA,aAAA,CAAA,CFFqB,EEEJ,gBFFQ;;;;;ADxBzC;;;;ACaY,iBGDI,sBAAA,CAAA,CHCoB,EGDM,cHCN;;;;;ADbpC;iBK6BgB,mBAAA,CAAA,GAAuB"}
@@ -67,12 +67,12 @@ const backspaceUnsetHeading = (state, dispatch, view) => {
67
67
  */
68
68
  function defineHeadingKeymap() {
69
69
  return defineKeymap({
70
- "mod-alt-1": toggleHeadingKeybinding(1),
71
- "mod-alt-2": toggleHeadingKeybinding(2),
72
- "mod-alt-3": toggleHeadingKeybinding(3),
73
- "mod-alt-4": toggleHeadingKeybinding(4),
74
- "mod-alt-5": toggleHeadingKeybinding(5),
75
- "mod-alt-6": toggleHeadingKeybinding(6),
70
+ "Mod-Alt-1": toggleHeadingKeybinding(1),
71
+ "Mod-Alt-2": toggleHeadingKeybinding(2),
72
+ "Mod-Alt-3": toggleHeadingKeybinding(3),
73
+ "Mod-Alt-4": toggleHeadingKeybinding(4),
74
+ "Mod-Alt-5": toggleHeadingKeybinding(5),
75
+ "Mod-Alt-6": toggleHeadingKeybinding(6),
76
76
  "Backspace": backspaceUnsetHeading
77
77
  });
78
78
  }