@tiptap/suggestion 3.26.0 → 3.27.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.
- package/dist/index.cjs +619 -270
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +193 -2
- package/dist/index.d.ts +193 -2
- package/dist/index.js +624 -270
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
- package/src/__tests__/suggestion.test.ts +837 -0
- package/src/helpers.ts +129 -0
- package/src/plugin/async.ts +89 -0
- package/src/plugin/floating-ui.ts +204 -0
- package/src/plugin/props.ts +94 -0
- package/src/plugin/state.ts +182 -0
- package/src/plugin/view.ts +236 -0
- package/src/suggestion.ts +97 -606
- package/src/types.ts +439 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/suggestion.ts
|
|
2
2
|
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
3
|
-
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
4
3
|
|
|
5
4
|
// src/findSuggestionMatch.ts
|
|
6
5
|
import { escapeForRegEx } from "@tiptap/core";
|
|
@@ -53,7 +52,7 @@ function findSuggestionMatch(config) {
|
|
|
53
52
|
return null;
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
// src/
|
|
55
|
+
// src/helpers.ts
|
|
57
56
|
function hasInsertedWhitespace(transaction) {
|
|
58
57
|
if (!transaction.docChanged) {
|
|
59
58
|
return false;
|
|
@@ -67,31 +66,8 @@ function hasInsertedWhitespace(transaction) {
|
|
|
67
66
|
return /\s/.test(inserted);
|
|
68
67
|
});
|
|
69
68
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
pluginKey = SuggestionPluginKey,
|
|
73
|
-
editor,
|
|
74
|
-
char = "@",
|
|
75
|
-
allowSpaces = false,
|
|
76
|
-
allowToIncludeChar = false,
|
|
77
|
-
allowedPrefixes = [" "],
|
|
78
|
-
startOfLine = false,
|
|
79
|
-
decorationTag = "span",
|
|
80
|
-
decorationClass = "suggestion",
|
|
81
|
-
decorationContent = "",
|
|
82
|
-
decorationEmptyClass = "is-empty",
|
|
83
|
-
command = () => null,
|
|
84
|
-
items = () => [],
|
|
85
|
-
render = () => ({}),
|
|
86
|
-
allow = () => true,
|
|
87
|
-
findSuggestionMatch: findSuggestionMatch2 = findSuggestionMatch,
|
|
88
|
-
shouldShow,
|
|
89
|
-
shouldResetDismissed
|
|
90
|
-
}) {
|
|
91
|
-
let props;
|
|
92
|
-
const renderer = render == null ? void 0 : render();
|
|
93
|
-
const effectiveAllowSpaces = allowSpaces && !allowToIncludeChar;
|
|
94
|
-
const getAnchorClientRect = () => {
|
|
69
|
+
function getAnchorClientRect(editor) {
|
|
70
|
+
return () => {
|
|
95
71
|
const pos = editor.state.selection.$anchor.pos;
|
|
96
72
|
const coords = editor.view.coordsAtPos(pos);
|
|
97
73
|
const { top, right, bottom, left } = coords;
|
|
@@ -101,275 +77,653 @@ function Suggestion({
|
|
|
101
77
|
return null;
|
|
102
78
|
}
|
|
103
79
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
80
|
+
}
|
|
81
|
+
function clientRectFor(editor, view, decorationNode, pluginKey) {
|
|
82
|
+
if (!decorationNode) {
|
|
83
|
+
return getAnchorClientRect(editor);
|
|
84
|
+
}
|
|
85
|
+
return () => {
|
|
86
|
+
const state = pluginKey.getState(editor.state);
|
|
87
|
+
const decorationId = state == null ? void 0 : state.decorationId;
|
|
88
|
+
const currentDecorationNode = view.dom.querySelector(`[data-decoration-id="${decorationId}"]`);
|
|
89
|
+
return (currentDecorationNode == null ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
|
|
114
90
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
91
|
+
}
|
|
92
|
+
function shouldKeepDismissed({
|
|
93
|
+
match,
|
|
94
|
+
dismissedRange,
|
|
95
|
+
state,
|
|
96
|
+
transaction,
|
|
97
|
+
editor,
|
|
98
|
+
shouldResetDismissed,
|
|
99
|
+
effectiveAllowSpaces
|
|
100
|
+
}) {
|
|
101
|
+
if (shouldResetDismissed == null ? void 0 : shouldResetDismissed({
|
|
102
|
+
editor,
|
|
118
103
|
state,
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
104
|
+
range: dismissedRange,
|
|
105
|
+
match,
|
|
106
|
+
transaction,
|
|
107
|
+
allowSpaces: effectiveAllowSpaces
|
|
108
|
+
})) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (effectiveAllowSpaces) {
|
|
112
|
+
return match.range.from === dismissedRange.from;
|
|
113
|
+
}
|
|
114
|
+
return match.range.from === dismissedRange.from && !hasInsertedWhitespace(transaction);
|
|
115
|
+
}
|
|
116
|
+
function dispatchExit({
|
|
117
|
+
view,
|
|
118
|
+
pluginKeyRef
|
|
119
|
+
}) {
|
|
120
|
+
const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true });
|
|
121
|
+
view.dispatch(tr);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/plugin/props.ts
|
|
125
|
+
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
126
|
+
function createSuggestionProps({
|
|
127
|
+
pluginKey,
|
|
128
|
+
decorationTag,
|
|
129
|
+
decorationClass,
|
|
130
|
+
decorationContent,
|
|
131
|
+
decorationEmptyClass,
|
|
132
|
+
renderer,
|
|
133
|
+
dispatchExit: dispatchExit2
|
|
134
|
+
}) {
|
|
135
|
+
return {
|
|
136
|
+
/**
|
|
137
|
+
* Call the keydown hook if suggestion is active.
|
|
138
|
+
*/
|
|
139
|
+
handleKeyDown(view, event) {
|
|
140
|
+
var _a, _b;
|
|
139
141
|
const state = pluginKey.getState(view.state);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
142
|
+
if (!state.active) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (event.key === "Escape" || event.key === "Esc") {
|
|
146
|
+
(_a = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _a.call(renderer, { view, event, range: state.range });
|
|
147
|
+
dispatchExit2(view);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
const handled = ((_b = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _b.call(renderer, { view, event, range: state.range })) || false;
|
|
151
|
+
return handled;
|
|
152
|
+
},
|
|
153
|
+
/**
|
|
154
|
+
* Setup decorator on the currently active suggestion.
|
|
155
|
+
*/
|
|
156
|
+
decorations(state) {
|
|
157
|
+
const pluginState = pluginKey.getState(state);
|
|
158
|
+
const { active, range, decorationId, query } = pluginState;
|
|
159
|
+
if (!active) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const isEmpty = !(query == null ? void 0 : query.length);
|
|
163
|
+
const classNames = [decorationClass];
|
|
164
|
+
if (isEmpty) {
|
|
165
|
+
classNames.push(decorationEmptyClass);
|
|
166
|
+
}
|
|
167
|
+
return DecorationSet.create(state.doc, [
|
|
168
|
+
Decoration.inline(range.from, range.to, {
|
|
169
|
+
nodeName: decorationTag,
|
|
170
|
+
class: classNames.join(" "),
|
|
171
|
+
"data-decoration-id": decorationId || void 0,
|
|
172
|
+
"data-decoration-content": decorationContent
|
|
173
|
+
})
|
|
174
|
+
]);
|
|
160
175
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/plugin/state.ts
|
|
180
|
+
function createSuggestionState({
|
|
181
|
+
editor,
|
|
182
|
+
char,
|
|
183
|
+
effectiveAllowSpaces,
|
|
184
|
+
allowToIncludeChar,
|
|
185
|
+
allowedPrefixes,
|
|
186
|
+
startOfLine,
|
|
187
|
+
findSuggestionMatch: findSuggestionMatch2,
|
|
188
|
+
allow,
|
|
189
|
+
shouldShow,
|
|
190
|
+
shouldKeepDismissed: shouldKeepDismissed2,
|
|
191
|
+
pluginKey
|
|
192
|
+
}) {
|
|
193
|
+
return {
|
|
194
|
+
/**
|
|
195
|
+
* Initialize the plugin's internal state.
|
|
196
|
+
*/
|
|
197
|
+
init() {
|
|
167
198
|
return {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const stopped = prev.active && !next.active;
|
|
175
|
-
const changed = !started && !stopped && prev.query !== next.query;
|
|
176
|
-
const handleStart = started || moved && changed;
|
|
177
|
-
const handleChange = changed || moved;
|
|
178
|
-
const handleExit = stopped || moved && changed;
|
|
179
|
-
if (!handleStart && !handleChange && !handleExit) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
const state = handleExit && !handleStart ? prev : next;
|
|
183
|
-
const decorationNode = view.dom.querySelector(
|
|
184
|
-
`[data-decoration-id="${state.decorationId}"]`
|
|
185
|
-
);
|
|
186
|
-
props = {
|
|
187
|
-
editor,
|
|
188
|
-
range: state.range,
|
|
189
|
-
query: state.query,
|
|
190
|
-
text: state.text,
|
|
191
|
-
items: [],
|
|
192
|
-
command: (commandProps) => {
|
|
193
|
-
return command({
|
|
194
|
-
editor,
|
|
195
|
-
range: state.range,
|
|
196
|
-
props: commandProps
|
|
197
|
-
});
|
|
198
|
-
},
|
|
199
|
-
decorationNode,
|
|
200
|
-
clientRect: clientRectFor(view, decorationNode)
|
|
201
|
-
};
|
|
202
|
-
if (handleStart) {
|
|
203
|
-
(_c = renderer == null ? void 0 : renderer.onBeforeStart) == null ? void 0 : _c.call(renderer, props);
|
|
204
|
-
}
|
|
205
|
-
if (handleChange) {
|
|
206
|
-
(_d = renderer == null ? void 0 : renderer.onBeforeUpdate) == null ? void 0 : _d.call(renderer, props);
|
|
207
|
-
}
|
|
208
|
-
if (handleChange || handleStart) {
|
|
209
|
-
props.items = await items({
|
|
210
|
-
editor,
|
|
211
|
-
query: state.query
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
if (handleExit) {
|
|
215
|
-
(_e = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _e.call(renderer, props);
|
|
216
|
-
}
|
|
217
|
-
if (handleChange) {
|
|
218
|
-
(_f = renderer == null ? void 0 : renderer.onUpdate) == null ? void 0 : _f.call(renderer, props);
|
|
219
|
-
}
|
|
220
|
-
if (handleStart) {
|
|
221
|
-
(_g = renderer == null ? void 0 : renderer.onStart) == null ? void 0 : _g.call(renderer, props);
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
destroy: () => {
|
|
225
|
-
var _a;
|
|
226
|
-
if (!props) {
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
(_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, props);
|
|
230
|
-
}
|
|
199
|
+
active: false,
|
|
200
|
+
range: { from: 0, to: 0 },
|
|
201
|
+
query: null,
|
|
202
|
+
text: null,
|
|
203
|
+
composing: false,
|
|
204
|
+
dismissedRange: null
|
|
231
205
|
};
|
|
232
206
|
},
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Apply changes to the plugin state from a view transaction.
|
|
209
|
+
*/
|
|
210
|
+
apply(transaction, prev, _oldState, state) {
|
|
211
|
+
const { isEditable } = editor;
|
|
212
|
+
const { composing } = editor.view;
|
|
213
|
+
const { selection } = transaction;
|
|
214
|
+
const { empty, from } = selection;
|
|
215
|
+
const next = { ...prev };
|
|
216
|
+
const meta = transaction.getMeta(pluginKey);
|
|
217
|
+
if (meta && meta.exit) {
|
|
218
|
+
next.active = false;
|
|
219
|
+
next.decorationId = null;
|
|
220
|
+
next.range = { from: 0, to: 0 };
|
|
221
|
+
next.query = null;
|
|
222
|
+
next.text = null;
|
|
223
|
+
next.dismissedRange = prev.active ? { ...prev.range } : prev.dismissedRange;
|
|
224
|
+
return next;
|
|
225
|
+
}
|
|
226
|
+
next.composing = composing;
|
|
227
|
+
if (transaction.docChanged && next.dismissedRange !== null) {
|
|
228
|
+
next.dismissedRange = {
|
|
229
|
+
from: transaction.mapping.map(next.dismissedRange.from),
|
|
230
|
+
to: transaction.mapping.map(next.dismissedRange.to)
|
|
246
231
|
};
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
apply(transaction, prev, _oldState, state) {
|
|
251
|
-
const { isEditable } = editor;
|
|
252
|
-
const { composing } = editor.view;
|
|
253
|
-
const { selection } = transaction;
|
|
254
|
-
const { empty, from } = selection;
|
|
255
|
-
const next = { ...prev };
|
|
256
|
-
const meta = transaction.getMeta(pluginKey);
|
|
257
|
-
if (meta && meta.exit) {
|
|
232
|
+
}
|
|
233
|
+
if (isEditable && (empty || editor.view.composing)) {
|
|
234
|
+
if ((from < prev.range.from || from > prev.range.to) && !composing && !prev.composing) {
|
|
258
235
|
next.active = false;
|
|
259
|
-
next.decorationId = null;
|
|
260
|
-
next.range = { from: 0, to: 0 };
|
|
261
|
-
next.query = null;
|
|
262
|
-
next.text = null;
|
|
263
|
-
next.dismissedRange = prev.active ? { ...prev.range } : prev.dismissedRange;
|
|
264
|
-
return next;
|
|
265
236
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
237
|
+
const match = findSuggestionMatch2({
|
|
238
|
+
char,
|
|
239
|
+
allowSpaces: effectiveAllowSpaces,
|
|
240
|
+
allowToIncludeChar,
|
|
241
|
+
allowedPrefixes,
|
|
242
|
+
startOfLine,
|
|
243
|
+
$position: selection.$from
|
|
244
|
+
});
|
|
245
|
+
const decorationId = `id_${Math.floor(Math.random() * 4294967295)}`;
|
|
246
|
+
if (match && allow({
|
|
247
|
+
editor,
|
|
248
|
+
state,
|
|
249
|
+
range: match.range,
|
|
250
|
+
isActive: prev.active
|
|
251
|
+
}) && (!shouldShow || shouldShow({
|
|
252
|
+
editor,
|
|
253
|
+
range: match.range,
|
|
254
|
+
query: match.query,
|
|
255
|
+
text: match.text,
|
|
256
|
+
transaction
|
|
257
|
+
}))) {
|
|
258
|
+
if (next.dismissedRange !== null && !shouldKeepDismissed2({
|
|
259
|
+
match,
|
|
260
|
+
dismissedRange: next.dismissedRange,
|
|
288
261
|
state,
|
|
289
|
-
range: match.range,
|
|
290
|
-
isActive: prev.active
|
|
291
|
-
}) && (!shouldShow || shouldShow({
|
|
292
|
-
editor,
|
|
293
|
-
range: match.range,
|
|
294
|
-
query: match.query,
|
|
295
|
-
text: match.text,
|
|
296
262
|
transaction
|
|
297
|
-
}))
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (next.dismissedRange === null) {
|
|
307
|
-
next.active = true;
|
|
308
|
-
next.decorationId = prev.decorationId ? prev.decorationId : decorationId;
|
|
309
|
-
next.range = match.range;
|
|
310
|
-
next.query = match.query;
|
|
311
|
-
next.text = match.text;
|
|
312
|
-
} else {
|
|
313
|
-
next.active = false;
|
|
314
|
-
}
|
|
263
|
+
})) {
|
|
264
|
+
next.dismissedRange = null;
|
|
265
|
+
}
|
|
266
|
+
if (next.dismissedRange === null) {
|
|
267
|
+
next.active = true;
|
|
268
|
+
next.decorationId = prev.decorationId || decorationId;
|
|
269
|
+
next.range = match.range;
|
|
270
|
+
next.query = match.query;
|
|
271
|
+
next.text = match.text;
|
|
315
272
|
} else {
|
|
316
|
-
if (!match) {
|
|
317
|
-
next.dismissedRange = null;
|
|
318
|
-
}
|
|
319
273
|
next.active = false;
|
|
320
274
|
}
|
|
321
275
|
} else {
|
|
276
|
+
if (!match) {
|
|
277
|
+
next.dismissedRange = null;
|
|
278
|
+
}
|
|
322
279
|
next.active = false;
|
|
323
280
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
next.range = { from: 0, to: 0 };
|
|
327
|
-
next.query = null;
|
|
328
|
-
next.text = null;
|
|
329
|
-
}
|
|
330
|
-
return next;
|
|
281
|
+
} else {
|
|
282
|
+
next.active = false;
|
|
331
283
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
284
|
+
if (!next.active) {
|
|
285
|
+
next.decorationId = null;
|
|
286
|
+
next.range = { from: 0, to: 0 };
|
|
287
|
+
next.query = null;
|
|
288
|
+
next.text = null;
|
|
289
|
+
}
|
|
290
|
+
return next;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/plugin/async.ts
|
|
296
|
+
function createSuggestionAsyncRequestManager({
|
|
297
|
+
editor,
|
|
298
|
+
items
|
|
299
|
+
}) {
|
|
300
|
+
let abortController = null;
|
|
301
|
+
let debounceTimer = null;
|
|
302
|
+
let debounceResolve = null;
|
|
303
|
+
const clearDebounceTimer = () => {
|
|
304
|
+
if (debounceTimer !== null) {
|
|
305
|
+
clearTimeout(debounceTimer);
|
|
306
|
+
debounceTimer = null;
|
|
307
|
+
}
|
|
308
|
+
debounceResolve == null ? void 0 : debounceResolve();
|
|
309
|
+
debounceResolve = null;
|
|
310
|
+
};
|
|
311
|
+
const waitForDebounce = (delay) => {
|
|
312
|
+
return new Promise((resolve) => {
|
|
313
|
+
debounceResolve = resolve;
|
|
314
|
+
debounceTimer = setTimeout(() => {
|
|
315
|
+
debounceTimer = null;
|
|
316
|
+
const pendingResolve = debounceResolve;
|
|
317
|
+
debounceResolve = null;
|
|
318
|
+
pendingResolve == null ? void 0 : pendingResolve();
|
|
319
|
+
}, delay);
|
|
320
|
+
});
|
|
321
|
+
};
|
|
322
|
+
const abort = () => {
|
|
323
|
+
abortController == null ? void 0 : abortController.abort();
|
|
324
|
+
clearDebounceTimer();
|
|
325
|
+
abortController = null;
|
|
326
|
+
};
|
|
327
|
+
const fetch = async (query, debounce) => {
|
|
328
|
+
abort();
|
|
329
|
+
abortController = new AbortController();
|
|
330
|
+
const controller = abortController;
|
|
331
|
+
if (debounce > 0) {
|
|
332
|
+
await waitForDebounce(debounce);
|
|
333
|
+
}
|
|
334
|
+
if (abortController !== controller || controller.signal.aborted) {
|
|
335
|
+
return { status: "aborted" };
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
const result = await items({
|
|
339
|
+
editor,
|
|
340
|
+
query,
|
|
341
|
+
signal: controller.signal
|
|
342
|
+
});
|
|
343
|
+
if (abortController !== controller || controller.signal.aborted) {
|
|
344
|
+
return { status: "aborted" };
|
|
345
|
+
}
|
|
346
|
+
return { status: "resolved", items: result };
|
|
347
|
+
} catch {
|
|
348
|
+
if (abortController !== controller || controller.signal.aborted) {
|
|
349
|
+
return { status: "aborted" };
|
|
350
|
+
}
|
|
351
|
+
return { status: "error" };
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
return {
|
|
355
|
+
abort,
|
|
356
|
+
fetch
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/plugin/floating-ui.ts
|
|
361
|
+
import {
|
|
362
|
+
autoUpdate,
|
|
363
|
+
computePosition,
|
|
364
|
+
flip as floatingUiFlip,
|
|
365
|
+
offset as floatingUiOffset
|
|
366
|
+
} from "@floating-ui/dom";
|
|
367
|
+
function createSuggestionFloatingUiConfig({
|
|
368
|
+
placement,
|
|
369
|
+
offset,
|
|
370
|
+
flip,
|
|
371
|
+
floatingUi
|
|
372
|
+
}) {
|
|
373
|
+
var _a, _b, _c, _d;
|
|
374
|
+
const middleware = [
|
|
375
|
+
floatingUiOffset({
|
|
376
|
+
mainAxis: (_a = offset.mainAxis) != null ? _a : 4,
|
|
377
|
+
crossAxis: (_b = offset.crossAxis) != null ? _b : 0
|
|
378
|
+
})
|
|
379
|
+
];
|
|
380
|
+
if (flip) {
|
|
381
|
+
middleware.push(floatingUiFlip());
|
|
382
|
+
}
|
|
383
|
+
if ((_c = floatingUi == null ? void 0 : floatingUi.middleware) == null ? void 0 : _c.length) {
|
|
384
|
+
middleware.push(...floatingUi.middleware);
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
placement,
|
|
388
|
+
strategy: (_d = floatingUi == null ? void 0 : floatingUi.strategy) != null ? _d : "absolute",
|
|
389
|
+
middleware
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function resolveContainer(container) {
|
|
393
|
+
if (container instanceof HTMLElement) {
|
|
394
|
+
return container;
|
|
395
|
+
}
|
|
396
|
+
if (typeof container === "string") {
|
|
397
|
+
try {
|
|
398
|
+
const found = document.querySelector(container);
|
|
399
|
+
if (found) {
|
|
400
|
+
return found;
|
|
401
|
+
}
|
|
402
|
+
} catch {
|
|
403
|
+
return document.body;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return document.body;
|
|
407
|
+
}
|
|
408
|
+
function createMount({
|
|
409
|
+
getReferenceRect,
|
|
410
|
+
contextElement,
|
|
411
|
+
config,
|
|
412
|
+
container,
|
|
413
|
+
dismissOnOutsideClick,
|
|
414
|
+
dismiss
|
|
415
|
+
}) {
|
|
416
|
+
return (element, options = {}) => {
|
|
417
|
+
const reference = {
|
|
418
|
+
getBoundingClientRect: () => {
|
|
419
|
+
var _a;
|
|
420
|
+
return (_a = getReferenceRect()) != null ? _a : new DOMRect();
|
|
421
|
+
},
|
|
422
|
+
contextElement
|
|
423
|
+
};
|
|
424
|
+
let positioned = false;
|
|
425
|
+
const mountedByUs = !element.isConnected;
|
|
426
|
+
if (mountedByUs) {
|
|
427
|
+
resolveContainer(container).appendChild(element);
|
|
428
|
+
}
|
|
429
|
+
if (!options.onPosition) {
|
|
430
|
+
element.style.visibility = "hidden";
|
|
431
|
+
element.style.width = "max-content";
|
|
432
|
+
}
|
|
433
|
+
const update = () => {
|
|
434
|
+
computePosition(reference, element, {
|
|
435
|
+
placement: config.placement,
|
|
436
|
+
strategy: config.strategy,
|
|
437
|
+
middleware: config.middleware
|
|
438
|
+
}).then(({ x, y, placement, strategy }) => {
|
|
439
|
+
if (options.onPosition) {
|
|
440
|
+
options.onPosition({ x, y, placement, strategy });
|
|
441
|
+
return;
|
|
340
442
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
443
|
+
Object.assign(element.style, {
|
|
444
|
+
position: strategy,
|
|
445
|
+
left: `${x}px`,
|
|
446
|
+
top: `${y}px`
|
|
447
|
+
});
|
|
448
|
+
if (!positioned) {
|
|
449
|
+
positioned = true;
|
|
450
|
+
element.style.visibility = "";
|
|
346
451
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
452
|
+
});
|
|
453
|
+
};
|
|
454
|
+
const cleanupAutoUpdate = autoUpdate(reference, element, update, options.autoUpdate);
|
|
455
|
+
let onOutsidePointerDown;
|
|
456
|
+
if (dismissOnOutsideClick) {
|
|
457
|
+
onOutsidePointerDown = (event) => {
|
|
458
|
+
const target = event.target;
|
|
459
|
+
if (!(target instanceof Node) || element.contains(target) || contextElement.contains(target)) {
|
|
460
|
+
return;
|
|
355
461
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
462
|
+
dismiss();
|
|
463
|
+
};
|
|
464
|
+
document.addEventListener("pointerdown", onOutsidePointerDown, true);
|
|
465
|
+
}
|
|
466
|
+
return () => {
|
|
467
|
+
cleanupAutoUpdate();
|
|
468
|
+
if (onOutsidePointerDown) {
|
|
469
|
+
document.removeEventListener("pointerdown", onOutsidePointerDown, true);
|
|
470
|
+
}
|
|
471
|
+
if (mountedByUs) {
|
|
472
|
+
element.remove();
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/plugin/view.ts
|
|
479
|
+
function createSuggestionView({
|
|
480
|
+
editor,
|
|
481
|
+
pluginKey,
|
|
482
|
+
items,
|
|
483
|
+
renderer,
|
|
484
|
+
minQueryLength,
|
|
485
|
+
debounce,
|
|
486
|
+
initialItems,
|
|
487
|
+
placement,
|
|
488
|
+
offset: offsetOption,
|
|
489
|
+
container,
|
|
490
|
+
flip,
|
|
491
|
+
floatingUi,
|
|
492
|
+
dismissOnOutsideClick,
|
|
493
|
+
command,
|
|
494
|
+
clientRectFor: clientRectFor2,
|
|
495
|
+
dispatchExit: dispatchExit2
|
|
496
|
+
}) {
|
|
497
|
+
let props;
|
|
498
|
+
const asyncRequest = createSuggestionAsyncRequestManager({
|
|
499
|
+
editor,
|
|
500
|
+
items
|
|
501
|
+
});
|
|
502
|
+
const floatingUiConfig = createSuggestionFloatingUiConfig({
|
|
503
|
+
placement,
|
|
504
|
+
offset: offsetOption,
|
|
505
|
+
flip,
|
|
506
|
+
floatingUi
|
|
507
|
+
});
|
|
508
|
+
function dispatchStateUpdate(state, dispatchProps) {
|
|
509
|
+
var _a, _b, _c;
|
|
510
|
+
switch (state) {
|
|
511
|
+
case "started":
|
|
512
|
+
(_a = renderer == null ? void 0 : renderer.onStart) == null ? void 0 : _a.call(renderer, dispatchProps);
|
|
513
|
+
break;
|
|
514
|
+
case "updated":
|
|
515
|
+
(_b = renderer == null ? void 0 : renderer.onUpdate) == null ? void 0 : _b.call(renderer, dispatchProps);
|
|
516
|
+
break;
|
|
517
|
+
case "stopped":
|
|
518
|
+
(_c = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _c.call(renderer, dispatchProps);
|
|
519
|
+
break;
|
|
520
|
+
default:
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
update: async (view, prevState) => {
|
|
526
|
+
var _a, _b, _c, _d;
|
|
527
|
+
const prev = pluginKey.getState(prevState);
|
|
528
|
+
const next = pluginKey.getState(view.state);
|
|
529
|
+
if (!prev || !next) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
let currentState = null;
|
|
533
|
+
const queryChanged = prev.query !== next.query;
|
|
534
|
+
const textChanged = prev.text !== next.text;
|
|
535
|
+
const rangeChanged = prev.range.from !== next.range.from || prev.range.to !== next.range.to;
|
|
536
|
+
const effectiveQueryChanged = queryChanged || textChanged || rangeChanged;
|
|
537
|
+
if (!prev.active && next.active) {
|
|
538
|
+
currentState = "started";
|
|
539
|
+
} else if (prev.active && !next.active) {
|
|
540
|
+
currentState = "stopped";
|
|
541
|
+
} else if (next.active && effectiveQueryChanged) {
|
|
542
|
+
currentState = "updated";
|
|
543
|
+
} else {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const state = currentState === "stopped" ? prev : next;
|
|
547
|
+
const decorationNode = view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
548
|
+
const clientRect = clientRectFor2(view, decorationNode);
|
|
549
|
+
const exceedsMinQueryLength = minQueryLength === 0 || (state.query ? state.query.length >= minQueryLength : false);
|
|
550
|
+
const willFetch = (currentState === "started" || currentState === "updated") && exceedsMinQueryLength;
|
|
551
|
+
props = {
|
|
552
|
+
editor,
|
|
553
|
+
range: state.range,
|
|
554
|
+
query: state.query || "",
|
|
555
|
+
text: state.text || "",
|
|
556
|
+
items: initialItems != null ? initialItems : [],
|
|
557
|
+
command: (commandProps) => {
|
|
558
|
+
return command({
|
|
559
|
+
editor,
|
|
560
|
+
range: state.range,
|
|
561
|
+
props: commandProps
|
|
562
|
+
});
|
|
563
|
+
},
|
|
564
|
+
decorationNode,
|
|
565
|
+
clientRect,
|
|
566
|
+
loading: willFetch,
|
|
567
|
+
placement,
|
|
568
|
+
offset: { mainAxis: (_a = offsetOption.mainAxis) != null ? _a : 4, crossAxis: (_b = offsetOption.crossAxis) != null ? _b : 0 },
|
|
569
|
+
container,
|
|
570
|
+
flip,
|
|
571
|
+
floatingUi: floatingUiConfig,
|
|
572
|
+
mount: createMount({
|
|
573
|
+
getReferenceRect: clientRect,
|
|
574
|
+
contextElement: view.dom,
|
|
575
|
+
config: floatingUiConfig,
|
|
576
|
+
container,
|
|
577
|
+
dismissOnOutsideClick,
|
|
578
|
+
dismiss: () => dispatchExit2(editor.view)
|
|
579
|
+
})
|
|
580
|
+
};
|
|
581
|
+
if (currentState === "started") {
|
|
582
|
+
(_c = renderer == null ? void 0 : renderer.onBeforeStart) == null ? void 0 : _c.call(renderer, props);
|
|
583
|
+
}
|
|
584
|
+
if (currentState === "updated") {
|
|
585
|
+
(_d = renderer == null ? void 0 : renderer.onBeforeUpdate) == null ? void 0 : _d.call(renderer, props);
|
|
586
|
+
}
|
|
587
|
+
if (currentState === "started") {
|
|
588
|
+
dispatchStateUpdate(currentState, props);
|
|
589
|
+
}
|
|
590
|
+
if (currentState === "started" || currentState === "updated") {
|
|
591
|
+
if (!willFetch) {
|
|
592
|
+
asyncRequest.abort();
|
|
593
|
+
props = { ...props, items: initialItems != null ? initialItems : [], loading: false };
|
|
594
|
+
} else {
|
|
595
|
+
props = { ...props, items: initialItems != null ? initialItems : [], loading: true };
|
|
596
|
+
currentState = "updated";
|
|
597
|
+
dispatchStateUpdate(currentState, props);
|
|
598
|
+
const result = await asyncRequest.fetch(state.query || "", debounce);
|
|
599
|
+
if (result.status === "aborted") {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const currentPluginState = pluginKey.getState(view.state);
|
|
603
|
+
if (!(currentPluginState == null ? void 0 : currentPluginState.active)) {
|
|
604
|
+
asyncRequest.abort();
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
props = result.status === "resolved" ? {
|
|
608
|
+
...props,
|
|
609
|
+
items: result.items,
|
|
610
|
+
loading: false
|
|
611
|
+
} : {
|
|
612
|
+
...props,
|
|
613
|
+
loading: false
|
|
614
|
+
};
|
|
360
615
|
}
|
|
361
|
-
return DecorationSet.create(state.doc, [
|
|
362
|
-
Decoration.inline(range.from, range.to, {
|
|
363
|
-
nodeName: decorationTag,
|
|
364
|
-
class: classNames.join(" "),
|
|
365
|
-
"data-decoration-id": decorationId,
|
|
366
|
-
"data-decoration-content": decorationContent
|
|
367
|
-
})
|
|
368
|
-
]);
|
|
369
616
|
}
|
|
617
|
+
if (currentState === "stopped") {
|
|
618
|
+
asyncRequest.abort();
|
|
619
|
+
dispatchStateUpdate(currentState, props);
|
|
620
|
+
props = void 0;
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (currentState === "updated") {
|
|
624
|
+
dispatchStateUpdate(currentState, props);
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
destroy: () => {
|
|
628
|
+
var _a;
|
|
629
|
+
asyncRequest.abort();
|
|
630
|
+
if (!props) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
(_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, props);
|
|
370
634
|
}
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/suggestion.ts
|
|
639
|
+
var SuggestionPluginKey = new PluginKey("suggestion");
|
|
640
|
+
function Suggestion({
|
|
641
|
+
pluginKey = SuggestionPluginKey,
|
|
642
|
+
editor,
|
|
643
|
+
char = "@",
|
|
644
|
+
allowSpaces = false,
|
|
645
|
+
allowToIncludeChar = false,
|
|
646
|
+
allowedPrefixes = [" "],
|
|
647
|
+
startOfLine = false,
|
|
648
|
+
decorationTag = "span",
|
|
649
|
+
decorationClass = "suggestion",
|
|
650
|
+
decorationContent = "",
|
|
651
|
+
decorationEmptyClass = "is-empty",
|
|
652
|
+
command = () => null,
|
|
653
|
+
items = () => [],
|
|
654
|
+
minQueryLength = 0,
|
|
655
|
+
debounce = 0,
|
|
656
|
+
initialItems,
|
|
657
|
+
placement = "bottom-start",
|
|
658
|
+
offset: offsetOption = {},
|
|
659
|
+
container,
|
|
660
|
+
flip = true,
|
|
661
|
+
floatingUi,
|
|
662
|
+
dismissOnOutsideClick = true,
|
|
663
|
+
render = () => ({}),
|
|
664
|
+
allow = () => true,
|
|
665
|
+
findSuggestionMatch: findSuggestionMatch2 = findSuggestionMatch,
|
|
666
|
+
shouldShow,
|
|
667
|
+
shouldResetDismissed
|
|
668
|
+
}) {
|
|
669
|
+
const renderer = render == null ? void 0 : render();
|
|
670
|
+
const effectiveAllowSpaces = allowSpaces && !allowToIncludeChar;
|
|
671
|
+
const clientRectFor2 = (view, decorationNode) => clientRectFor(editor, view, decorationNode, pluginKey);
|
|
672
|
+
function shouldKeepDismissed2(props) {
|
|
673
|
+
return shouldKeepDismissed({
|
|
674
|
+
...props,
|
|
675
|
+
editor,
|
|
676
|
+
shouldResetDismissed,
|
|
677
|
+
effectiveAllowSpaces
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
const dispatchExit2 = (view) => dispatchExit({
|
|
681
|
+
view,
|
|
682
|
+
pluginKeyRef: pluginKey
|
|
683
|
+
});
|
|
684
|
+
return new Plugin({
|
|
685
|
+
key: pluginKey,
|
|
686
|
+
view: () => createSuggestionView({
|
|
687
|
+
editor,
|
|
688
|
+
pluginKey,
|
|
689
|
+
items,
|
|
690
|
+
renderer,
|
|
691
|
+
minQueryLength,
|
|
692
|
+
debounce,
|
|
693
|
+
initialItems,
|
|
694
|
+
placement,
|
|
695
|
+
offset: offsetOption,
|
|
696
|
+
container,
|
|
697
|
+
flip,
|
|
698
|
+
floatingUi,
|
|
699
|
+
dismissOnOutsideClick,
|
|
700
|
+
command,
|
|
701
|
+
clientRectFor: clientRectFor2,
|
|
702
|
+
dispatchExit: dispatchExit2
|
|
703
|
+
}),
|
|
704
|
+
state: createSuggestionState({
|
|
705
|
+
editor,
|
|
706
|
+
char,
|
|
707
|
+
effectiveAllowSpaces,
|
|
708
|
+
allowToIncludeChar,
|
|
709
|
+
allowedPrefixes,
|
|
710
|
+
startOfLine,
|
|
711
|
+
findSuggestionMatch: findSuggestionMatch2,
|
|
712
|
+
allow,
|
|
713
|
+
shouldShow,
|
|
714
|
+
shouldKeepDismissed: shouldKeepDismissed2,
|
|
715
|
+
pluginKey
|
|
716
|
+
}),
|
|
717
|
+
props: createSuggestionProps({
|
|
718
|
+
pluginKey,
|
|
719
|
+
decorationTag,
|
|
720
|
+
decorationClass,
|
|
721
|
+
decorationContent,
|
|
722
|
+
decorationEmptyClass,
|
|
723
|
+
renderer,
|
|
724
|
+
dispatchExit: dispatchExit2
|
|
725
|
+
})
|
|
371
726
|
});
|
|
372
|
-
return plugin;
|
|
373
727
|
}
|
|
374
728
|
function exitSuggestion(view, pluginKeyRef = SuggestionPluginKey) {
|
|
375
729
|
const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true });
|