@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.
- package/LICENSE +23 -0
- package/README.md +40 -0
- package/assets/ui.css +32 -0
- package/esm/ExtensionUi.d.ts +6 -0
- package/esm/ExtensionUi.d.ts.map +1 -0
- package/esm/ExtensionUi.js +9 -0
- package/esm/ExtensionUi.js.map +1 -0
- package/esm/autocomplete/AutocompletePlugin.d.ts +53 -0
- package/esm/autocomplete/AutocompletePlugin.d.ts.map +1 -0
- package/esm/autocomplete/AutocompletePlugin.js +417 -0
- package/esm/autocomplete/AutocompletePlugin.js.map +1 -0
- package/esm/autocomplete/DefaultRenderer.d.ts +25 -0
- package/esm/autocomplete/DefaultRenderer.d.ts.map +1 -0
- package/esm/autocomplete/DefaultRenderer.js +162 -0
- package/esm/autocomplete/DefaultRenderer.js.map +1 -0
- package/esm/autocomplete/ExtensionAutocomplete.d.ts +11 -0
- package/esm/autocomplete/ExtensionAutocomplete.d.ts.map +1 -0
- package/esm/autocomplete/ExtensionAutocomplete.js +33 -0
- package/esm/autocomplete/ExtensionAutocomplete.js.map +1 -0
- package/esm/autocomplete/createDefaultMatcher.d.ts +11 -0
- package/esm/autocomplete/createDefaultMatcher.d.ts.map +1 -0
- package/esm/autocomplete/createDefaultMatcher.js +58 -0
- package/esm/autocomplete/createDefaultMatcher.js.map +1 -0
- package/esm/autocomplete/createPosMatcher.d.ts +3 -0
- package/esm/autocomplete/createPosMatcher.d.ts.map +1 -0
- package/esm/autocomplete/createPosMatcher.js +16 -0
- package/esm/autocomplete/createPosMatcher.js.map +1 -0
- package/esm/autocomplete/createRegexMatcher.d.ts +4 -0
- package/esm/autocomplete/createRegexMatcher.d.ts.map +1 -0
- package/esm/autocomplete/createRegexMatcher.js +50 -0
- package/esm/autocomplete/createRegexMatcher.js.map +1 -0
- package/esm/autocomplete/mod.d.ts +6 -0
- package/esm/autocomplete/mod.d.ts.map +1 -0
- package/esm/autocomplete/mod.js +6 -0
- package/esm/autocomplete/mod.js.map +1 -0
- package/esm/autocomplete/types.d.ts +49 -0
- package/esm/autocomplete/types.d.ts.map +1 -0
- package/esm/autocomplete/types.js +2 -0
- package/esm/autocomplete/types.js.map +1 -0
- package/esm/hover/ExtensionHover.d.ts +11 -0
- package/esm/hover/ExtensionHover.d.ts.map +1 -0
- package/esm/hover/ExtensionHover.js +33 -0
- package/esm/hover/ExtensionHover.js.map +1 -0
- package/esm/hover/HoverPlugin.d.ts +49 -0
- package/esm/hover/HoverPlugin.d.ts.map +1 -0
- package/esm/hover/HoverPlugin.js +368 -0
- package/esm/hover/HoverPlugin.js.map +1 -0
- package/esm/hover/MarkdownRenderer.d.ts +23 -0
- package/esm/hover/MarkdownRenderer.d.ts.map +1 -0
- package/esm/hover/MarkdownRenderer.js +54 -0
- package/esm/hover/MarkdownRenderer.js.map +1 -0
- package/esm/hover/mod.d.ts +3 -0
- package/esm/hover/mod.d.ts.map +1 -0
- package/esm/hover/mod.js +3 -0
- package/esm/hover/mod.js.map +1 -0
- package/esm/hover/types.d.ts +23 -0
- package/esm/hover/types.d.ts.map +1 -0
- package/esm/hover/types.js +2 -0
- package/esm/hover/types.js.map +1 -0
- package/esm/mod.d.ts +2 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +2 -0
- package/esm/mod.js.map +1 -0
- package/esm/overlayer/mod.d.ts +13 -0
- package/esm/overlayer/mod.d.ts.map +1 -0
- package/esm/overlayer/mod.js +111 -0
- package/esm/overlayer/mod.js.map +1 -0
- package/esm/package.json +3 -0
- package/package.json +43 -0
- package/src/ExtensionUi.ts +10 -0
- package/src/autocomplete/AutocompletePlugin.ts +580 -0
- package/src/autocomplete/DefaultRenderer.ts +189 -0
- package/src/autocomplete/ExtensionAutocomplete.ts +49 -0
- package/src/autocomplete/createDefaultMatcher.ts +94 -0
- package/src/autocomplete/createPosMatcher.ts +21 -0
- package/src/autocomplete/createRegexMatcher.ts +70 -0
- package/src/autocomplete/mod.ts +5 -0
- package/src/autocomplete/types.ts +90 -0
- package/src/hover/ExtensionHover.ts +46 -0
- package/src/hover/HoverPlugin.ts +467 -0
- package/src/hover/MarkdownRenderer.ts +68 -0
- package/src/hover/mod.ts +2 -0
- package/src/hover/types.ts +26 -0
- package/src/mod.ts +1 -0
- package/src/overlayer/mod.ts +146 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
import { Plugin, PluginKey, Selection, Transaction } from 'prosemirror-state';
|
|
2
|
+
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
3
|
+
|
|
4
|
+
import { type CoreEditor } from '@kerebron/editor';
|
|
5
|
+
|
|
6
|
+
import { debounce } from '@kerebron/editor/utilities';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
AutocompleteConfig,
|
|
10
|
+
AutocompleteMatcher,
|
|
11
|
+
AutocompleteProps,
|
|
12
|
+
AutocompleteRenderer,
|
|
13
|
+
AutocompleteSource,
|
|
14
|
+
MatchedSource,
|
|
15
|
+
SuggestionMatch,
|
|
16
|
+
SuggestionProps,
|
|
17
|
+
} from './types.js';
|
|
18
|
+
import { DefaultRenderer } from './DefaultRenderer.js';
|
|
19
|
+
import { createDefaultMatcher } from './createDefaultMatcher.js';
|
|
20
|
+
|
|
21
|
+
// type AutoCompleteStatexx =
|
|
22
|
+
// | { type: "Idle" }
|
|
23
|
+
// | { type: "Matched"; match: SuggestionMatch; source: AutocompleteSource }
|
|
24
|
+
// | { type: "Suggesting"; text: string };
|
|
25
|
+
|
|
26
|
+
interface AutocompleteMeta {
|
|
27
|
+
addAutocompleteSource?: {
|
|
28
|
+
autocompleteSource: AutocompleteSource;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
trigger?: {
|
|
32
|
+
manual: boolean;
|
|
33
|
+
};
|
|
34
|
+
setRequest?: {
|
|
35
|
+
match: SuggestionMatch;
|
|
36
|
+
source: AutocompleteSource;
|
|
37
|
+
// renderer: AutocompleteRenderer;
|
|
38
|
+
};
|
|
39
|
+
setResponse?: {
|
|
40
|
+
items: any[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
clearRequest?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class AutoCompleteState {
|
|
47
|
+
idGenerator = 1;
|
|
48
|
+
|
|
49
|
+
autocompleteSources: AutocompleteSource[] = [];
|
|
50
|
+
manual: boolean = false;
|
|
51
|
+
|
|
52
|
+
request?: {
|
|
53
|
+
id: number;
|
|
54
|
+
|
|
55
|
+
decorationId: string;
|
|
56
|
+
|
|
57
|
+
match: SuggestionMatch;
|
|
58
|
+
source: AutocompleteSource;
|
|
59
|
+
renderer: AutocompleteRenderer;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
response?: {
|
|
63
|
+
items: any[];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
composing: boolean = false;
|
|
67
|
+
|
|
68
|
+
matchSource: typeof this.matchSourceImpl;
|
|
69
|
+
|
|
70
|
+
constructor(private editor: CoreEditor) {
|
|
71
|
+
this.matchSource = debounce(this.matchSourceImpl.bind(this), 200);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
destroy() {
|
|
75
|
+
if (this.request) {
|
|
76
|
+
this.request.renderer.destroy();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
include(from: number) {
|
|
81
|
+
if (!this.request) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (from < this.request.match.range.from) return false;
|
|
86
|
+
if (from > this.request.match.range.to) return false;
|
|
87
|
+
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
addSource(source: AutocompleteSource) {
|
|
92
|
+
if (!source.matchers && source.triggerKeys?.length === 1) {
|
|
93
|
+
source.matchers = [
|
|
94
|
+
createDefaultMatcher({ char: source.triggerKeys[0] }),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
this.autocompleteSources.push(
|
|
98
|
+
source,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
clearRequest() {
|
|
103
|
+
if (this.request) {
|
|
104
|
+
this.request.renderer.destroy();
|
|
105
|
+
this.request = undefined;
|
|
106
|
+
}
|
|
107
|
+
this.response = undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
handleSource(matched?: MatchedSource) {
|
|
111
|
+
// If we found a match, update the current state to show it
|
|
112
|
+
if (
|
|
113
|
+
matched && (!matched.source.allow || matched.source.allow({
|
|
114
|
+
range: matched.match.range,
|
|
115
|
+
isActive: !!this.request,
|
|
116
|
+
}))
|
|
117
|
+
) {
|
|
118
|
+
const { source, match } = matched;
|
|
119
|
+
|
|
120
|
+
if (match) {
|
|
121
|
+
this.dispatchMeta({
|
|
122
|
+
setRequest: {
|
|
123
|
+
match,
|
|
124
|
+
source,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
this.clearRequest();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
docChanged() {
|
|
134
|
+
this.matchSource({ manual: false });
|
|
135
|
+
// for (const source of sources) {
|
|
136
|
+
// if (!source.matchers) {
|
|
137
|
+
// continue;
|
|
138
|
+
// }
|
|
139
|
+
// const matchers: AutocompleteMatcher[] = source.matchers;
|
|
140
|
+
|
|
141
|
+
// if (
|
|
142
|
+
// !['code_blocxk'].includes(parentNode.type.name)
|
|
143
|
+
// ) {
|
|
144
|
+
// for (const matcher of matchers) {
|
|
145
|
+
// const match = matcher(selection.$from);
|
|
146
|
+
// if (match) {
|
|
147
|
+
// matched = {
|
|
148
|
+
// source,
|
|
149
|
+
// match,
|
|
150
|
+
// };
|
|
151
|
+
|
|
152
|
+
// this.handleSource(matched);
|
|
153
|
+
// break;
|
|
154
|
+
// // return matched;
|
|
155
|
+
// }
|
|
156
|
+
|
|
157
|
+
// }
|
|
158
|
+
|
|
159
|
+
// }
|
|
160
|
+
// }
|
|
161
|
+
// throw new Error('Method not implemented.');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private matchSourceImpl({ manual }: { manual: boolean }): void {
|
|
165
|
+
this.manual = manual;
|
|
166
|
+
|
|
167
|
+
const selection: Selection = this.editor.state.selection;
|
|
168
|
+
|
|
169
|
+
const sources: AutocompleteSource[] = this.autocompleteSources;
|
|
170
|
+
let matched: MatchedSource | undefined = undefined;
|
|
171
|
+
const parentNode = selection.$anchor.parent;
|
|
172
|
+
|
|
173
|
+
for (const source of sources) {
|
|
174
|
+
if (!source.matchers) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const matchers: AutocompleteMatcher[] = source.matchers;
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
!['code_blocxk'].includes(parentNode.type.name)
|
|
181
|
+
) {
|
|
182
|
+
for (const matcher of matchers) {
|
|
183
|
+
const match = matcher(selection.$from);
|
|
184
|
+
console.log('matchSourceImpl', source, match);
|
|
185
|
+
if (match) {
|
|
186
|
+
matched = {
|
|
187
|
+
source,
|
|
188
|
+
match,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
this.handleSource(matched);
|
|
192
|
+
break;
|
|
193
|
+
// return matched;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!matched) {
|
|
200
|
+
this.dispatchMeta({ clearRequest: true });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
handleCommands(
|
|
207
|
+
pluginMeta: AutocompleteMeta | undefined,
|
|
208
|
+
transaction: Transaction,
|
|
209
|
+
) {
|
|
210
|
+
if (!pluginMeta) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (pluginMeta?.addAutocompleteSource) {
|
|
215
|
+
this.addSource({
|
|
216
|
+
...pluginMeta.addAutocompleteSource.autocompleteSource,
|
|
217
|
+
});
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (pluginMeta?.trigger) {
|
|
222
|
+
const { selection } = transaction;
|
|
223
|
+
|
|
224
|
+
this.matchSource(pluginMeta?.trigger);
|
|
225
|
+
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (pluginMeta?.setRequest) {
|
|
230
|
+
const id = this.idGenerator++;
|
|
231
|
+
const decorationId = `id_${Math.floor(Math.random() * 0xffffffff)}`;
|
|
232
|
+
|
|
233
|
+
let renderer = this.request?.renderer;
|
|
234
|
+
if (this.request?.source !== pluginMeta.setRequest.source) {
|
|
235
|
+
if (renderer) {
|
|
236
|
+
renderer.destroy();
|
|
237
|
+
renderer = undefined;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (!renderer) {
|
|
241
|
+
renderer = new DefaultRenderer(this.editor);
|
|
242
|
+
renderer.setAnchorSelector(`[data-decoration-id="${decorationId}"]`);
|
|
243
|
+
|
|
244
|
+
if (this.request) {
|
|
245
|
+
const onSelect = this.request.source.onSelect;
|
|
246
|
+
renderer.setCommand((selected) => {
|
|
247
|
+
console.log('COM', selected);
|
|
248
|
+
if (!this.request) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
return onSelect(selected, this.request.match.range);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
renderer.addEventListener('close', () => {
|
|
255
|
+
this.dispatchMeta({
|
|
256
|
+
clearRequest: true,
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.request = {
|
|
262
|
+
id,
|
|
263
|
+
decorationId,
|
|
264
|
+
match: pluginMeta.setRequest.match,
|
|
265
|
+
source: pluginMeta.setRequest.source,
|
|
266
|
+
renderer,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const request = this.request;
|
|
270
|
+
|
|
271
|
+
const go = async () => {
|
|
272
|
+
const ctx = {
|
|
273
|
+
range: request.match.range,
|
|
274
|
+
isActive: !!request,
|
|
275
|
+
};
|
|
276
|
+
const items = await request.source.getItems(
|
|
277
|
+
request.match.query,
|
|
278
|
+
ctx,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
this.dispatchMeta({
|
|
282
|
+
setResponse: {
|
|
283
|
+
items,
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
go();
|
|
288
|
+
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (pluginMeta?.setResponse && this.request) {
|
|
293
|
+
this.response = {
|
|
294
|
+
...pluginMeta?.setResponse,
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const props: SuggestionProps = {
|
|
298
|
+
match: this.request.match,
|
|
299
|
+
items: this.response.items,
|
|
300
|
+
// anchor: ,
|
|
301
|
+
// decorationNode,
|
|
302
|
+
// virtual node for popper.js or tippy.js
|
|
303
|
+
// this can be used for building popups without a DOM node
|
|
304
|
+
// clientRect: () => {
|
|
305
|
+
// // because of `items` can be asynchrounous we’ll search for the current decoration node
|
|
306
|
+
// const { decorationId } = next; // eslint-disable-line
|
|
307
|
+
// const currentDecorationNode = view.dom.querySelector(
|
|
308
|
+
// `[data-decoration-id="${decorationId}"]`,
|
|
309
|
+
// );
|
|
310
|
+
|
|
311
|
+
// return currentDecorationNode?.getBoundingClientRect() ||
|
|
312
|
+
// null;
|
|
313
|
+
// },
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
this.request?.renderer.onUpdate(props);
|
|
317
|
+
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (pluginMeta?.clearRequest) {
|
|
322
|
+
this.clearRequest();
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
dispatchMeta(meta: AutocompleteMeta) {
|
|
330
|
+
const tr = this.editor.state.tr;
|
|
331
|
+
tr.setMeta(AutocompletePluginKey, meta);
|
|
332
|
+
this.editor.dispatchTransaction(tr);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export const AutocompletePluginKey = new PluginKey<AutoCompleteState>(
|
|
337
|
+
'autocomplete',
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
export class AutocompletePlugin<Item, TSelected>
|
|
341
|
+
extends Plugin<AutoCompleteState> {
|
|
342
|
+
constructor(config: AutocompleteConfig, editor: CoreEditor) {
|
|
343
|
+
super({
|
|
344
|
+
key: AutocompletePluginKey,
|
|
345
|
+
state: {
|
|
346
|
+
init() {
|
|
347
|
+
return new AutoCompleteState(editor);
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
apply(transaction, value: AutoCompleteState, prevState, state) {
|
|
351
|
+
const pluginMeta: AutocompleteMeta | undefined = transaction.getMeta(
|
|
352
|
+
AutocompletePluginKey,
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const nextAutocompleteState = value;
|
|
356
|
+
|
|
357
|
+
if (!pluginMeta && !transaction.isGeneric) {
|
|
358
|
+
// return next;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const { editable, composing } = editor.view;
|
|
362
|
+
const { selection } = transaction;
|
|
363
|
+
|
|
364
|
+
nextAutocompleteState.composing = composing;
|
|
365
|
+
|
|
366
|
+
if (!editable) {
|
|
367
|
+
nextAutocompleteState.clearRequest();
|
|
368
|
+
return nextAutocompleteState;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (!selection.empty && !editor.view.composing) {
|
|
372
|
+
nextAutocompleteState.clearRequest();
|
|
373
|
+
return nextAutocompleteState;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (nextAutocompleteState.handleCommands(pluginMeta, transaction)) {
|
|
377
|
+
return nextAutocompleteState;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (transaction.docChanged) {
|
|
381
|
+
nextAutocompleteState.docChanged();
|
|
382
|
+
// nextAutocompleteState.clearActive();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Reset active state if we just left the previous suggestion range
|
|
386
|
+
if (!composing && !nextAutocompleteState.include(selection.from)) {
|
|
387
|
+
nextAutocompleteState.clearRequest();
|
|
388
|
+
return nextAutocompleteState;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (transaction.getMeta('isCommand')) {
|
|
392
|
+
return nextAutocompleteState;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
nextAutocompleteState.matchSource({ manual: false });
|
|
396
|
+
|
|
397
|
+
return nextAutocompleteState;
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
view(editorView) {
|
|
402
|
+
return {
|
|
403
|
+
update: async (view, prevState) => {
|
|
404
|
+
const prev: AutoCompleteState = this.key?.getState(prevState);
|
|
405
|
+
const next: AutoCompleteState = this.key?.getState(view.state);
|
|
406
|
+
const pluginState = next;
|
|
407
|
+
|
|
408
|
+
if (next.request?.decorationId) {
|
|
409
|
+
const decorationNode = view.dom.querySelector(
|
|
410
|
+
`[data-decoration-id="${next.request.decorationId}"]`,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const moved = prev.request && next.request &&
|
|
415
|
+
prev.request.match.range.from !== next.request.match.range.from;
|
|
416
|
+
const started = !prev.request?.match && next.request?.match;
|
|
417
|
+
const stopped = prev.request?.match && !next.request?.match;
|
|
418
|
+
const changed = !started && !stopped &&
|
|
419
|
+
prev.request?.match.query !== next.request?.match.query;
|
|
420
|
+
|
|
421
|
+
if (stopped && prev.request) {
|
|
422
|
+
prev.request.renderer.destroy();
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (next.activexxx) {
|
|
427
|
+
for (const source of next.autocompleteSources) {
|
|
428
|
+
if (!source.triggerKeys) {
|
|
429
|
+
pluginState.dispatchMeta({
|
|
430
|
+
trigger: {
|
|
431
|
+
manual: false,
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const changedQuery =
|
|
439
|
+
prev.request?.match.query !== next.request.match.query;
|
|
440
|
+
|
|
441
|
+
if (changedQuery || moved) {
|
|
442
|
+
const decorationNode = view.dom.querySelector(
|
|
443
|
+
`[data-decoration-id="${next.request.decorationId}"]`,
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
const props: SuggestionProps = {
|
|
447
|
+
match: next.request.match,
|
|
448
|
+
items: [],
|
|
449
|
+
// anchor: ,
|
|
450
|
+
// decorationNode,
|
|
451
|
+
// virtual node for popper.js or tippy.js
|
|
452
|
+
// this can be used for building popups without a DOM node
|
|
453
|
+
// clientRect: () => {
|
|
454
|
+
// // because of `items` can be asynchrounous we’ll search for the current decoration node
|
|
455
|
+
// const { decorationId } = next; // eslint-disable-line
|
|
456
|
+
// const currentDecorationNode = view.dom.querySelector(
|
|
457
|
+
// `[data-decoration-id="${decorationId}"]`,
|
|
458
|
+
// );
|
|
459
|
+
|
|
460
|
+
// return currentDecorationNode?.getBoundingClientRect() ||
|
|
461
|
+
// null;
|
|
462
|
+
// },
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
next.request.renderer.onUpdate(props);
|
|
466
|
+
|
|
467
|
+
try {
|
|
468
|
+
const ctx = {
|
|
469
|
+
range: next.request.match.range,
|
|
470
|
+
isActive: !!next.request,
|
|
471
|
+
};
|
|
472
|
+
const items = await next.request.source.getItems(
|
|
473
|
+
next.request.match.query,
|
|
474
|
+
ctx,
|
|
475
|
+
);
|
|
476
|
+
next.request.renderer.onUpdate({ ...props, items });
|
|
477
|
+
} catch (err: any) {
|
|
478
|
+
if (err.isLSP) {
|
|
479
|
+
console.error(
|
|
480
|
+
'LSP error config.getItems()',
|
|
481
|
+
err.message,
|
|
482
|
+
next.request.source.getItems,
|
|
483
|
+
);
|
|
484
|
+
} else {
|
|
485
|
+
throw err;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const handleStart = started || (moved && changed);
|
|
492
|
+
const handleChange = changed || moved;
|
|
493
|
+
const handleExit = stopped || (moved && changed);
|
|
494
|
+
|
|
495
|
+
if (!handleStart && !handleChange && !handleExit) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const state = handleExit && !handleStart ? prev : next;
|
|
500
|
+
|
|
501
|
+
// const active = state.active;
|
|
502
|
+
},
|
|
503
|
+
|
|
504
|
+
destroy: () => {
|
|
505
|
+
const pluginState = AutocompletePluginKey.getState(editor.state);
|
|
506
|
+
if (!pluginState) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
pluginState.destroy();
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
},
|
|
514
|
+
|
|
515
|
+
props: {
|
|
516
|
+
// Call the keydown hook if suggestion is active.
|
|
517
|
+
handleKeyDown(view, event) {
|
|
518
|
+
const pluginState = this.getState(view.state) as AutoCompleteState;
|
|
519
|
+
const { autocompleteSources } = pluginState;
|
|
520
|
+
|
|
521
|
+
for (const source of autocompleteSources) {
|
|
522
|
+
if (source.triggerKeys) {
|
|
523
|
+
const triggerKeys: string[] = source.triggerKeys;
|
|
524
|
+
for (const origKey of triggerKeys) {
|
|
525
|
+
let key = origKey.toLowerCase();
|
|
526
|
+
if (key.startsWith('ctrl+')) {
|
|
527
|
+
if (!event.ctrlKey) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
key = key.substring('ctrl+'.length);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (key === event.key) {
|
|
534
|
+
pluginState.dispatchMeta({
|
|
535
|
+
trigger: {
|
|
536
|
+
manual: true,
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return false;
|
|
546
|
+
},
|
|
547
|
+
|
|
548
|
+
// Setup decorator on the currently active suggestion.
|
|
549
|
+
decorations(state) {
|
|
550
|
+
const { request, response } = this.getState(state) || {};
|
|
551
|
+
|
|
552
|
+
if (!request) {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const anchor = document.createElement('span');
|
|
557
|
+
anchor.className = config.decorationClass || 'kb-autocomplete--decor';
|
|
558
|
+
anchor.setAttribute('data-decoration-id', request.decorationId);
|
|
559
|
+
|
|
560
|
+
return DecorationSet.create(state.doc, [
|
|
561
|
+
Decoration.inline(
|
|
562
|
+
request.match.range.from,
|
|
563
|
+
request.match.range.to,
|
|
564
|
+
{
|
|
565
|
+
class: config.decorationClass || 'kb-autocomplete--decor',
|
|
566
|
+
'data-decoration-id': request.decorationId,
|
|
567
|
+
decorationId: request.decorationId,
|
|
568
|
+
},
|
|
569
|
+
),
|
|
570
|
+
// Decoration.widget(active.match.range.from, anchor, {
|
|
571
|
+
// class: anchor.className,
|
|
572
|
+
// decorationId,
|
|
573
|
+
// // refresh: () => active.renderer.refresh(),
|
|
574
|
+
// }),
|
|
575
|
+
]);
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|