@kerebron/extension-ui 0.8.1

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 (85) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +40 -0
  3. package/assets/ui.css +32 -0
  4. package/esm/ExtensionUi.d.ts +6 -0
  5. package/esm/ExtensionUi.d.ts.map +1 -0
  6. package/esm/ExtensionUi.js +9 -0
  7. package/esm/ExtensionUi.js.map +1 -0
  8. package/esm/autocomplete/AutocompletePlugin.d.ts +53 -0
  9. package/esm/autocomplete/AutocompletePlugin.d.ts.map +1 -0
  10. package/esm/autocomplete/AutocompletePlugin.js +417 -0
  11. package/esm/autocomplete/AutocompletePlugin.js.map +1 -0
  12. package/esm/autocomplete/DefaultRenderer.d.ts +25 -0
  13. package/esm/autocomplete/DefaultRenderer.d.ts.map +1 -0
  14. package/esm/autocomplete/DefaultRenderer.js +162 -0
  15. package/esm/autocomplete/DefaultRenderer.js.map +1 -0
  16. package/esm/autocomplete/ExtensionAutocomplete.d.ts +11 -0
  17. package/esm/autocomplete/ExtensionAutocomplete.d.ts.map +1 -0
  18. package/esm/autocomplete/ExtensionAutocomplete.js +33 -0
  19. package/esm/autocomplete/ExtensionAutocomplete.js.map +1 -0
  20. package/esm/autocomplete/createDefaultMatcher.d.ts +11 -0
  21. package/esm/autocomplete/createDefaultMatcher.d.ts.map +1 -0
  22. package/esm/autocomplete/createDefaultMatcher.js +58 -0
  23. package/esm/autocomplete/createDefaultMatcher.js.map +1 -0
  24. package/esm/autocomplete/createPosMatcher.d.ts +3 -0
  25. package/esm/autocomplete/createPosMatcher.d.ts.map +1 -0
  26. package/esm/autocomplete/createPosMatcher.js +16 -0
  27. package/esm/autocomplete/createPosMatcher.js.map +1 -0
  28. package/esm/autocomplete/createRegexMatcher.d.ts +4 -0
  29. package/esm/autocomplete/createRegexMatcher.d.ts.map +1 -0
  30. package/esm/autocomplete/createRegexMatcher.js +50 -0
  31. package/esm/autocomplete/createRegexMatcher.js.map +1 -0
  32. package/esm/autocomplete/mod.d.ts +6 -0
  33. package/esm/autocomplete/mod.d.ts.map +1 -0
  34. package/esm/autocomplete/mod.js +6 -0
  35. package/esm/autocomplete/mod.js.map +1 -0
  36. package/esm/autocomplete/types.d.ts +49 -0
  37. package/esm/autocomplete/types.d.ts.map +1 -0
  38. package/esm/autocomplete/types.js +2 -0
  39. package/esm/autocomplete/types.js.map +1 -0
  40. package/esm/hover/ExtensionHover.d.ts +11 -0
  41. package/esm/hover/ExtensionHover.d.ts.map +1 -0
  42. package/esm/hover/ExtensionHover.js +33 -0
  43. package/esm/hover/ExtensionHover.js.map +1 -0
  44. package/esm/hover/HoverPlugin.d.ts +49 -0
  45. package/esm/hover/HoverPlugin.d.ts.map +1 -0
  46. package/esm/hover/HoverPlugin.js +368 -0
  47. package/esm/hover/HoverPlugin.js.map +1 -0
  48. package/esm/hover/MarkdownRenderer.d.ts +23 -0
  49. package/esm/hover/MarkdownRenderer.d.ts.map +1 -0
  50. package/esm/hover/MarkdownRenderer.js +54 -0
  51. package/esm/hover/MarkdownRenderer.js.map +1 -0
  52. package/esm/hover/mod.d.ts +3 -0
  53. package/esm/hover/mod.d.ts.map +1 -0
  54. package/esm/hover/mod.js +3 -0
  55. package/esm/hover/mod.js.map +1 -0
  56. package/esm/hover/types.d.ts +23 -0
  57. package/esm/hover/types.d.ts.map +1 -0
  58. package/esm/hover/types.js +2 -0
  59. package/esm/hover/types.js.map +1 -0
  60. package/esm/mod.d.ts +2 -0
  61. package/esm/mod.d.ts.map +1 -0
  62. package/esm/mod.js +2 -0
  63. package/esm/mod.js.map +1 -0
  64. package/esm/overlayer/mod.d.ts +13 -0
  65. package/esm/overlayer/mod.d.ts.map +1 -0
  66. package/esm/overlayer/mod.js +111 -0
  67. package/esm/overlayer/mod.js.map +1 -0
  68. package/esm/package.json +3 -0
  69. package/package.json +43 -0
  70. package/src/ExtensionUi.ts +10 -0
  71. package/src/autocomplete/AutocompletePlugin.ts +580 -0
  72. package/src/autocomplete/DefaultRenderer.ts +189 -0
  73. package/src/autocomplete/ExtensionAutocomplete.ts +49 -0
  74. package/src/autocomplete/createDefaultMatcher.ts +94 -0
  75. package/src/autocomplete/createPosMatcher.ts +21 -0
  76. package/src/autocomplete/createRegexMatcher.ts +70 -0
  77. package/src/autocomplete/mod.ts +5 -0
  78. package/src/autocomplete/types.ts +90 -0
  79. package/src/hover/ExtensionHover.ts +46 -0
  80. package/src/hover/HoverPlugin.ts +467 -0
  81. package/src/hover/MarkdownRenderer.ts +68 -0
  82. package/src/hover/mod.ts +2 -0
  83. package/src/hover/types.ts +26 -0
  84. package/src/mod.ts +1 -0
  85. package/src/overlayer/mod.ts +146 -0
@@ -0,0 +1,189 @@
1
+ import { CoreEditor } from '@kerebron/editor';
2
+ import {
3
+ AutocompleteRenderer,
4
+ SuggestionKeyDownProps,
5
+ SuggestionProps,
6
+ } from './types.js';
7
+ import { anchorElement, OverLayer } from '../overlayer/mod.js';
8
+
9
+ const CSS_PREFIX = 'kb-autocomplete';
10
+
11
+ export class DefaultRenderer<Item> extends EventTarget
12
+ implements AutocompleteRenderer {
13
+ command: (props: any) => void;
14
+ wrapper: HTMLElement | undefined;
15
+ list: HTMLElement | undefined;
16
+ items: Array<Item> = [];
17
+ pos: number = -1;
18
+ props: SuggestionProps<Item, any> | undefined;
19
+ readonly keyDownHandler: (this: HTMLElement, ev: KeyboardEvent) => any;
20
+ overlayer: OverLayer;
21
+ anchor?: string;
22
+
23
+ constructor(private editor: CoreEditor) {
24
+ super();
25
+
26
+ this.overlayer = this.editor.ci.resolve('overlayer');
27
+
28
+ this.command = () => {};
29
+
30
+ this.keyDownHandler = (event) => {
31
+ if (this.onKeyDown({ event })) {
32
+ event.stopPropagation();
33
+ event.preventDefault();
34
+ }
35
+ };
36
+ }
37
+
38
+ setCommand(command: (props: any) => void) {
39
+ this.command = command;
40
+ this.refresh();
41
+ }
42
+
43
+ setResponse() {
44
+ this.refresh();
45
+ }
46
+
47
+ onUpdate(props: SuggestionProps<Item>) {
48
+ this.items.splice(0, this.items.length, ...props.items);
49
+ this.props = props;
50
+ this.refresh();
51
+ }
52
+
53
+ destroy() {
54
+ document.body.removeEventListener('keydown', this.keyDownHandler, {
55
+ capture: true,
56
+ });
57
+ if (this.wrapper) {
58
+ this.wrapper.parentNode?.removeChild(this.wrapper);
59
+ this.wrapper = undefined;
60
+ this.dispatchEvent(new Event('close'));
61
+ }
62
+ this.pos = -1;
63
+ }
64
+
65
+ onKeyDown(props: SuggestionKeyDownProps) {
66
+ if (!this.wrapper) {
67
+ return false;
68
+ }
69
+ if (this.items.length === 0) {
70
+ return false;
71
+ }
72
+
73
+ if (props.event.key === 'Escape') {
74
+ if (this.wrapper) {
75
+ this.wrapper.parentNode?.removeChild(this.wrapper);
76
+ this.wrapper = undefined;
77
+ return true;
78
+ }
79
+ }
80
+ if (props.event.key === 'ArrowUp') {
81
+ if (this.pos > -1) {
82
+ this.pos = this.pos - 1;
83
+ this.refresh();
84
+ return true;
85
+ }
86
+ }
87
+
88
+ if (props.event.key === 'ArrowDown') {
89
+ if (this.pos < this.items.length - 1) {
90
+ this.pos++;
91
+ this.refresh();
92
+ return true;
93
+ }
94
+ }
95
+
96
+ if (props.event.key === 'Enter') {
97
+ if (this.pos > -1 && this.pos < this.items.length) {
98
+ const item = this.items[this.pos];
99
+ this.items.splice(0, this.items.length);
100
+ this.destroy();
101
+ this.command(item);
102
+ return true;
103
+ }
104
+ }
105
+ return false;
106
+ }
107
+
108
+ createListItem(item: Item, cnt: number) { // override
109
+ const li = document.createElement('li');
110
+ if (cnt === this.pos) {
111
+ li.classList.add('active');
112
+ }
113
+ li.innerText = '' + item; // TODO item to string and item formatting
114
+ li.style.cursor = 'pointer';
115
+ li.addEventListener('click', () => {
116
+ this.destroy();
117
+ this.command(item);
118
+ });
119
+ return li;
120
+ }
121
+
122
+ refresh() {
123
+ if (!this.wrapper) {
124
+ this.wrapper = this.overlayer.createElement('div');
125
+ this.wrapper.classList.add(CSS_PREFIX + '__wrapper');
126
+
127
+ this.list = document.createElement('ul');
128
+ this.wrapper.appendChild(this.list);
129
+ }
130
+
131
+ document.body.removeEventListener('keydown', this.keyDownHandler, {
132
+ capture: true,
133
+ });
134
+
135
+ if (!this.list) {
136
+ return;
137
+ }
138
+
139
+ this.list.innerHTML = '';
140
+ for (let cnt = 0; cnt < this.items.length; cnt++) {
141
+ const item = this.items[cnt];
142
+ this.list.appendChild(this.createListItem(item, cnt));
143
+ }
144
+
145
+ if (this.items.length > 0) {
146
+ document.body.addEventListener('keydown', this.keyDownHandler, {
147
+ capture: true,
148
+ });
149
+ }
150
+
151
+ let visible = false;
152
+ if (this.items.length === 0) {
153
+ // this.wrapper.style.display = 'none';
154
+ } else {
155
+ visible = true;
156
+ // this.wrapper.style.display = '';
157
+ }
158
+
159
+ if (this.anchor) {
160
+ anchorElement(this.wrapper, this.anchor, {
161
+ container: this.editor.config.element,
162
+ });
163
+ } else {
164
+ visible = false;
165
+ }
166
+
167
+ // if (visible) {
168
+ // if (!this.wrapper.matches(':popover-open')) {
169
+ // const el = document.activeElement;
170
+ // const previousFocus = el instanceof HTMLElement ? el : null;
171
+ // this.wrapper.showPopover();
172
+ // requestAnimationFrame(() => {
173
+ // if (previousFocus && previousFocus.isConnected) {
174
+ // previousFocus.focus();
175
+ // }
176
+ // });
177
+ // }
178
+ // } else {
179
+ // if (this.wrapper.matches(':popover-open')) {
180
+ // this.wrapper.hidePopover();
181
+ // }
182
+ // }
183
+ }
184
+
185
+ setAnchorSelector(anchor: string): void {
186
+ this.anchor = anchor;
187
+ this.refresh();
188
+ }
189
+ }
@@ -0,0 +1,49 @@
1
+ import type { EditorState, Plugin, Transaction } from 'prosemirror-state';
2
+
3
+ import { CommandFactories, Extension } from '@kerebron/editor';
4
+ import { CommandFactory } from '@kerebron/editor/commands';
5
+
6
+ import {
7
+ AutocompletePlugin,
8
+ AutocompletePluginKey,
9
+ } from './AutocompletePlugin.js';
10
+ import { AutocompleteConfig, AutocompleteSource } from './types.js';
11
+
12
+ export class ExtensionAutocomplete extends Extension {
13
+ name = 'autocomplete';
14
+
15
+ public constructor(
16
+ public override config: AutocompleteConfig = {},
17
+ ) {
18
+ super(config);
19
+ }
20
+
21
+ override getCommandFactories(): Partial<CommandFactories> {
22
+ const addAutocompleteSource: CommandFactory = (
23
+ autocompleteSource: AutocompleteSource,
24
+ ) => {
25
+ return (state: EditorState, dispatch?: (tr: Transaction) => void) => {
26
+ const tr = state.tr;
27
+ tr.setMeta(AutocompletePluginKey, {
28
+ addAutocompleteSource: { autocompleteSource },
29
+ });
30
+
31
+ if (dispatch) {
32
+ dispatch(tr);
33
+ }
34
+
35
+ return true;
36
+ };
37
+ };
38
+
39
+ return {
40
+ addAutocompleteSource,
41
+ };
42
+ }
43
+
44
+ override getProseMirrorPlugins(): Plugin[] {
45
+ return [
46
+ new AutocompletePlugin(this.config, this.editor),
47
+ ];
48
+ }
49
+ }
@@ -0,0 +1,94 @@
1
+ import { ResolvedPos } from 'prosemirror-model';
2
+ import { AutocompleteMatcher, SuggestionMatch } from './types.js';
3
+
4
+ // source: https://stackoverflow.com/a/6969486
5
+ export function escapeForRegEx(string: string): string {
6
+ return string.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
7
+ }
8
+
9
+ export interface MatcherConfig {
10
+ char?: string;
11
+ allowSpaces?: boolean;
12
+ allowToIncludeChar?: boolean;
13
+ allowedPrefixes?: string[] | null;
14
+ startOfLine?: boolean;
15
+ }
16
+
17
+ export function createDefaultMatcher(
18
+ config: MatcherConfig = {},
19
+ ): AutocompleteMatcher {
20
+ const char = config.char || '@';
21
+ const allowToIncludeChar = config.allowToIncludeChar || false;
22
+ const allowedPrefixes = config.allowedPrefixes || [' '];
23
+ const startOfLine = config.startOfLine || false;
24
+
25
+ return ($position: ResolvedPos): SuggestionMatch | undefined => {
26
+ const allowSpaces = config.allowSpaces && !allowToIncludeChar;
27
+
28
+ const escapedChar = escapeForRegEx(char);
29
+ const suffix = new RegExp(`\\s${escapedChar}$`);
30
+ const prefix = startOfLine ? '^' : '';
31
+ const finalEscapedChar = allowToIncludeChar ? '' : escapedChar;
32
+ const regexp = allowSpaces
33
+ ? new RegExp(
34
+ `${prefix}${escapedChar}.*?(?=\\s${finalEscapedChar}|$)`,
35
+ 'gm',
36
+ )
37
+ : new RegExp(
38
+ `${prefix}(?:^)?${escapedChar}[^\\s${finalEscapedChar}]*`,
39
+ 'gm',
40
+ );
41
+
42
+ const text = $position.nodeBefore?.isText && $position.nodeBefore.text;
43
+
44
+ if (!text) {
45
+ return undefined;
46
+ }
47
+
48
+ const textFrom = $position.pos - text.length;
49
+ const match = Array.from(text.matchAll(regexp)).pop();
50
+
51
+ if (!match || match.input === undefined || match.index === undefined) {
52
+ return undefined;
53
+ }
54
+
55
+ // JavaScript doesn't have lookbehinds. This hacks a check that first character
56
+ // is a space or the start of the line
57
+ const matchPrefix = match.input.slice(
58
+ Math.max(0, match.index - 1),
59
+ match.index,
60
+ );
61
+ const matchPrefixIsAllowed = new RegExp(
62
+ `^[${allowedPrefixes?.join('')}\0]?$`,
63
+ )
64
+ .test(matchPrefix);
65
+
66
+ if (allowedPrefixes !== null && !matchPrefixIsAllowed) {
67
+ return undefined;
68
+ }
69
+
70
+ // The absolute position of the match in the document
71
+ const from = textFrom + match.index;
72
+ let to = from + match[0].length;
73
+
74
+ // Edge case handling; if spaces are allowed and we're directly in between
75
+ // two triggers
76
+ if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
77
+ match[0] += ' ';
78
+ to += 1;
79
+ }
80
+
81
+ // If the $position is located within the matched substring, return that range
82
+ if (from < $position.pos && to >= $position.pos) {
83
+ return {
84
+ range: {
85
+ from,
86
+ to,
87
+ },
88
+ query: match[0],
89
+ };
90
+ }
91
+
92
+ return undefined;
93
+ };
94
+ }
@@ -0,0 +1,21 @@
1
+ import { ResolvedPos } from 'prosemirror-model';
2
+ import { AutocompleteMatcher, SuggestionMatch } from './types.js';
3
+
4
+ export function createPosMatcher(): AutocompleteMatcher {
5
+ return ($position: ResolvedPos): SuggestionMatch | undefined => {
6
+ const textFrom = $position.pos;
7
+
8
+ const query = '';
9
+
10
+ const from = textFrom;
11
+ const to = from + query.length;
12
+
13
+ return {
14
+ range: {
15
+ from,
16
+ to,
17
+ },
18
+ query,
19
+ };
20
+ };
21
+ }
@@ -0,0 +1,70 @@
1
+ import type { ResolvedPos } from 'prosemirror-model';
2
+ import { AutocompleteMatcher, SuggestionMatch } from './types.js';
3
+
4
+ export function ensureAnchor(expr: RegExp, start: boolean) {
5
+ let { source } = expr;
6
+ let addStart = start && source[0] != '^',
7
+ addEnd = source[source.length - 1] != '$';
8
+ if (!addStart && !addEnd) return expr;
9
+ return new RegExp(
10
+ `${addStart ? '^' : ''}(?:${source})${addEnd ? '$' : ''}`,
11
+ expr.flags ?? (expr.ignoreCase ? 'i' : ''),
12
+ );
13
+ }
14
+
15
+ function matchBefore($position: ResolvedPos, expr: RegExp) {
16
+ const text = $position.nodeBefore?.isText && $position.nodeBefore.text;
17
+
18
+ if (!text) {
19
+ return null;
20
+ }
21
+
22
+ const textFrom = $position.pos - text.length;
23
+
24
+ const start = Math.max(textFrom, $position.pos - 250);
25
+ const str = text.slice();
26
+
27
+ const found = str.search(ensureAnchor(expr, false));
28
+
29
+ return found < 0
30
+ ? null
31
+ : { from: start + found, to: $position.pos, text: str.slice(found) };
32
+ }
33
+
34
+ export function createRegexMatcher(
35
+ regexes: RegExp[],
36
+ ): AutocompleteMatcher {
37
+ return ($position: ResolvedPos): SuggestionMatch | undefined => {
38
+ const text = $position.nodeBefore?.isText && $position.nodeBefore.text;
39
+
40
+ if (!text) {
41
+ return undefined;
42
+ }
43
+ const textFrom = $position.pos - text.length;
44
+
45
+ const matches = regexes.map((regex) => matchBefore($position, regex))
46
+ .filter((m) => !!m);
47
+
48
+ if (matches.length === 0) {
49
+ return undefined;
50
+ }
51
+
52
+ matches.sort((a, b) => b.text.length - a.text.length);
53
+
54
+ let from = matches[0].from;
55
+ let matchedText = matches[0].text;
56
+ let to = matches[0].to;
57
+ while (matchedText.match(/^\s/)) {
58
+ matchedText = matchedText.substring(1);
59
+ from++;
60
+ }
61
+
62
+ return {
63
+ range: {
64
+ from,
65
+ to,
66
+ },
67
+ query: matchedText,
68
+ };
69
+ };
70
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './ExtensionAutocomplete.js';
3
+ export * from './createRegexMatcher.js';
4
+ export * from './createDefaultMatcher.js';
5
+ export * from './createPosMatcher.js';
@@ -0,0 +1,90 @@
1
+ import type { ResolvedPos } from 'prosemirror-model';
2
+ import { EditorState } from 'prosemirror-state';
3
+
4
+ import type { TextRange } from '@kerebron/editor';
5
+
6
+ export interface AutocompleteConfig {
7
+ decorationTag?: string;
8
+ decorationClass?: string;
9
+ }
10
+
11
+ export interface AutocompleteProps {
12
+ range: TextRange;
13
+ isActive?: boolean;
14
+ }
15
+
16
+ export type AutocompleteMatcher = (
17
+ pos: ResolvedPos,
18
+ ) => SuggestionMatch | undefined;
19
+
20
+ export interface AutocompleteSource<I = any, TSelected = any> {
21
+ getItems: (query: string, props: AutocompleteProps) => I[] | Promise<I[]>;
22
+
23
+ onSelect: (selected: TSelected, range: TextRange) => void;
24
+ allow?: (
25
+ props: AutocompleteProps,
26
+ ) => boolean;
27
+
28
+ matchers?: AutocompleteMatcher[];
29
+ renderer?: AutocompleteRenderer<I, TSelected>;
30
+ triggerKeys?: string[];
31
+ }
32
+
33
+ export interface SuggestionKeyDownProps {
34
+ event: KeyboardEvent;
35
+ }
36
+
37
+ export type SuggestionMatch = {
38
+ range: TextRange;
39
+ query: string;
40
+ };
41
+
42
+ export interface SuggestionProps<I = any, TSelected = any> {
43
+ match: SuggestionMatch;
44
+
45
+ /**
46
+ * The suggestion items array.
47
+ */
48
+ items: I[];
49
+
50
+ // /**
51
+ // * The decoration node HTML element
52
+ // * @default null
53
+ // */
54
+ // decorationNode: Element | null;
55
+
56
+ // anchor: HTMLElement;
57
+
58
+ /**
59
+ * The function that returns the client rect
60
+ * @default null
61
+ * @example () => new DOMRect(0, 0, 0, 0)
62
+ */
63
+ // clientRect?: (() => DOMRect | null) | null;
64
+ }
65
+
66
+ export interface AutocompleteRenderer<I = any, TSelected = any> {
67
+ setAnchorSelector(anchor: string): void;
68
+ setCommand: (command: (props: TSelected) => void) => void;
69
+ setResponse: () => void;
70
+ onUpdate: (props: SuggestionProps<I, TSelected>) => void;
71
+ onKeyDown?: (props: SuggestionKeyDownProps) => boolean;
72
+ destroy: () => void;
73
+ refresh: () => void;
74
+
75
+ addEventListener(
76
+ type: string,
77
+ listener: EventListenerOrEventListenerObject,
78
+ options?: boolean | AddEventListenerOptions,
79
+ ): void;
80
+ removeEventListener(
81
+ type: string,
82
+ callback: EventListenerOrEventListenerObject | null,
83
+ options?: EventListenerOptions | boolean,
84
+ ): void;
85
+ }
86
+
87
+ export type MatchedSource = {
88
+ match: SuggestionMatch;
89
+ source: AutocompleteSource;
90
+ };
@@ -0,0 +1,46 @@
1
+ import type { EditorState, Plugin, Transaction } from 'prosemirror-state';
2
+
3
+ import { CommandFactories, Extension } from '@kerebron/editor';
4
+ import { CommandFactory } from '@kerebron/editor/commands';
5
+
6
+ import { HoverPlugin, HoverPluginKey } from './HoverPlugin.js';
7
+ import { HoverConfig, HoverSource } from './types.js';
8
+
9
+ export class ExtensionHover extends Extension {
10
+ name = 'hover';
11
+
12
+ public constructor(
13
+ public override config: HoverConfig = {},
14
+ ) {
15
+ super(config);
16
+ }
17
+
18
+ override getCommandFactories(): Partial<CommandFactories> {
19
+ const addHoverSource: CommandFactory = (
20
+ hoverSource: HoverSource,
21
+ ) => {
22
+ return (state: EditorState, dispatch?: (tr: Transaction) => void) => {
23
+ const tr = state.tr;
24
+ tr.setMeta(HoverPluginKey, {
25
+ addHoverSource: { hoverSource },
26
+ });
27
+
28
+ if (dispatch) {
29
+ dispatch(tr);
30
+ }
31
+
32
+ return true;
33
+ };
34
+ };
35
+
36
+ return {
37
+ addHoverSource,
38
+ };
39
+ }
40
+
41
+ override getProseMirrorPlugins(): Plugin[] {
42
+ return [
43
+ new HoverPlugin(this.config, this.editor),
44
+ ];
45
+ }
46
+ }