@kerebron/extension-autocomplete 0.4.26 → 0.4.27
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/package.json +3 -2
- package/assets/autocomplete.css +0 -12
- package/esm/AutocompletePlugin.d.ts +0 -8
- package/esm/AutocompletePlugin.d.ts.map +0 -1
- package/esm/AutocompletePlugin.js +0 -232
- package/esm/DefaultRenderer.d.ts +0 -17
- package/esm/DefaultRenderer.d.ts.map +0 -1
- package/esm/DefaultRenderer.js +0 -113
- package/esm/ExtensionAutocomplete.d.ts +0 -26
- package/esm/ExtensionAutocomplete.d.ts.map +0 -1
- package/esm/ExtensionAutocomplete.js +0 -16
- package/esm/createDefaultMatcher.d.ts +0 -11
- package/esm/createDefaultMatcher.d.ts.map +0 -1
- package/esm/createDefaultMatcher.js +0 -58
- package/esm/createRegexMatcher.d.ts +0 -4
- package/esm/createRegexMatcher.d.ts.map +0 -1
- package/esm/createRegexMatcher.js +0 -50
- package/esm/mod.d.ts +0 -3
- package/esm/mod.d.ts.map +0 -1
- package/esm/mod.js +0 -2
- package/esm/package.json +0 -3
- package/esm/types.d.ts +0 -60
- package/esm/types.d.ts.map +0 -1
- package/esm/types.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kerebron/extension-autocomplete",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.27",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"module": "./esm/mod.js",
|
|
6
6
|
"exports": {
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"scripts": {},
|
|
21
|
+
"files": [],
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@kerebron/editor": "0.4.
|
|
23
|
+
"@kerebron/editor": "0.4.27",
|
|
23
24
|
"prosemirror-model": "1.25.3",
|
|
24
25
|
"prosemirror-state": "1.4.3",
|
|
25
26
|
"prosemirror-view": "1.40.0"
|
package/assets/autocomplete.css
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
.kb-autocomplete__wrapper {
|
|
2
|
-
position: fixed;
|
|
3
|
-
z-index: 99999;
|
|
4
|
-
padding: 6px var(--kb-space-sm);
|
|
5
|
-
background: var(--kb-color-surface-elevated);
|
|
6
|
-
list-style: none;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.kb-autocomplete__wrapper li.active {
|
|
10
|
-
background: rgba(232, 234, 237, 0.08);
|
|
11
|
-
color: #fff;
|
|
12
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Plugin, PluginKey } from 'prosemirror-state';
|
|
2
|
-
import { type CoreEditor } from '@kerebron/editor';
|
|
3
|
-
import type { AutocompleteConfig } from './ExtensionAutocomplete.js';
|
|
4
|
-
export declare const AutocompletePluginKey: PluginKey<any>;
|
|
5
|
-
export declare class AutocompletePlugin<Item, TSelected> extends Plugin {
|
|
6
|
-
constructor(config: AutocompleteConfig, editor: CoreEditor);
|
|
7
|
-
}
|
|
8
|
-
//# sourceMappingURL=AutocompletePlugin.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompletePlugin.d.ts","sourceRoot":"","sources":["../src/AutocompletePlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAAE,KAAK,UAAU,EAAkB,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AASrE,eAAO,MAAM,qBAAqB,gBAAgC,CAAC;AAEnE,qBAAa,kBAAkB,CAAC,IAAI,EAAE,SAAS,CAAE,SAAQ,MAAM;gBACjD,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,UAAU;CA2R3D"}
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import { Plugin, PluginKey } from 'prosemirror-state';
|
|
2
|
-
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
3
|
-
import { createDefaultMatcher } from './createDefaultMatcher.js';
|
|
4
|
-
import { DefaultRenderer } from './DefaultRenderer.js';
|
|
5
|
-
export const AutocompletePluginKey = new PluginKey('autocomplete');
|
|
6
|
-
export class AutocompletePlugin extends Plugin {
|
|
7
|
-
constructor(config, editor) {
|
|
8
|
-
let props;
|
|
9
|
-
const renderer = config.renderer ||
|
|
10
|
-
new DefaultRenderer(editor);
|
|
11
|
-
super({
|
|
12
|
-
key: AutocompletePluginKey,
|
|
13
|
-
view() {
|
|
14
|
-
return {
|
|
15
|
-
update: async (view, prevState) => {
|
|
16
|
-
const prev = this.key?.getState(prevState);
|
|
17
|
-
const next = this.key?.getState(view.state);
|
|
18
|
-
const moved = prev.active && next.active &&
|
|
19
|
-
prev.range.from !== next.range.from;
|
|
20
|
-
const started = !prev.active && next.active;
|
|
21
|
-
const stopped = prev.active && !next.active;
|
|
22
|
-
const changed = !started && !stopped && prev.query !== next.query;
|
|
23
|
-
const handleStart = started || (moved && changed);
|
|
24
|
-
const handleChange = changed || moved;
|
|
25
|
-
let handleExit = stopped || (moved && changed);
|
|
26
|
-
if (!handleStart && !handleChange && !handleExit) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const state = handleExit && !handleStart ? prev : next;
|
|
30
|
-
// await new Promise(r => setTimeout(r, 100));
|
|
31
|
-
const decorationNode = view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
32
|
-
props = {
|
|
33
|
-
range: state.range,
|
|
34
|
-
query: state.query,
|
|
35
|
-
text: state.text,
|
|
36
|
-
items: [],
|
|
37
|
-
command: (selected) => {
|
|
38
|
-
if (!config.onSelect) {
|
|
39
|
-
return () => { };
|
|
40
|
-
}
|
|
41
|
-
return config.onSelect(selected, state.range);
|
|
42
|
-
},
|
|
43
|
-
decorationNode,
|
|
44
|
-
// virtual node for popper.js or tippy.js
|
|
45
|
-
// this can be used for building popups without a DOM node
|
|
46
|
-
clientRect: decorationNode
|
|
47
|
-
? () => {
|
|
48
|
-
// because of `items` can be asynchrounous we’ll search for the current decoration node
|
|
49
|
-
const { decorationId } = this.key?.getState(editor.state); // eslint-disable-line
|
|
50
|
-
const currentDecorationNode = view.dom.querySelector(`[data-decoration-id="${decorationId}"]`);
|
|
51
|
-
return currentDecorationNode?.getBoundingClientRect() || null;
|
|
52
|
-
}
|
|
53
|
-
: null,
|
|
54
|
-
};
|
|
55
|
-
if (handleStart) {
|
|
56
|
-
renderer?.onBeforeStart?.(props);
|
|
57
|
-
}
|
|
58
|
-
if (handleChange) {
|
|
59
|
-
renderer?.onBeforeUpdate?.(props);
|
|
60
|
-
}
|
|
61
|
-
if (handleChange || handleStart) {
|
|
62
|
-
if (config.getItems) {
|
|
63
|
-
try {
|
|
64
|
-
const ctx = { state, range: state.range, isActive: true };
|
|
65
|
-
props.items = await config.getItems(state.query, ctx);
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
if (err.isLSP) {
|
|
69
|
-
props.items = [];
|
|
70
|
-
console.error('LSP error config.getItems()', err.message, config.getItems);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
throw err;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (props.items.length === 0) {
|
|
77
|
-
handleExit = true;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (handleExit) {
|
|
82
|
-
renderer?.onExit?.(props);
|
|
83
|
-
}
|
|
84
|
-
if (handleChange) {
|
|
85
|
-
renderer?.onUpdate?.(props);
|
|
86
|
-
}
|
|
87
|
-
if (handleStart) {
|
|
88
|
-
renderer?.onStart?.(props);
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
destroy: () => {
|
|
92
|
-
if (!props) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
renderer?.onExit?.(props);
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
},
|
|
99
|
-
state: {
|
|
100
|
-
// Initialize the plugin's internal state.
|
|
101
|
-
init() {
|
|
102
|
-
const state = {
|
|
103
|
-
manual: false,
|
|
104
|
-
active: false,
|
|
105
|
-
range: {
|
|
106
|
-
from: 0,
|
|
107
|
-
to: 0,
|
|
108
|
-
},
|
|
109
|
-
query: null,
|
|
110
|
-
text: null,
|
|
111
|
-
composing: false,
|
|
112
|
-
};
|
|
113
|
-
return state;
|
|
114
|
-
},
|
|
115
|
-
// Apply changes to the plugin state from a view transaction.
|
|
116
|
-
apply(transaction, prev, _oldState, state) {
|
|
117
|
-
// const { isEditable } = editor; // TODO
|
|
118
|
-
const isEditable = true;
|
|
119
|
-
const { composing } = editor.view;
|
|
120
|
-
const { selection } = transaction;
|
|
121
|
-
const { empty, from } = selection;
|
|
122
|
-
const next = { ...prev };
|
|
123
|
-
const meta = transaction.getMeta(AutocompletePluginKey);
|
|
124
|
-
if (!meta && !transaction.isGeneric) {
|
|
125
|
-
return next;
|
|
126
|
-
}
|
|
127
|
-
if (meta?.type === 'deactivate') {
|
|
128
|
-
console.info('Deactivate autocomplete');
|
|
129
|
-
next.active = false;
|
|
130
|
-
return next;
|
|
131
|
-
}
|
|
132
|
-
if (meta?.type === 'activate') {
|
|
133
|
-
console.info('Trigger manual autocomplete');
|
|
134
|
-
next.range = { from: selection.from, to: selection.to };
|
|
135
|
-
next.active = true;
|
|
136
|
-
next.manual = true;
|
|
137
|
-
next.query = null;
|
|
138
|
-
return next;
|
|
139
|
-
}
|
|
140
|
-
next.composing = composing;
|
|
141
|
-
const parentNode = selection.$anchor.parent;
|
|
142
|
-
if (!['code_block'].includes(parentNode?.type.name) && isEditable &&
|
|
143
|
-
(empty || editor.view.composing)) {
|
|
144
|
-
// Reset active state if we just left the previous suggestion range
|
|
145
|
-
if ((from < prev.range.from || from > prev.range.to) && !composing &&
|
|
146
|
-
!prev.composing) {
|
|
147
|
-
next.active = false;
|
|
148
|
-
}
|
|
149
|
-
const matchers = config.matchers ||
|
|
150
|
-
[createDefaultMatcher()];
|
|
151
|
-
let match = undefined;
|
|
152
|
-
for (const matcher of matchers) {
|
|
153
|
-
match = matcher(selection.$from);
|
|
154
|
-
if (match) {
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const decorationId = `id_${Math.floor(Math.random() * 0xffffffff)}`;
|
|
159
|
-
// If we found a match, update the current state to show it
|
|
160
|
-
if (match && (!config.allow || config.allow({
|
|
161
|
-
state,
|
|
162
|
-
range: match.range,
|
|
163
|
-
isActive: prev.active,
|
|
164
|
-
}))) {
|
|
165
|
-
console.info('Trigger matcher autocomplete', match);
|
|
166
|
-
next.active = true;
|
|
167
|
-
next.decorationId = prev.decorationId
|
|
168
|
-
? prev.decorationId
|
|
169
|
-
: decorationId;
|
|
170
|
-
next.range = match.range;
|
|
171
|
-
next.query = match.query;
|
|
172
|
-
next.text = match.text;
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
next.active = false;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
next.active = false;
|
|
180
|
-
}
|
|
181
|
-
next.manual = false;
|
|
182
|
-
// Make sure to empty the range if suggestion is inactive
|
|
183
|
-
if (!next.active) {
|
|
184
|
-
next.decorationId = null;
|
|
185
|
-
next.range = { from: 0, to: 0 };
|
|
186
|
-
next.query = null;
|
|
187
|
-
next.text = null;
|
|
188
|
-
}
|
|
189
|
-
return next;
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
props: {
|
|
193
|
-
// Call the keydown hook if suggestion is active.
|
|
194
|
-
handleKeyDown(view, event) {
|
|
195
|
-
const { active, range } = this.getState(view.state);
|
|
196
|
-
if (event.key === ' ' && event.ctrlKey) {
|
|
197
|
-
const tr = view.state.tr.setMeta(AutocompletePluginKey, {
|
|
198
|
-
type: 'activate',
|
|
199
|
-
});
|
|
200
|
-
console.info('Manual autocomplete key');
|
|
201
|
-
view.dispatch(tr);
|
|
202
|
-
return true;
|
|
203
|
-
}
|
|
204
|
-
if (active) {
|
|
205
|
-
return renderer?.onKeyDown?.({ view, event, range }) || false;
|
|
206
|
-
}
|
|
207
|
-
return false;
|
|
208
|
-
},
|
|
209
|
-
// Setup decorator on the currently active suggestion.
|
|
210
|
-
decorations(state) {
|
|
211
|
-
const { active, range, decorationId } = this.getState(state);
|
|
212
|
-
if (!active) {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
const node = document.createElement('span');
|
|
216
|
-
node.className = config.decorationClass || 'kb-autocomplete--decor';
|
|
217
|
-
node.setAttribute('data-decoration-id', decorationId);
|
|
218
|
-
return DecorationSet.create(state.doc, [
|
|
219
|
-
Decoration.widget(range.from, node),
|
|
220
|
-
]);
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
renderer.addEventListener('close', () => {
|
|
225
|
-
const tr = editor.state.tr.setMeta(AutocompletePluginKey, {
|
|
226
|
-
type: 'deactivate',
|
|
227
|
-
});
|
|
228
|
-
console.info('Manual autocomplete deactivate');
|
|
229
|
-
editor.view.dispatch(tr);
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
package/esm/DefaultRenderer.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { CoreEditor } from '@kerebron/editor';
|
|
2
|
-
import { AutocompleteRenderer, SuggestionKeyDownProps, SuggestionProps } from './types.js';
|
|
3
|
-
export declare class DefaultRenderer<Item> extends EventTarget implements AutocompleteRenderer {
|
|
4
|
-
private editor;
|
|
5
|
-
command: (props: any) => void;
|
|
6
|
-
wrapper: HTMLElement | undefined;
|
|
7
|
-
items: Array<Item>;
|
|
8
|
-
pos: number;
|
|
9
|
-
constructor(editor: CoreEditor);
|
|
10
|
-
onStart(props: SuggestionProps<Item>): void;
|
|
11
|
-
onUpdate(props: SuggestionProps<Item>): void;
|
|
12
|
-
onExit(): void;
|
|
13
|
-
onKeyDown(props: SuggestionKeyDownProps): boolean;
|
|
14
|
-
createListItem(item: Item, cnt: number): any;
|
|
15
|
-
recreateList(props?: SuggestionProps<Item>): void;
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=DefaultRenderer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultRenderer.d.ts","sourceRoot":"","sources":["../src/DefaultRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EAChB,MAAM,YAAY,CAAC;AAIpB,qBAAa,eAAe,CAAC,IAAI,CAAE,SAAQ,WACzC,YAAW,oBAAoB;IAMnB,OAAO,CAAC,MAAM;IAL1B,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IAC9B,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAM;IACxB,GAAG,EAAE,MAAM,CAAM;gBAEG,MAAM,EAAE,UAAU;IAKtC,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,IAAI,CAAC;IAiBpC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,IAAI,CAAC;IAMrC,MAAM;IASN,SAAS,CAAC,KAAK,EAAE,sBAAsB;IA0CvC,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM;IAatC,YAAY,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC,IAAI,CAAC;CAyB3C"}
|
package/esm/DefaultRenderer.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
const CSS_PREFIX = 'kb-autocomplete';
|
|
2
|
-
export class DefaultRenderer extends EventTarget {
|
|
3
|
-
editor;
|
|
4
|
-
command;
|
|
5
|
-
wrapper;
|
|
6
|
-
items = [];
|
|
7
|
-
pos = -1;
|
|
8
|
-
constructor(editor) {
|
|
9
|
-
super();
|
|
10
|
-
this.editor = editor;
|
|
11
|
-
this.command = () => { };
|
|
12
|
-
}
|
|
13
|
-
onStart(props) {
|
|
14
|
-
this.command = props.command;
|
|
15
|
-
if (this.wrapper) {
|
|
16
|
-
this.wrapper.parentElement?.removeChild(this.wrapper);
|
|
17
|
-
}
|
|
18
|
-
const root = ('root' in this.editor.view)
|
|
19
|
-
? this.editor.view.root
|
|
20
|
-
: document || document;
|
|
21
|
-
this.wrapper = document.createElement('ul');
|
|
22
|
-
this.wrapper.classList.add(CSS_PREFIX + '__wrapper');
|
|
23
|
-
root.appendChild(this.wrapper);
|
|
24
|
-
this.items.splice(0, this.items.length, ...props.items);
|
|
25
|
-
this.recreateList(props);
|
|
26
|
-
}
|
|
27
|
-
onUpdate(props) {
|
|
28
|
-
this.command = props.command;
|
|
29
|
-
this.items.splice(0, this.items.length, ...props.items);
|
|
30
|
-
this.recreateList(props);
|
|
31
|
-
}
|
|
32
|
-
onExit() {
|
|
33
|
-
if (this.wrapper) {
|
|
34
|
-
this.wrapper.parentNode?.removeChild(this.wrapper);
|
|
35
|
-
this.wrapper = undefined;
|
|
36
|
-
}
|
|
37
|
-
this.dispatchEvent(new Event('close'));
|
|
38
|
-
this.pos = -1;
|
|
39
|
-
}
|
|
40
|
-
onKeyDown(props) {
|
|
41
|
-
if (!this.wrapper) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
if (this.items.length === 0) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
if (props.event.key === 'Escape') {
|
|
48
|
-
if (this.wrapper) {
|
|
49
|
-
this.wrapper.parentNode?.removeChild(this.wrapper);
|
|
50
|
-
this.wrapper = undefined;
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (props.event.key === 'ArrowUp') {
|
|
55
|
-
if (this.pos > -1) {
|
|
56
|
-
this.pos = this.pos - 1;
|
|
57
|
-
this.recreateList();
|
|
58
|
-
}
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
if (props.event.key === 'ArrowDown') {
|
|
62
|
-
if (this.pos < this.items.length - 1) {
|
|
63
|
-
this.pos++;
|
|
64
|
-
this.recreateList();
|
|
65
|
-
}
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
if (props.event.key === 'Enter') {
|
|
69
|
-
if (this.pos > -1 && this.pos < this.items.length) {
|
|
70
|
-
this.command(this.items[this.pos]);
|
|
71
|
-
this.items.splice(0, this.items.length);
|
|
72
|
-
this.onExit();
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
createListItem(item, cnt) {
|
|
79
|
-
const li = document.createElement('li');
|
|
80
|
-
if (cnt === this.pos) {
|
|
81
|
-
li.classList.add('active');
|
|
82
|
-
}
|
|
83
|
-
li.innerText = '' + item; // TODO item to string and item formatting
|
|
84
|
-
li.style.cursor = 'pointer';
|
|
85
|
-
li.addEventListener('click', () => {
|
|
86
|
-
this.command(item);
|
|
87
|
-
});
|
|
88
|
-
return li;
|
|
89
|
-
}
|
|
90
|
-
recreateList(props) {
|
|
91
|
-
if (!this.wrapper) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
this.wrapper.innerHTML = '';
|
|
95
|
-
for (let cnt = 0; cnt < this.items.length; cnt++) {
|
|
96
|
-
const item = this.items[cnt];
|
|
97
|
-
this.wrapper.appendChild(this.createListItem(item, cnt));
|
|
98
|
-
}
|
|
99
|
-
if (this.items.length === 0) {
|
|
100
|
-
this.wrapper.style.display = 'none';
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
this.wrapper.style.display = '';
|
|
104
|
-
}
|
|
105
|
-
const rect = props?.clientRect?.();
|
|
106
|
-
if (rect?.height) {
|
|
107
|
-
const left = rect.left;
|
|
108
|
-
const bottom = rect.bottom;
|
|
109
|
-
this.wrapper.style.left = left + 'px';
|
|
110
|
-
this.wrapper.style.top = bottom + 'px';
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { EditorState, Plugin } from 'prosemirror-state';
|
|
2
|
-
import { Extension, type TextRange } from '@kerebron/editor';
|
|
3
|
-
import { AutocompletePlugin } from './AutocompletePlugin.js';
|
|
4
|
-
import { AutocompleteMatcher, AutocompleteRenderer } from './types.js';
|
|
5
|
-
export interface AutocompleteProps {
|
|
6
|
-
state: EditorState;
|
|
7
|
-
range: TextRange;
|
|
8
|
-
isActive?: boolean;
|
|
9
|
-
}
|
|
10
|
-
export interface AutocompleteConfig<I = any, TSelected = any> {
|
|
11
|
-
getItems: (query: string, props: AutocompleteProps) => I[] | Promise<I[]>;
|
|
12
|
-
onSelect?: (selected: TSelected, range: TextRange) => void;
|
|
13
|
-
allow?: (props: AutocompleteProps) => boolean;
|
|
14
|
-
matchers?: AutocompleteMatcher[];
|
|
15
|
-
renderer?: AutocompleteRenderer<I, TSelected>;
|
|
16
|
-
decorationTag?: string;
|
|
17
|
-
decorationClass?: string;
|
|
18
|
-
}
|
|
19
|
-
export declare class ExtensionAutocomplete extends Extension {
|
|
20
|
-
protected config: AutocompleteConfig;
|
|
21
|
-
name: string;
|
|
22
|
-
plugin: AutocompletePlugin<unknown, unknown>;
|
|
23
|
-
constructor(config: AutocompleteConfig);
|
|
24
|
-
getProseMirrorPlugins(): Plugin[];
|
|
25
|
-
}
|
|
26
|
-
//# sourceMappingURL=ExtensionAutocomplete.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ExtensionAutocomplete.d.ts","sourceRoot":"","sources":["../src/ExtensionAutocomplete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EAAmB,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG;IAC1D,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAE1E,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC3D,KAAK,CAAC,EAAE,CACN,KAAK,EAAE,iBAAiB,KACrB,OAAO,CAAC;IAEb,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,QAAQ,CAAC,EAAE,oBAAoB,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAE9C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,qBAAsB,SAAQ,SAAS;cAK7B,MAAM,EAAE,kBAAkB;IAJ/C,IAAI,SAAkB;IACtB,MAAM,EAAG,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAGzB,MAAM,EAAE,kBAAkB;IAKtC,qBAAqB,IAAI,MAAM,EAAE;CAK3C"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Extension } from '@kerebron/editor';
|
|
2
|
-
import { AutocompletePlugin } from './AutocompletePlugin.js';
|
|
3
|
-
export class ExtensionAutocomplete extends Extension {
|
|
4
|
-
config;
|
|
5
|
-
name = 'autocomplete';
|
|
6
|
-
plugin;
|
|
7
|
-
constructor(config) {
|
|
8
|
-
super(config);
|
|
9
|
-
this.config = config;
|
|
10
|
-
}
|
|
11
|
-
getProseMirrorPlugins() {
|
|
12
|
-
return [
|
|
13
|
-
new AutocompletePlugin(this.config, this.editor),
|
|
14
|
-
];
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { AutocompleteMatcher } from './types.js';
|
|
2
|
-
export declare function escapeForRegEx(string: string): string;
|
|
3
|
-
export interface MatcherConfig {
|
|
4
|
-
char?: string;
|
|
5
|
-
allowSpaces?: boolean;
|
|
6
|
-
allowToIncludeChar?: boolean;
|
|
7
|
-
allowedPrefixes?: string[] | null;
|
|
8
|
-
startOfLine?: boolean;
|
|
9
|
-
}
|
|
10
|
-
export declare function createDefaultMatcher(config?: MatcherConfig): AutocompleteMatcher;
|
|
11
|
-
//# sourceMappingURL=createDefaultMatcher.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createDefaultMatcher.d.ts","sourceRoot":"","sources":["../src/createDefaultMatcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAmB,MAAM,YAAY,CAAC;AAGlE,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,GAAE,aAAkB,GACzB,mBAAmB,CA4ErB"}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// source: https://stackoverflow.com/a/6969486
|
|
2
|
-
export function escapeForRegEx(string) {
|
|
3
|
-
return string.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
4
|
-
}
|
|
5
|
-
export function createDefaultMatcher(config = {}) {
|
|
6
|
-
const char = config.char || '@';
|
|
7
|
-
const allowToIncludeChar = config.allowToIncludeChar || false;
|
|
8
|
-
const allowedPrefixes = config.allowedPrefixes || [' '];
|
|
9
|
-
const startOfLine = config.startOfLine || false;
|
|
10
|
-
return ($position) => {
|
|
11
|
-
const allowSpaces = config.allowSpaces && !allowToIncludeChar;
|
|
12
|
-
const escapedChar = escapeForRegEx(char);
|
|
13
|
-
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
14
|
-
const prefix = startOfLine ? '^' : '';
|
|
15
|
-
const finalEscapedChar = allowToIncludeChar ? '' : escapedChar;
|
|
16
|
-
const regexp = allowSpaces
|
|
17
|
-
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${finalEscapedChar}|$)`, 'gm')
|
|
18
|
-
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${finalEscapedChar}]*`, 'gm');
|
|
19
|
-
const text = $position.nodeBefore?.isText && $position.nodeBefore.text;
|
|
20
|
-
if (!text) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
const textFrom = $position.pos - text.length;
|
|
24
|
-
const match = Array.from(text.matchAll(regexp)).pop();
|
|
25
|
-
if (!match || match.input === undefined || match.index === undefined) {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
// JavaScript doesn't have lookbehinds. This hacks a check that first character
|
|
29
|
-
// is a space or the start of the line
|
|
30
|
-
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
|
|
31
|
-
const matchPrefixIsAllowed = new RegExp(`^[${allowedPrefixes?.join('')}\0]?$`)
|
|
32
|
-
.test(matchPrefix);
|
|
33
|
-
if (allowedPrefixes !== null && !matchPrefixIsAllowed) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
// The absolute position of the match in the document
|
|
37
|
-
const from = textFrom + match.index;
|
|
38
|
-
let to = from + match[0].length;
|
|
39
|
-
// Edge case handling; if spaces are allowed and we're directly in between
|
|
40
|
-
// two triggers
|
|
41
|
-
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
|
42
|
-
match[0] += ' ';
|
|
43
|
-
to += 1;
|
|
44
|
-
}
|
|
45
|
-
// If the $position is located within the matched substring, return that range
|
|
46
|
-
if (from < $position.pos && to >= $position.pos) {
|
|
47
|
-
return {
|
|
48
|
-
range: {
|
|
49
|
-
from,
|
|
50
|
-
to,
|
|
51
|
-
},
|
|
52
|
-
query: match[0],
|
|
53
|
-
text: match[0],
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createRegexMatcher.d.ts","sourceRoot":"","sources":["../src/createRegexMatcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAmB,MAAM,YAAY,CAAC;AAElE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,UASxD;AAqBD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EAAE,GAChB,mBAAmB,CAmCrB"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export function ensureAnchor(expr, start) {
|
|
2
|
-
let { source } = expr;
|
|
3
|
-
let addStart = start && source[0] != '^', addEnd = source[source.length - 1] != '$';
|
|
4
|
-
if (!addStart && !addEnd)
|
|
5
|
-
return expr;
|
|
6
|
-
return new RegExp(`${addStart ? '^' : ''}(?:${source})${addEnd ? '$' : ''}`, expr.flags ?? (expr.ignoreCase ? 'i' : ''));
|
|
7
|
-
}
|
|
8
|
-
function matchBefore($position, expr) {
|
|
9
|
-
const text = $position.nodeBefore?.isText && $position.nodeBefore.text;
|
|
10
|
-
if (!text) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
const textFrom = $position.pos - text.length;
|
|
14
|
-
const start = Math.max(textFrom, $position.pos - 250);
|
|
15
|
-
const str = text.slice();
|
|
16
|
-
const found = str.search(ensureAnchor(expr, false));
|
|
17
|
-
return found < 0
|
|
18
|
-
? null
|
|
19
|
-
: { from: start + found, to: $position.pos, text: str.slice(found) };
|
|
20
|
-
}
|
|
21
|
-
export function createRegexMatcher(regexes) {
|
|
22
|
-
return ($position) => {
|
|
23
|
-
const text = $position.nodeBefore?.isText && $position.nodeBefore.text;
|
|
24
|
-
if (!text) {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
const textFrom = $position.pos - text.length;
|
|
28
|
-
const matches = regexes.map((regex) => matchBefore($position, regex))
|
|
29
|
-
.filter((m) => !!m);
|
|
30
|
-
if (matches.length === 0) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
matches.sort((a, b) => b.text.length - a.text.length);
|
|
34
|
-
let from = matches[0].from;
|
|
35
|
-
let matchedText = matches[0].text;
|
|
36
|
-
let to = matches[0].to;
|
|
37
|
-
while (matchedText.match(/^\s/)) {
|
|
38
|
-
matchedText = matchedText.substring(1);
|
|
39
|
-
from++;
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
range: {
|
|
43
|
-
from,
|
|
44
|
-
to,
|
|
45
|
-
},
|
|
46
|
-
query: matchedText,
|
|
47
|
-
text: matchedText,
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
}
|
package/esm/mod.d.ts
DELETED
package/esm/mod.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/esm/mod.js
DELETED
package/esm/package.json
DELETED
package/esm/types.d.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { ResolvedPos } from 'prosemirror-model';
|
|
2
|
-
import type { TextRange } from '@kerebron/editor';
|
|
3
|
-
import { EditorView } from 'prosemirror-view';
|
|
4
|
-
export type SuggestionMatch = {
|
|
5
|
-
range: TextRange;
|
|
6
|
-
query: string;
|
|
7
|
-
text: string;
|
|
8
|
-
} | null;
|
|
9
|
-
export type AutocompleteMatcher = (pos: ResolvedPos) => SuggestionMatch;
|
|
10
|
-
export interface SuggestionKeyDownProps {
|
|
11
|
-
view: EditorView;
|
|
12
|
-
event: KeyboardEvent;
|
|
13
|
-
range: TextRange;
|
|
14
|
-
}
|
|
15
|
-
export interface AutocompleteRenderer<I = any, TSelected = any> {
|
|
16
|
-
onBeforeStart?: (props: SuggestionProps<I, TSelected>) => void;
|
|
17
|
-
onStart?: (props: SuggestionProps<I, TSelected>) => void;
|
|
18
|
-
onBeforeUpdate?: (props: SuggestionProps<I, TSelected>) => void;
|
|
19
|
-
onUpdate?: (props: SuggestionProps<I, TSelected>) => void;
|
|
20
|
-
onExit?: (props: SuggestionProps<I, TSelected>) => void;
|
|
21
|
-
onKeyDown?: (props: SuggestionKeyDownProps) => boolean;
|
|
22
|
-
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
23
|
-
removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
|
|
24
|
-
}
|
|
25
|
-
export interface SuggestionProps<I = any, TSelected = any> {
|
|
26
|
-
/**
|
|
27
|
-
* The range of the suggestion.
|
|
28
|
-
*/
|
|
29
|
-
range: TextRange;
|
|
30
|
-
/**
|
|
31
|
-
* The current suggestion query.
|
|
32
|
-
*/
|
|
33
|
-
query: string;
|
|
34
|
-
/**
|
|
35
|
-
* The current suggestion text.
|
|
36
|
-
*/
|
|
37
|
-
text: string;
|
|
38
|
-
/**
|
|
39
|
-
* The suggestion items array.
|
|
40
|
-
*/
|
|
41
|
-
items: I[];
|
|
42
|
-
/**
|
|
43
|
-
* A function that is called when a suggestion is selected.
|
|
44
|
-
* @param props The props object.
|
|
45
|
-
* @returns void
|
|
46
|
-
*/
|
|
47
|
-
command: (props: TSelected) => void;
|
|
48
|
-
/**
|
|
49
|
-
* The decoration node HTML element
|
|
50
|
-
* @default null
|
|
51
|
-
*/
|
|
52
|
-
decorationNode: Element | null;
|
|
53
|
-
/**
|
|
54
|
-
* The function that returns the client rect
|
|
55
|
-
* @default null
|
|
56
|
-
* @example () => new DOMRect(0, 0, 0, 0)
|
|
57
|
-
*/
|
|
58
|
-
clientRect?: (() => DOMRect | null) | null;
|
|
59
|
-
}
|
|
60
|
-
//# sourceMappingURL=types.d.ts.map
|
package/esm/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,IAAI,CAAC;AAET,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,eAAe,CAAC;AAExE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB,CAAC,CAAC,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG;IAC5D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,IAAI,CAAC;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,IAAI,CAAC;IACzD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,IAAI,CAAC;IAChE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,IAAI,CAAC;IAC1D,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,IAAI,CAAC;IACxD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,OAAO,CAAC;IAEvD,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,GAAG,IAAI,EACnD,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,GACvC,IAAI,CAAC;CACT;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG;IACvD;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,EAAE,CAAC,EAAE,CAAC;IAEX;;;;OAIG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAEpC;;;OAGG;IACH,cAAc,EAAE,OAAO,GAAG,IAAI,CAAC;IAE/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5C"}
|
package/esm/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|