@tiptap/suggestion 3.20.1 → 3.20.3
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 +5 -5
- package/dist/index.cjs +0 -369
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -213
- package/dist/index.d.ts +0 -213
- package/dist/index.js +0 -339
- package/dist/index.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiptap/suggestion",
|
|
3
3
|
"description": "suggestion plugin for tiptap",
|
|
4
|
-
"version": "3.20.
|
|
4
|
+
"version": "3.20.3",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"dist"
|
|
32
32
|
],
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@tiptap/core": "^3.20.
|
|
35
|
-
"@tiptap/pm": "^3.20.
|
|
34
|
+
"@tiptap/core": "^3.20.3",
|
|
35
|
+
"@tiptap/pm": "^3.20.3"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@tiptap/core": "^3.20.
|
|
39
|
-
"@tiptap/pm": "^3.20.
|
|
38
|
+
"@tiptap/core": "^3.20.3",
|
|
39
|
+
"@tiptap/pm": "^3.20.3"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|
package/dist/index.cjs
DELETED
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
Suggestion: () => Suggestion,
|
|
24
|
-
SuggestionPluginKey: () => SuggestionPluginKey,
|
|
25
|
-
default: () => index_default,
|
|
26
|
-
exitSuggestion: () => exitSuggestion,
|
|
27
|
-
findSuggestionMatch: () => findSuggestionMatch
|
|
28
|
-
});
|
|
29
|
-
module.exports = __toCommonJS(index_exports);
|
|
30
|
-
|
|
31
|
-
// src/suggestion.ts
|
|
32
|
-
var import_state = require("@tiptap/pm/state");
|
|
33
|
-
var import_view = require("@tiptap/pm/view");
|
|
34
|
-
|
|
35
|
-
// src/findSuggestionMatch.ts
|
|
36
|
-
var import_core = require("@tiptap/core");
|
|
37
|
-
function findSuggestionMatch(config) {
|
|
38
|
-
var _a;
|
|
39
|
-
const { char, allowSpaces: allowSpacesOption, allowToIncludeChar, allowedPrefixes, startOfLine, $position } = config;
|
|
40
|
-
const allowSpaces = allowSpacesOption && !allowToIncludeChar;
|
|
41
|
-
const escapedChar = (0, import_core.escapeForRegEx)(char);
|
|
42
|
-
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
43
|
-
const prefix = startOfLine ? "^" : "";
|
|
44
|
-
const finalEscapedChar = allowToIncludeChar ? "" : escapedChar;
|
|
45
|
-
const regexp = allowSpaces ? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${finalEscapedChar}|$)`, "gm") : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${finalEscapedChar}]*`, "gm");
|
|
46
|
-
const text = ((_a = $position.nodeBefore) == null ? void 0 : _a.isText) && $position.nodeBefore.text;
|
|
47
|
-
if (!text) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
const textFrom = $position.pos - text.length;
|
|
51
|
-
const match = Array.from(text.matchAll(regexp)).pop();
|
|
52
|
-
if (!match || match.input === void 0 || match.index === void 0) {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
|
|
56
|
-
const matchPrefixIsAllowed = new RegExp(`^[${allowedPrefixes == null ? void 0 : allowedPrefixes.join("")}\0]?$`).test(matchPrefix);
|
|
57
|
-
if (allowedPrefixes !== null && !matchPrefixIsAllowed) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
const from = textFrom + match.index;
|
|
61
|
-
let to = from + match[0].length;
|
|
62
|
-
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
|
63
|
-
match[0] += " ";
|
|
64
|
-
to += 1;
|
|
65
|
-
}
|
|
66
|
-
if (from < $position.pos && to >= $position.pos) {
|
|
67
|
-
return {
|
|
68
|
-
range: {
|
|
69
|
-
from,
|
|
70
|
-
to
|
|
71
|
-
},
|
|
72
|
-
query: match[0].slice(char.length),
|
|
73
|
-
text: match[0]
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// src/suggestion.ts
|
|
80
|
-
var SuggestionPluginKey = new import_state.PluginKey("suggestion");
|
|
81
|
-
function Suggestion({
|
|
82
|
-
pluginKey = SuggestionPluginKey,
|
|
83
|
-
editor,
|
|
84
|
-
char = "@",
|
|
85
|
-
allowSpaces = false,
|
|
86
|
-
allowToIncludeChar = false,
|
|
87
|
-
allowedPrefixes = [" "],
|
|
88
|
-
startOfLine = false,
|
|
89
|
-
decorationTag = "span",
|
|
90
|
-
decorationClass = "suggestion",
|
|
91
|
-
decorationContent = "",
|
|
92
|
-
decorationEmptyClass = "is-empty",
|
|
93
|
-
command = () => null,
|
|
94
|
-
items = () => [],
|
|
95
|
-
render = () => ({}),
|
|
96
|
-
allow = () => true,
|
|
97
|
-
findSuggestionMatch: findSuggestionMatch2 = findSuggestionMatch,
|
|
98
|
-
shouldShow
|
|
99
|
-
}) {
|
|
100
|
-
let props;
|
|
101
|
-
const renderer = render == null ? void 0 : render();
|
|
102
|
-
const getAnchorClientRect = () => {
|
|
103
|
-
const pos = editor.state.selection.$anchor.pos;
|
|
104
|
-
const coords = editor.view.coordsAtPos(pos);
|
|
105
|
-
const { top, right, bottom, left } = coords;
|
|
106
|
-
try {
|
|
107
|
-
return new DOMRect(left, top, right - left, bottom - top);
|
|
108
|
-
} catch {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
const clientRectFor = (view, decorationNode) => {
|
|
113
|
-
if (!decorationNode) {
|
|
114
|
-
return getAnchorClientRect;
|
|
115
|
-
}
|
|
116
|
-
return () => {
|
|
117
|
-
const state = pluginKey.getState(editor.state);
|
|
118
|
-
const decorationId = state == null ? void 0 : state.decorationId;
|
|
119
|
-
const currentDecorationNode = view.dom.querySelector(`[data-decoration-id="${decorationId}"]`);
|
|
120
|
-
return (currentDecorationNode == null ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
|
|
121
|
-
};
|
|
122
|
-
};
|
|
123
|
-
function dispatchExit(view, pluginKeyRef) {
|
|
124
|
-
var _a;
|
|
125
|
-
try {
|
|
126
|
-
const state = pluginKey.getState(view.state);
|
|
127
|
-
const decorationNode = (state == null ? void 0 : state.decorationId) ? view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`) : null;
|
|
128
|
-
const exitProps = {
|
|
129
|
-
// @ts-ignore editor is available in closure
|
|
130
|
-
editor,
|
|
131
|
-
range: (state == null ? void 0 : state.range) || { from: 0, to: 0 },
|
|
132
|
-
query: (state == null ? void 0 : state.query) || null,
|
|
133
|
-
text: (state == null ? void 0 : state.text) || null,
|
|
134
|
-
items: [],
|
|
135
|
-
command: (commandProps) => {
|
|
136
|
-
return command({ editor, range: (state == null ? void 0 : state.range) || { from: 0, to: 0 }, props: commandProps });
|
|
137
|
-
},
|
|
138
|
-
decorationNode,
|
|
139
|
-
clientRect: clientRectFor(view, decorationNode)
|
|
140
|
-
};
|
|
141
|
-
(_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, exitProps);
|
|
142
|
-
} catch {
|
|
143
|
-
}
|
|
144
|
-
const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true });
|
|
145
|
-
view.dispatch(tr);
|
|
146
|
-
}
|
|
147
|
-
const plugin = new import_state.Plugin({
|
|
148
|
-
key: pluginKey,
|
|
149
|
-
view() {
|
|
150
|
-
return {
|
|
151
|
-
update: async (view, prevState) => {
|
|
152
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
153
|
-
const prev = (_a = this.key) == null ? void 0 : _a.getState(prevState);
|
|
154
|
-
const next = (_b = this.key) == null ? void 0 : _b.getState(view.state);
|
|
155
|
-
const moved = prev.active && next.active && prev.range.from !== next.range.from;
|
|
156
|
-
const started = !prev.active && next.active;
|
|
157
|
-
const stopped = prev.active && !next.active;
|
|
158
|
-
const changed = !started && !stopped && prev.query !== next.query;
|
|
159
|
-
const handleStart = started || moved && changed;
|
|
160
|
-
const handleChange = changed || moved;
|
|
161
|
-
const handleExit = stopped || moved && changed;
|
|
162
|
-
if (!handleStart && !handleChange && !handleExit) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const state = handleExit && !handleStart ? prev : next;
|
|
166
|
-
const decorationNode = view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
167
|
-
props = {
|
|
168
|
-
editor,
|
|
169
|
-
range: state.range,
|
|
170
|
-
query: state.query,
|
|
171
|
-
text: state.text,
|
|
172
|
-
items: [],
|
|
173
|
-
command: (commandProps) => {
|
|
174
|
-
return command({
|
|
175
|
-
editor,
|
|
176
|
-
range: state.range,
|
|
177
|
-
props: commandProps
|
|
178
|
-
});
|
|
179
|
-
},
|
|
180
|
-
decorationNode,
|
|
181
|
-
clientRect: clientRectFor(view, decorationNode)
|
|
182
|
-
};
|
|
183
|
-
if (handleStart) {
|
|
184
|
-
(_c = renderer == null ? void 0 : renderer.onBeforeStart) == null ? void 0 : _c.call(renderer, props);
|
|
185
|
-
}
|
|
186
|
-
if (handleChange) {
|
|
187
|
-
(_d = renderer == null ? void 0 : renderer.onBeforeUpdate) == null ? void 0 : _d.call(renderer, props);
|
|
188
|
-
}
|
|
189
|
-
if (handleChange || handleStart) {
|
|
190
|
-
props.items = await items({
|
|
191
|
-
editor,
|
|
192
|
-
query: state.query
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
if (handleExit) {
|
|
196
|
-
(_e = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _e.call(renderer, props);
|
|
197
|
-
}
|
|
198
|
-
if (handleChange) {
|
|
199
|
-
(_f = renderer == null ? void 0 : renderer.onUpdate) == null ? void 0 : _f.call(renderer, props);
|
|
200
|
-
}
|
|
201
|
-
if (handleStart) {
|
|
202
|
-
(_g = renderer == null ? void 0 : renderer.onStart) == null ? void 0 : _g.call(renderer, props);
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
destroy: () => {
|
|
206
|
-
var _a;
|
|
207
|
-
if (!props) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
(_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, props);
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
},
|
|
214
|
-
state: {
|
|
215
|
-
// Initialize the plugin's internal state.
|
|
216
|
-
init() {
|
|
217
|
-
const state = {
|
|
218
|
-
active: false,
|
|
219
|
-
range: {
|
|
220
|
-
from: 0,
|
|
221
|
-
to: 0
|
|
222
|
-
},
|
|
223
|
-
query: null,
|
|
224
|
-
text: null,
|
|
225
|
-
composing: false
|
|
226
|
-
};
|
|
227
|
-
return state;
|
|
228
|
-
},
|
|
229
|
-
// Apply changes to the plugin state from a view transaction.
|
|
230
|
-
apply(transaction, prev, _oldState, state) {
|
|
231
|
-
const { isEditable } = editor;
|
|
232
|
-
const { composing } = editor.view;
|
|
233
|
-
const { selection } = transaction;
|
|
234
|
-
const { empty, from } = selection;
|
|
235
|
-
const next = { ...prev };
|
|
236
|
-
const meta = transaction.getMeta(pluginKey);
|
|
237
|
-
if (meta && meta.exit) {
|
|
238
|
-
next.active = false;
|
|
239
|
-
next.decorationId = null;
|
|
240
|
-
next.range = { from: 0, to: 0 };
|
|
241
|
-
next.query = null;
|
|
242
|
-
next.text = null;
|
|
243
|
-
return next;
|
|
244
|
-
}
|
|
245
|
-
next.composing = composing;
|
|
246
|
-
if (isEditable && (empty || editor.view.composing)) {
|
|
247
|
-
if ((from < prev.range.from || from > prev.range.to) && !composing && !prev.composing) {
|
|
248
|
-
next.active = false;
|
|
249
|
-
}
|
|
250
|
-
const match = findSuggestionMatch2({
|
|
251
|
-
char,
|
|
252
|
-
allowSpaces,
|
|
253
|
-
allowToIncludeChar,
|
|
254
|
-
allowedPrefixes,
|
|
255
|
-
startOfLine,
|
|
256
|
-
$position: selection.$from
|
|
257
|
-
});
|
|
258
|
-
const decorationId = `id_${Math.floor(Math.random() * 4294967295)}`;
|
|
259
|
-
if (match && allow({
|
|
260
|
-
editor,
|
|
261
|
-
state,
|
|
262
|
-
range: match.range,
|
|
263
|
-
isActive: prev.active
|
|
264
|
-
}) && (!shouldShow || shouldShow({
|
|
265
|
-
editor,
|
|
266
|
-
range: match.range,
|
|
267
|
-
query: match.query,
|
|
268
|
-
text: match.text,
|
|
269
|
-
transaction
|
|
270
|
-
}))) {
|
|
271
|
-
next.active = true;
|
|
272
|
-
next.decorationId = prev.decorationId ? prev.decorationId : decorationId;
|
|
273
|
-
next.range = match.range;
|
|
274
|
-
next.query = match.query;
|
|
275
|
-
next.text = match.text;
|
|
276
|
-
} else {
|
|
277
|
-
next.active = false;
|
|
278
|
-
}
|
|
279
|
-
} else {
|
|
280
|
-
next.active = false;
|
|
281
|
-
}
|
|
282
|
-
if (!next.active) {
|
|
283
|
-
next.decorationId = null;
|
|
284
|
-
next.range = { from: 0, to: 0 };
|
|
285
|
-
next.query = null;
|
|
286
|
-
next.text = null;
|
|
287
|
-
}
|
|
288
|
-
return next;
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
props: {
|
|
292
|
-
// Call the keydown hook if suggestion is active.
|
|
293
|
-
handleKeyDown(view, event) {
|
|
294
|
-
var _a, _b, _c, _d;
|
|
295
|
-
const { active, range } = plugin.getState(view.state);
|
|
296
|
-
if (!active) {
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
if (event.key === "Escape" || event.key === "Esc") {
|
|
300
|
-
const state = plugin.getState(view.state);
|
|
301
|
-
const cachedNode = (_a = props == null ? void 0 : props.decorationNode) != null ? _a : null;
|
|
302
|
-
const decorationNode = cachedNode != null ? cachedNode : (state == null ? void 0 : state.decorationId) ? view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`) : null;
|
|
303
|
-
const handledByKeyDown = ((_b = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _b.call(renderer, { view, event, range: state.range })) || false;
|
|
304
|
-
if (handledByKeyDown) {
|
|
305
|
-
return true;
|
|
306
|
-
}
|
|
307
|
-
const exitProps = {
|
|
308
|
-
editor,
|
|
309
|
-
range: state.range,
|
|
310
|
-
query: state.query,
|
|
311
|
-
text: state.text,
|
|
312
|
-
items: [],
|
|
313
|
-
command: (commandProps) => {
|
|
314
|
-
return command({ editor, range: state.range, props: commandProps });
|
|
315
|
-
},
|
|
316
|
-
decorationNode,
|
|
317
|
-
// If we have a cached decoration node, use it for the clientRect
|
|
318
|
-
// to avoid another DOM lookup. If not, leave clientRect null and
|
|
319
|
-
// let consumer decide if they want to query.
|
|
320
|
-
clientRect: decorationNode ? () => {
|
|
321
|
-
return decorationNode.getBoundingClientRect() || null;
|
|
322
|
-
} : null
|
|
323
|
-
};
|
|
324
|
-
(_c = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _c.call(renderer, exitProps);
|
|
325
|
-
dispatchExit(view, pluginKey);
|
|
326
|
-
return true;
|
|
327
|
-
}
|
|
328
|
-
const handled = ((_d = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _d.call(renderer, { view, event, range })) || false;
|
|
329
|
-
return handled;
|
|
330
|
-
},
|
|
331
|
-
// Setup decorator on the currently active suggestion.
|
|
332
|
-
decorations(state) {
|
|
333
|
-
const { active, range, decorationId, query } = plugin.getState(state);
|
|
334
|
-
if (!active) {
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
const isEmpty = !(query == null ? void 0 : query.length);
|
|
338
|
-
const classNames = [decorationClass];
|
|
339
|
-
if (isEmpty) {
|
|
340
|
-
classNames.push(decorationEmptyClass);
|
|
341
|
-
}
|
|
342
|
-
return import_view.DecorationSet.create(state.doc, [
|
|
343
|
-
import_view.Decoration.inline(range.from, range.to, {
|
|
344
|
-
nodeName: decorationTag,
|
|
345
|
-
class: classNames.join(" "),
|
|
346
|
-
"data-decoration-id": decorationId,
|
|
347
|
-
"data-decoration-content": decorationContent
|
|
348
|
-
})
|
|
349
|
-
]);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
return plugin;
|
|
354
|
-
}
|
|
355
|
-
function exitSuggestion(view, pluginKeyRef = SuggestionPluginKey) {
|
|
356
|
-
const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true });
|
|
357
|
-
view.dispatch(tr);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// src/index.ts
|
|
361
|
-
var index_default = Suggestion;
|
|
362
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
363
|
-
0 && (module.exports = {
|
|
364
|
-
Suggestion,
|
|
365
|
-
SuggestionPluginKey,
|
|
366
|
-
exitSuggestion,
|
|
367
|
-
findSuggestionMatch
|
|
368
|
-
});
|
|
369
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/suggestion.ts","../src/findSuggestionMatch.ts"],"sourcesContent":["import { exitSuggestion, Suggestion } from './suggestion.js'\n\nexport * from './findSuggestionMatch.js'\nexport * from './suggestion.js'\n\nexport { exitSuggestion }\n\nexport default Suggestion\n","import type { Editor, Range } from '@tiptap/core'\nimport type { EditorState, Transaction } from '@tiptap/pm/state'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nimport { findSuggestionMatch as defaultFindSuggestionMatch } from './findSuggestionMatch.js'\n\nexport interface SuggestionOptions<I = any, TSelected = any> {\n /**\n * The plugin key for the suggestion plugin.\n * @default 'suggestion'\n * @example 'mention'\n */\n pluginKey?: PluginKey\n\n /**\n * A function that returns a boolean to indicate if the suggestion should be active.\n * This is useful to prevent suggestions from opening for remote users in collaborative environments.\n * @param props The props object.\n * @param props.editor The editor instance.\n * @param props.range The range of the suggestion.\n * @param props.query The current suggestion query.\n * @param props.text The current suggestion text.\n * @param props.transaction The current transaction.\n * @returns {boolean}\n * @example ({ transaction }) => isChangeOrigin(transaction)\n */\n shouldShow?: (props: {\n editor: Editor\n range: Range\n query: string\n text: string\n transaction: Transaction\n }) => boolean\n\n /**\n * The editor instance.\n * @default null\n */\n editor: Editor\n\n /**\n * The character that triggers the suggestion.\n * @default '@'\n * @example '#'\n */\n char?: string\n\n /**\n * Allow spaces in the suggestion query. Not compatible with `allowToIncludeChar`. Will be disabled if `allowToIncludeChar` is set to `true`.\n * @default false\n * @example true\n */\n allowSpaces?: boolean\n\n /**\n * Allow the character to be included in the suggestion query. Not compatible with `allowSpaces`.\n * @default false\n */\n allowToIncludeChar?: boolean\n\n /**\n * Allow prefixes in the suggestion query.\n * @default [' ']\n * @example [' ', '@']\n */\n allowedPrefixes?: string[] | null\n\n /**\n * Only match suggestions at the start of the line.\n * @default false\n * @example true\n */\n startOfLine?: boolean\n\n /**\n * The tag name of the decoration node.\n * @default 'span'\n * @example 'div'\n */\n decorationTag?: string\n\n /**\n * The class name of the decoration node.\n * @default 'suggestion'\n * @example 'mention'\n */\n decorationClass?: string\n\n /**\n * Creates a decoration with the provided content.\n * @param decorationContent - The content to display in the decoration\n * @default \"\" - Creates an empty decoration if no content provided\n */\n decorationContent?: string\n\n /**\n * The class name of the decoration node when it is empty.\n * @default 'is-empty'\n * @example 'is-empty'\n */\n decorationEmptyClass?: string\n\n /**\n * A function that is called when a suggestion is selected.\n * @param props The props object.\n * @param props.editor The editor instance.\n * @param props.range The range of the suggestion.\n * @param props.props The props of the selected suggestion.\n * @returns void\n * @example ({ editor, range, props }) => { props.command(props.props) }\n */\n command?: (props: { editor: Editor; range: Range; props: TSelected }) => void\n\n /**\n * A function that returns the suggestion items in form of an array.\n * @param props The props object.\n * @param props.editor The editor instance.\n * @param props.query The current suggestion query.\n * @returns An array of suggestion items.\n * @example ({ editor, query }) => [{ id: 1, label: 'John Doe' }]\n */\n items?: (props: { query: string; editor: Editor }) => I[] | Promise<I[]>\n\n /**\n * The render function for the suggestion.\n * @returns An object with render functions.\n */\n render?: () => {\n onBeforeStart?: (props: SuggestionProps<I, TSelected>) => void\n onStart?: (props: SuggestionProps<I, TSelected>) => void\n onBeforeUpdate?: (props: SuggestionProps<I, TSelected>) => void\n onUpdate?: (props: SuggestionProps<I, TSelected>) => void\n onExit?: (props: SuggestionProps<I, TSelected>) => void\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean\n }\n\n /**\n * A function that returns a boolean to indicate if the suggestion should be active.\n * @param props The props object.\n * @returns {boolean}\n */\n allow?: (props: { editor: Editor; state: EditorState; range: Range; isActive?: boolean }) => boolean\n findSuggestionMatch?: typeof defaultFindSuggestionMatch\n}\n\nexport interface SuggestionProps<I = any, TSelected = any> {\n /**\n * The editor instance.\n */\n editor: Editor\n\n /**\n * The range of the suggestion.\n */\n range: Range\n\n /**\n * The current suggestion query.\n */\n query: string\n\n /**\n * The current suggestion text.\n */\n text: string\n\n /**\n * The suggestion items array.\n */\n items: I[]\n\n /**\n * A function that is called when a suggestion is selected.\n * @param props The props object.\n * @returns void\n */\n command: (props: TSelected) => void\n\n /**\n * The decoration node HTML element\n * @default null\n */\n decorationNode: Element | null\n\n /**\n * The function that returns the client rect\n * @default null\n * @example () => new DOMRect(0, 0, 0, 0)\n */\n clientRect?: (() => DOMRect | null) | null\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView\n event: KeyboardEvent\n range: Range\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\n/**\n * This utility allows you to create suggestions.\n * @see https://tiptap.dev/api/utilities/suggestion\n */\nexport function Suggestion<I = any, TSelected = any>({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n allowToIncludeChar = false,\n allowedPrefixes = [' '],\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n decorationContent = '',\n decorationEmptyClass = 'is-empty',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n findSuggestionMatch = defaultFindSuggestionMatch,\n shouldShow,\n}: SuggestionOptions<I, TSelected>) {\n let props: SuggestionProps<I, TSelected> | undefined\n const renderer = render?.()\n\n // Gets the DOM rectangle corresponding to the current editor cursor anchor position\n // Calculates screen coordinates based on Tiptap's cursor position and converts to a DOMRect object\n const getAnchorClientRect = () => {\n const pos = editor.state.selection.$anchor.pos\n const coords = editor.view.coordsAtPos(pos)\n const { top, right, bottom, left } = coords\n\n try {\n return new DOMRect(left, top, right - left, bottom - top)\n } catch {\n return null\n }\n }\n\n // Helper to create a clientRect callback for a given decoration node.\n // Returns null when no decoration node is present. Uses the pluginKey's\n // state to resolve the current decoration node on demand, avoiding a\n // duplicated implementation in multiple places.\n const clientRectFor = (view: EditorView, decorationNode: Element | null) => {\n if (!decorationNode) {\n return getAnchorClientRect\n }\n\n return () => {\n const state = pluginKey.getState(editor.state)\n const decorationId = state?.decorationId\n const currentDecorationNode = view.dom.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n return currentDecorationNode?.getBoundingClientRect() || null\n }\n }\n // small helper used internally by the view to dispatch an exit\n function dispatchExit(view: EditorView, pluginKeyRef: PluginKey) {\n try {\n const state = pluginKey.getState(view.state)\n const decorationNode = state?.decorationId\n ? view.dom.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n : null\n\n const exitProps: SuggestionProps = {\n // @ts-ignore editor is available in closure\n editor,\n range: state?.range || { from: 0, to: 0 },\n query: state?.query || null,\n text: state?.text || null,\n items: [],\n command: commandProps => {\n return command({ editor, range: state?.range || { from: 0, to: 0 }, props: commandProps as any })\n },\n decorationNode,\n clientRect: clientRectFor(view, decorationNode),\n }\n\n renderer?.onExit?.(exitProps)\n } catch {\n // ignore errors from consumer renderers\n }\n\n const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true })\n // Dispatch a metadata-only transaction to signal the plugin to exit\n view.dispatch(tr)\n }\n\n const plugin: Plugin<any> = new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n\n const handleStart = started || (moved && changed)\n const handleChange = changed || moved\n const handleExit = stopped || (moved && changed)\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart ? prev : next\n const decorationNode = view.dom.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: [],\n command: commandProps => {\n return command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n clientRect: clientRectFor(view, decorationNode),\n }\n\n if (handleStart) {\n renderer?.onBeforeStart?.(props)\n }\n\n if (handleChange) {\n renderer?.onBeforeUpdate?.(props)\n }\n\n if (handleChange || handleStart) {\n props.items = await items({\n editor,\n query: state.query,\n })\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n const state: {\n active: boolean\n range: Range\n query: null | string\n text: null | string\n composing: boolean\n decorationId?: string | null\n } = {\n active: false,\n range: {\n from: 0,\n to: 0,\n },\n query: null,\n text: null,\n composing: false,\n }\n\n return state\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev, _oldState, state) {\n const { isEditable } = editor\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n // If a transaction carries the exit meta for this plugin, immediately\n // deactivate the suggestion. This allows metadata-only transactions\n // (dispatched by escape or programmatic exit) to deterministically\n // clear decorations without changing the document.\n const meta = transaction.getMeta(pluginKey)\n if (meta && meta.exit) {\n next.active = false\n next.decorationId = null\n next.range = { from: 0, to: 0 }\n next.query = null\n next.text = null\n\n return next\n }\n\n next.composing = composing\n\n // We can only be suggesting if the view is editable, and:\n // * there is no selection, or\n // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (isEditable && (empty || editor.view.composing)) {\n // Reset active state if we just left the previous suggestion range\n if ((from < prev.range.from || from > prev.range.to) && !composing && !prev.composing) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n allowToIncludeChar,\n allowedPrefixes,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xffffffff)}`\n\n // If we found a match, update the current state to show it\n if (\n match &&\n allow({\n editor,\n state,\n range: match.range,\n isActive: prev.active,\n }) &&\n (!shouldShow ||\n shouldShow({\n editor,\n range: match.range,\n query: match.query,\n text: match.text,\n transaction,\n }))\n ) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = { from: 0, to: 0 }\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = plugin.getState(view.state)\n\n if (!active) {\n return false\n }\n\n // If Escape is pressed, call onExit and dispatch a metadata-only\n // transaction to unset the suggestion state. This provides a safe\n // and deterministic way to exit the suggestion without altering the\n // document (avoids transaction mapping/mismatch issues).\n if (event.key === 'Escape' || event.key === 'Esc') {\n const state = plugin.getState(view.state)\n const cachedNode = props?.decorationNode ?? null\n const decorationNode =\n cachedNode ??\n (state?.decorationId ? view.dom.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`) : null)\n\n // Give the consumer a chance to handle Escape via onKeyDown first.\n // If the consumer returns `true` we assume they handled the event and\n // we won't call onExit/dispatchExit so they can both prevent\n // propagation and decide whether to close the suggestion themselves.\n const handledByKeyDown = renderer?.onKeyDown?.({ view, event, range: state.range }) || false\n\n if (handledByKeyDown) {\n return true\n }\n\n const exitProps: SuggestionProps = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: [],\n command: commandProps => {\n return command({ editor, range: state.range, props: commandProps as any })\n },\n decorationNode,\n // If we have a cached decoration node, use it for the clientRect\n // to avoid another DOM lookup. If not, leave clientRect null and\n // let consumer decide if they want to query.\n clientRect: decorationNode\n ? () => {\n return decorationNode.getBoundingClientRect() || null\n }\n : null,\n }\n\n renderer?.onExit?.(exitProps)\n\n // dispatch metadata-only transaction to unset the plugin state\n dispatchExit(view, pluginKey)\n\n return true\n }\n\n const handled = renderer?.onKeyDown?.({ view, event, range }) || false\n return handled\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId, query } = plugin.getState(state)\n\n if (!active) {\n return null\n }\n\n const isEmpty = !query?.length\n const classNames = [decorationClass]\n\n if (isEmpty) {\n classNames.push(decorationEmptyClass)\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: classNames.join(' '),\n 'data-decoration-id': decorationId,\n 'data-decoration-content': decorationContent,\n }),\n ])\n },\n },\n })\n\n return plugin\n}\n\n/**\n * Programmatically exit a suggestion plugin by dispatching a metadata-only\n * transaction. This is the safe, recommended API to remove suggestion\n * decorations without touching the document or causing mapping errors.\n */\nexport function exitSuggestion(view: EditorView, pluginKeyRef: PluginKey = SuggestionPluginKey) {\n const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true })\n view.dispatch(tr)\n}\n","import type { Range } from '@tiptap/core'\nimport { escapeForRegEx } from '@tiptap/core'\nimport type { ResolvedPos } from '@tiptap/pm/model'\n\nexport interface Trigger {\n char: string\n allowSpaces: boolean\n allowToIncludeChar: boolean\n allowedPrefixes: string[] | null\n startOfLine: boolean\n $position: ResolvedPos\n}\n\nexport type SuggestionMatch = {\n range: Range\n query: string\n text: string\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const { char, allowSpaces: allowSpacesOption, allowToIncludeChar, allowedPrefixes, startOfLine, $position } = config\n\n const allowSpaces = allowSpacesOption && !allowToIncludeChar\n\n const escapedChar = escapeForRegEx(char)\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const finalEscapedChar = allowToIncludeChar ? '' : escapedChar\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${finalEscapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${finalEscapedChar}]*`, 'gm')\n\n const text = $position.nodeBefore?.isText && $position.nodeBefore.text\n\n if (!text) {\n return null\n }\n\n const textFrom = $position.pos - text.length\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsAllowed = new RegExp(`^[${allowedPrefixes?.join('')}\\0]?$`).test(matchPrefix)\n\n if (allowedPrefixes !== null && !matchPrefixIsAllowed) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = textFrom + match.index\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n"],"mappings":"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","names":["findSuggestionMatch"]}
|
package/dist/index.d.cts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { Range, Editor } from '@tiptap/core';
|
|
2
|
-
import { PluginKey, Transaction, EditorState, Plugin } from '@tiptap/pm/state';
|
|
3
|
-
import { EditorView } from '@tiptap/pm/view';
|
|
4
|
-
import { ResolvedPos } from '@tiptap/pm/model';
|
|
5
|
-
|
|
6
|
-
interface Trigger {
|
|
7
|
-
char: string;
|
|
8
|
-
allowSpaces: boolean;
|
|
9
|
-
allowToIncludeChar: boolean;
|
|
10
|
-
allowedPrefixes: string[] | null;
|
|
11
|
-
startOfLine: boolean;
|
|
12
|
-
$position: ResolvedPos;
|
|
13
|
-
}
|
|
14
|
-
type SuggestionMatch = {
|
|
15
|
-
range: Range;
|
|
16
|
-
query: string;
|
|
17
|
-
text: string;
|
|
18
|
-
} | null;
|
|
19
|
-
declare function findSuggestionMatch(config: Trigger): SuggestionMatch;
|
|
20
|
-
|
|
21
|
-
interface SuggestionOptions<I = any, TSelected = any> {
|
|
22
|
-
/**
|
|
23
|
-
* The plugin key for the suggestion plugin.
|
|
24
|
-
* @default 'suggestion'
|
|
25
|
-
* @example 'mention'
|
|
26
|
-
*/
|
|
27
|
-
pluginKey?: PluginKey;
|
|
28
|
-
/**
|
|
29
|
-
* A function that returns a boolean to indicate if the suggestion should be active.
|
|
30
|
-
* This is useful to prevent suggestions from opening for remote users in collaborative environments.
|
|
31
|
-
* @param props The props object.
|
|
32
|
-
* @param props.editor The editor instance.
|
|
33
|
-
* @param props.range The range of the suggestion.
|
|
34
|
-
* @param props.query The current suggestion query.
|
|
35
|
-
* @param props.text The current suggestion text.
|
|
36
|
-
* @param props.transaction The current transaction.
|
|
37
|
-
* @returns {boolean}
|
|
38
|
-
* @example ({ transaction }) => isChangeOrigin(transaction)
|
|
39
|
-
*/
|
|
40
|
-
shouldShow?: (props: {
|
|
41
|
-
editor: Editor;
|
|
42
|
-
range: Range;
|
|
43
|
-
query: string;
|
|
44
|
-
text: string;
|
|
45
|
-
transaction: Transaction;
|
|
46
|
-
}) => boolean;
|
|
47
|
-
/**
|
|
48
|
-
* The editor instance.
|
|
49
|
-
* @default null
|
|
50
|
-
*/
|
|
51
|
-
editor: Editor;
|
|
52
|
-
/**
|
|
53
|
-
* The character that triggers the suggestion.
|
|
54
|
-
* @default '@'
|
|
55
|
-
* @example '#'
|
|
56
|
-
*/
|
|
57
|
-
char?: string;
|
|
58
|
-
/**
|
|
59
|
-
* Allow spaces in the suggestion query. Not compatible with `allowToIncludeChar`. Will be disabled if `allowToIncludeChar` is set to `true`.
|
|
60
|
-
* @default false
|
|
61
|
-
* @example true
|
|
62
|
-
*/
|
|
63
|
-
allowSpaces?: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Allow the character to be included in the suggestion query. Not compatible with `allowSpaces`.
|
|
66
|
-
* @default false
|
|
67
|
-
*/
|
|
68
|
-
allowToIncludeChar?: boolean;
|
|
69
|
-
/**
|
|
70
|
-
* Allow prefixes in the suggestion query.
|
|
71
|
-
* @default [' ']
|
|
72
|
-
* @example [' ', '@']
|
|
73
|
-
*/
|
|
74
|
-
allowedPrefixes?: string[] | null;
|
|
75
|
-
/**
|
|
76
|
-
* Only match suggestions at the start of the line.
|
|
77
|
-
* @default false
|
|
78
|
-
* @example true
|
|
79
|
-
*/
|
|
80
|
-
startOfLine?: boolean;
|
|
81
|
-
/**
|
|
82
|
-
* The tag name of the decoration node.
|
|
83
|
-
* @default 'span'
|
|
84
|
-
* @example 'div'
|
|
85
|
-
*/
|
|
86
|
-
decorationTag?: string;
|
|
87
|
-
/**
|
|
88
|
-
* The class name of the decoration node.
|
|
89
|
-
* @default 'suggestion'
|
|
90
|
-
* @example 'mention'
|
|
91
|
-
*/
|
|
92
|
-
decorationClass?: string;
|
|
93
|
-
/**
|
|
94
|
-
* Creates a decoration with the provided content.
|
|
95
|
-
* @param decorationContent - The content to display in the decoration
|
|
96
|
-
* @default "" - Creates an empty decoration if no content provided
|
|
97
|
-
*/
|
|
98
|
-
decorationContent?: string;
|
|
99
|
-
/**
|
|
100
|
-
* The class name of the decoration node when it is empty.
|
|
101
|
-
* @default 'is-empty'
|
|
102
|
-
* @example 'is-empty'
|
|
103
|
-
*/
|
|
104
|
-
decorationEmptyClass?: string;
|
|
105
|
-
/**
|
|
106
|
-
* A function that is called when a suggestion is selected.
|
|
107
|
-
* @param props The props object.
|
|
108
|
-
* @param props.editor The editor instance.
|
|
109
|
-
* @param props.range The range of the suggestion.
|
|
110
|
-
* @param props.props The props of the selected suggestion.
|
|
111
|
-
* @returns void
|
|
112
|
-
* @example ({ editor, range, props }) => { props.command(props.props) }
|
|
113
|
-
*/
|
|
114
|
-
command?: (props: {
|
|
115
|
-
editor: Editor;
|
|
116
|
-
range: Range;
|
|
117
|
-
props: TSelected;
|
|
118
|
-
}) => void;
|
|
119
|
-
/**
|
|
120
|
-
* A function that returns the suggestion items in form of an array.
|
|
121
|
-
* @param props The props object.
|
|
122
|
-
* @param props.editor The editor instance.
|
|
123
|
-
* @param props.query The current suggestion query.
|
|
124
|
-
* @returns An array of suggestion items.
|
|
125
|
-
* @example ({ editor, query }) => [{ id: 1, label: 'John Doe' }]
|
|
126
|
-
*/
|
|
127
|
-
items?: (props: {
|
|
128
|
-
query: string;
|
|
129
|
-
editor: Editor;
|
|
130
|
-
}) => I[] | Promise<I[]>;
|
|
131
|
-
/**
|
|
132
|
-
* The render function for the suggestion.
|
|
133
|
-
* @returns An object with render functions.
|
|
134
|
-
*/
|
|
135
|
-
render?: () => {
|
|
136
|
-
onBeforeStart?: (props: SuggestionProps<I, TSelected>) => void;
|
|
137
|
-
onStart?: (props: SuggestionProps<I, TSelected>) => void;
|
|
138
|
-
onBeforeUpdate?: (props: SuggestionProps<I, TSelected>) => void;
|
|
139
|
-
onUpdate?: (props: SuggestionProps<I, TSelected>) => void;
|
|
140
|
-
onExit?: (props: SuggestionProps<I, TSelected>) => void;
|
|
141
|
-
onKeyDown?: (props: SuggestionKeyDownProps) => boolean;
|
|
142
|
-
};
|
|
143
|
-
/**
|
|
144
|
-
* A function that returns a boolean to indicate if the suggestion should be active.
|
|
145
|
-
* @param props The props object.
|
|
146
|
-
* @returns {boolean}
|
|
147
|
-
*/
|
|
148
|
-
allow?: (props: {
|
|
149
|
-
editor: Editor;
|
|
150
|
-
state: EditorState;
|
|
151
|
-
range: Range;
|
|
152
|
-
isActive?: boolean;
|
|
153
|
-
}) => boolean;
|
|
154
|
-
findSuggestionMatch?: typeof findSuggestionMatch;
|
|
155
|
-
}
|
|
156
|
-
interface SuggestionProps<I = any, TSelected = any> {
|
|
157
|
-
/**
|
|
158
|
-
* The editor instance.
|
|
159
|
-
*/
|
|
160
|
-
editor: Editor;
|
|
161
|
-
/**
|
|
162
|
-
* The range of the suggestion.
|
|
163
|
-
*/
|
|
164
|
-
range: Range;
|
|
165
|
-
/**
|
|
166
|
-
* The current suggestion query.
|
|
167
|
-
*/
|
|
168
|
-
query: string;
|
|
169
|
-
/**
|
|
170
|
-
* The current suggestion text.
|
|
171
|
-
*/
|
|
172
|
-
text: string;
|
|
173
|
-
/**
|
|
174
|
-
* The suggestion items array.
|
|
175
|
-
*/
|
|
176
|
-
items: I[];
|
|
177
|
-
/**
|
|
178
|
-
* A function that is called when a suggestion is selected.
|
|
179
|
-
* @param props The props object.
|
|
180
|
-
* @returns void
|
|
181
|
-
*/
|
|
182
|
-
command: (props: TSelected) => void;
|
|
183
|
-
/**
|
|
184
|
-
* The decoration node HTML element
|
|
185
|
-
* @default null
|
|
186
|
-
*/
|
|
187
|
-
decorationNode: Element | null;
|
|
188
|
-
/**
|
|
189
|
-
* The function that returns the client rect
|
|
190
|
-
* @default null
|
|
191
|
-
* @example () => new DOMRect(0, 0, 0, 0)
|
|
192
|
-
*/
|
|
193
|
-
clientRect?: (() => DOMRect | null) | null;
|
|
194
|
-
}
|
|
195
|
-
interface SuggestionKeyDownProps {
|
|
196
|
-
view: EditorView;
|
|
197
|
-
event: KeyboardEvent;
|
|
198
|
-
range: Range;
|
|
199
|
-
}
|
|
200
|
-
declare const SuggestionPluginKey: PluginKey<any>;
|
|
201
|
-
/**
|
|
202
|
-
* This utility allows you to create suggestions.
|
|
203
|
-
* @see https://tiptap.dev/api/utilities/suggestion
|
|
204
|
-
*/
|
|
205
|
-
declare function Suggestion<I = any, TSelected = any>({ pluginKey, editor, char, allowSpaces, allowToIncludeChar, allowedPrefixes, startOfLine, decorationTag, decorationClass, decorationContent, decorationEmptyClass, command, items, render, allow, findSuggestionMatch, shouldShow, }: SuggestionOptions<I, TSelected>): Plugin<any>;
|
|
206
|
-
/**
|
|
207
|
-
* Programmatically exit a suggestion plugin by dispatching a metadata-only
|
|
208
|
-
* transaction. This is the safe, recommended API to remove suggestion
|
|
209
|
-
* decorations without touching the document or causing mapping errors.
|
|
210
|
-
*/
|
|
211
|
-
declare function exitSuggestion(view: EditorView, pluginKeyRef?: PluginKey): void;
|
|
212
|
-
|
|
213
|
-
export { Suggestion, type SuggestionKeyDownProps, type SuggestionMatch, type SuggestionOptions, SuggestionPluginKey, type SuggestionProps, type Trigger, Suggestion as default, exitSuggestion, findSuggestionMatch };
|
package/dist/index.d.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { Range, Editor } from '@tiptap/core';
|
|
2
|
-
import { PluginKey, Transaction, EditorState, Plugin } from '@tiptap/pm/state';
|
|
3
|
-
import { EditorView } from '@tiptap/pm/view';
|
|
4
|
-
import { ResolvedPos } from '@tiptap/pm/model';
|
|
5
|
-
|
|
6
|
-
interface Trigger {
|
|
7
|
-
char: string;
|
|
8
|
-
allowSpaces: boolean;
|
|
9
|
-
allowToIncludeChar: boolean;
|
|
10
|
-
allowedPrefixes: string[] | null;
|
|
11
|
-
startOfLine: boolean;
|
|
12
|
-
$position: ResolvedPos;
|
|
13
|
-
}
|
|
14
|
-
type SuggestionMatch = {
|
|
15
|
-
range: Range;
|
|
16
|
-
query: string;
|
|
17
|
-
text: string;
|
|
18
|
-
} | null;
|
|
19
|
-
declare function findSuggestionMatch(config: Trigger): SuggestionMatch;
|
|
20
|
-
|
|
21
|
-
interface SuggestionOptions<I = any, TSelected = any> {
|
|
22
|
-
/**
|
|
23
|
-
* The plugin key for the suggestion plugin.
|
|
24
|
-
* @default 'suggestion'
|
|
25
|
-
* @example 'mention'
|
|
26
|
-
*/
|
|
27
|
-
pluginKey?: PluginKey;
|
|
28
|
-
/**
|
|
29
|
-
* A function that returns a boolean to indicate if the suggestion should be active.
|
|
30
|
-
* This is useful to prevent suggestions from opening for remote users in collaborative environments.
|
|
31
|
-
* @param props The props object.
|
|
32
|
-
* @param props.editor The editor instance.
|
|
33
|
-
* @param props.range The range of the suggestion.
|
|
34
|
-
* @param props.query The current suggestion query.
|
|
35
|
-
* @param props.text The current suggestion text.
|
|
36
|
-
* @param props.transaction The current transaction.
|
|
37
|
-
* @returns {boolean}
|
|
38
|
-
* @example ({ transaction }) => isChangeOrigin(transaction)
|
|
39
|
-
*/
|
|
40
|
-
shouldShow?: (props: {
|
|
41
|
-
editor: Editor;
|
|
42
|
-
range: Range;
|
|
43
|
-
query: string;
|
|
44
|
-
text: string;
|
|
45
|
-
transaction: Transaction;
|
|
46
|
-
}) => boolean;
|
|
47
|
-
/**
|
|
48
|
-
* The editor instance.
|
|
49
|
-
* @default null
|
|
50
|
-
*/
|
|
51
|
-
editor: Editor;
|
|
52
|
-
/**
|
|
53
|
-
* The character that triggers the suggestion.
|
|
54
|
-
* @default '@'
|
|
55
|
-
* @example '#'
|
|
56
|
-
*/
|
|
57
|
-
char?: string;
|
|
58
|
-
/**
|
|
59
|
-
* Allow spaces in the suggestion query. Not compatible with `allowToIncludeChar`. Will be disabled if `allowToIncludeChar` is set to `true`.
|
|
60
|
-
* @default false
|
|
61
|
-
* @example true
|
|
62
|
-
*/
|
|
63
|
-
allowSpaces?: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Allow the character to be included in the suggestion query. Not compatible with `allowSpaces`.
|
|
66
|
-
* @default false
|
|
67
|
-
*/
|
|
68
|
-
allowToIncludeChar?: boolean;
|
|
69
|
-
/**
|
|
70
|
-
* Allow prefixes in the suggestion query.
|
|
71
|
-
* @default [' ']
|
|
72
|
-
* @example [' ', '@']
|
|
73
|
-
*/
|
|
74
|
-
allowedPrefixes?: string[] | null;
|
|
75
|
-
/**
|
|
76
|
-
* Only match suggestions at the start of the line.
|
|
77
|
-
* @default false
|
|
78
|
-
* @example true
|
|
79
|
-
*/
|
|
80
|
-
startOfLine?: boolean;
|
|
81
|
-
/**
|
|
82
|
-
* The tag name of the decoration node.
|
|
83
|
-
* @default 'span'
|
|
84
|
-
* @example 'div'
|
|
85
|
-
*/
|
|
86
|
-
decorationTag?: string;
|
|
87
|
-
/**
|
|
88
|
-
* The class name of the decoration node.
|
|
89
|
-
* @default 'suggestion'
|
|
90
|
-
* @example 'mention'
|
|
91
|
-
*/
|
|
92
|
-
decorationClass?: string;
|
|
93
|
-
/**
|
|
94
|
-
* Creates a decoration with the provided content.
|
|
95
|
-
* @param decorationContent - The content to display in the decoration
|
|
96
|
-
* @default "" - Creates an empty decoration if no content provided
|
|
97
|
-
*/
|
|
98
|
-
decorationContent?: string;
|
|
99
|
-
/**
|
|
100
|
-
* The class name of the decoration node when it is empty.
|
|
101
|
-
* @default 'is-empty'
|
|
102
|
-
* @example 'is-empty'
|
|
103
|
-
*/
|
|
104
|
-
decorationEmptyClass?: string;
|
|
105
|
-
/**
|
|
106
|
-
* A function that is called when a suggestion is selected.
|
|
107
|
-
* @param props The props object.
|
|
108
|
-
* @param props.editor The editor instance.
|
|
109
|
-
* @param props.range The range of the suggestion.
|
|
110
|
-
* @param props.props The props of the selected suggestion.
|
|
111
|
-
* @returns void
|
|
112
|
-
* @example ({ editor, range, props }) => { props.command(props.props) }
|
|
113
|
-
*/
|
|
114
|
-
command?: (props: {
|
|
115
|
-
editor: Editor;
|
|
116
|
-
range: Range;
|
|
117
|
-
props: TSelected;
|
|
118
|
-
}) => void;
|
|
119
|
-
/**
|
|
120
|
-
* A function that returns the suggestion items in form of an array.
|
|
121
|
-
* @param props The props object.
|
|
122
|
-
* @param props.editor The editor instance.
|
|
123
|
-
* @param props.query The current suggestion query.
|
|
124
|
-
* @returns An array of suggestion items.
|
|
125
|
-
* @example ({ editor, query }) => [{ id: 1, label: 'John Doe' }]
|
|
126
|
-
*/
|
|
127
|
-
items?: (props: {
|
|
128
|
-
query: string;
|
|
129
|
-
editor: Editor;
|
|
130
|
-
}) => I[] | Promise<I[]>;
|
|
131
|
-
/**
|
|
132
|
-
* The render function for the suggestion.
|
|
133
|
-
* @returns An object with render functions.
|
|
134
|
-
*/
|
|
135
|
-
render?: () => {
|
|
136
|
-
onBeforeStart?: (props: SuggestionProps<I, TSelected>) => void;
|
|
137
|
-
onStart?: (props: SuggestionProps<I, TSelected>) => void;
|
|
138
|
-
onBeforeUpdate?: (props: SuggestionProps<I, TSelected>) => void;
|
|
139
|
-
onUpdate?: (props: SuggestionProps<I, TSelected>) => void;
|
|
140
|
-
onExit?: (props: SuggestionProps<I, TSelected>) => void;
|
|
141
|
-
onKeyDown?: (props: SuggestionKeyDownProps) => boolean;
|
|
142
|
-
};
|
|
143
|
-
/**
|
|
144
|
-
* A function that returns a boolean to indicate if the suggestion should be active.
|
|
145
|
-
* @param props The props object.
|
|
146
|
-
* @returns {boolean}
|
|
147
|
-
*/
|
|
148
|
-
allow?: (props: {
|
|
149
|
-
editor: Editor;
|
|
150
|
-
state: EditorState;
|
|
151
|
-
range: Range;
|
|
152
|
-
isActive?: boolean;
|
|
153
|
-
}) => boolean;
|
|
154
|
-
findSuggestionMatch?: typeof findSuggestionMatch;
|
|
155
|
-
}
|
|
156
|
-
interface SuggestionProps<I = any, TSelected = any> {
|
|
157
|
-
/**
|
|
158
|
-
* The editor instance.
|
|
159
|
-
*/
|
|
160
|
-
editor: Editor;
|
|
161
|
-
/**
|
|
162
|
-
* The range of the suggestion.
|
|
163
|
-
*/
|
|
164
|
-
range: Range;
|
|
165
|
-
/**
|
|
166
|
-
* The current suggestion query.
|
|
167
|
-
*/
|
|
168
|
-
query: string;
|
|
169
|
-
/**
|
|
170
|
-
* The current suggestion text.
|
|
171
|
-
*/
|
|
172
|
-
text: string;
|
|
173
|
-
/**
|
|
174
|
-
* The suggestion items array.
|
|
175
|
-
*/
|
|
176
|
-
items: I[];
|
|
177
|
-
/**
|
|
178
|
-
* A function that is called when a suggestion is selected.
|
|
179
|
-
* @param props The props object.
|
|
180
|
-
* @returns void
|
|
181
|
-
*/
|
|
182
|
-
command: (props: TSelected) => void;
|
|
183
|
-
/**
|
|
184
|
-
* The decoration node HTML element
|
|
185
|
-
* @default null
|
|
186
|
-
*/
|
|
187
|
-
decorationNode: Element | null;
|
|
188
|
-
/**
|
|
189
|
-
* The function that returns the client rect
|
|
190
|
-
* @default null
|
|
191
|
-
* @example () => new DOMRect(0, 0, 0, 0)
|
|
192
|
-
*/
|
|
193
|
-
clientRect?: (() => DOMRect | null) | null;
|
|
194
|
-
}
|
|
195
|
-
interface SuggestionKeyDownProps {
|
|
196
|
-
view: EditorView;
|
|
197
|
-
event: KeyboardEvent;
|
|
198
|
-
range: Range;
|
|
199
|
-
}
|
|
200
|
-
declare const SuggestionPluginKey: PluginKey<any>;
|
|
201
|
-
/**
|
|
202
|
-
* This utility allows you to create suggestions.
|
|
203
|
-
* @see https://tiptap.dev/api/utilities/suggestion
|
|
204
|
-
*/
|
|
205
|
-
declare function Suggestion<I = any, TSelected = any>({ pluginKey, editor, char, allowSpaces, allowToIncludeChar, allowedPrefixes, startOfLine, decorationTag, decorationClass, decorationContent, decorationEmptyClass, command, items, render, allow, findSuggestionMatch, shouldShow, }: SuggestionOptions<I, TSelected>): Plugin<any>;
|
|
206
|
-
/**
|
|
207
|
-
* Programmatically exit a suggestion plugin by dispatching a metadata-only
|
|
208
|
-
* transaction. This is the safe, recommended API to remove suggestion
|
|
209
|
-
* decorations without touching the document or causing mapping errors.
|
|
210
|
-
*/
|
|
211
|
-
declare function exitSuggestion(view: EditorView, pluginKeyRef?: PluginKey): void;
|
|
212
|
-
|
|
213
|
-
export { Suggestion, type SuggestionKeyDownProps, type SuggestionMatch, type SuggestionOptions, SuggestionPluginKey, type SuggestionProps, type Trigger, Suggestion as default, exitSuggestion, findSuggestionMatch };
|
package/dist/index.js
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
// src/suggestion.ts
|
|
2
|
-
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
3
|
-
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
4
|
-
|
|
5
|
-
// src/findSuggestionMatch.ts
|
|
6
|
-
import { escapeForRegEx } from "@tiptap/core";
|
|
7
|
-
function findSuggestionMatch(config) {
|
|
8
|
-
var _a;
|
|
9
|
-
const { char, allowSpaces: allowSpacesOption, allowToIncludeChar, allowedPrefixes, startOfLine, $position } = config;
|
|
10
|
-
const allowSpaces = allowSpacesOption && !allowToIncludeChar;
|
|
11
|
-
const escapedChar = escapeForRegEx(char);
|
|
12
|
-
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
13
|
-
const prefix = startOfLine ? "^" : "";
|
|
14
|
-
const finalEscapedChar = allowToIncludeChar ? "" : escapedChar;
|
|
15
|
-
const regexp = allowSpaces ? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${finalEscapedChar}|$)`, "gm") : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${finalEscapedChar}]*`, "gm");
|
|
16
|
-
const text = ((_a = $position.nodeBefore) == null ? void 0 : _a.isText) && $position.nodeBefore.text;
|
|
17
|
-
if (!text) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
const textFrom = $position.pos - text.length;
|
|
21
|
-
const match = Array.from(text.matchAll(regexp)).pop();
|
|
22
|
-
if (!match || match.input === void 0 || match.index === void 0) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
|
|
26
|
-
const matchPrefixIsAllowed = new RegExp(`^[${allowedPrefixes == null ? void 0 : allowedPrefixes.join("")}\0]?$`).test(matchPrefix);
|
|
27
|
-
if (allowedPrefixes !== null && !matchPrefixIsAllowed) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
const from = textFrom + match.index;
|
|
31
|
-
let to = from + match[0].length;
|
|
32
|
-
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
|
33
|
-
match[0] += " ";
|
|
34
|
-
to += 1;
|
|
35
|
-
}
|
|
36
|
-
if (from < $position.pos && to >= $position.pos) {
|
|
37
|
-
return {
|
|
38
|
-
range: {
|
|
39
|
-
from,
|
|
40
|
-
to
|
|
41
|
-
},
|
|
42
|
-
query: match[0].slice(char.length),
|
|
43
|
-
text: match[0]
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// src/suggestion.ts
|
|
50
|
-
var SuggestionPluginKey = new PluginKey("suggestion");
|
|
51
|
-
function Suggestion({
|
|
52
|
-
pluginKey = SuggestionPluginKey,
|
|
53
|
-
editor,
|
|
54
|
-
char = "@",
|
|
55
|
-
allowSpaces = false,
|
|
56
|
-
allowToIncludeChar = false,
|
|
57
|
-
allowedPrefixes = [" "],
|
|
58
|
-
startOfLine = false,
|
|
59
|
-
decorationTag = "span",
|
|
60
|
-
decorationClass = "suggestion",
|
|
61
|
-
decorationContent = "",
|
|
62
|
-
decorationEmptyClass = "is-empty",
|
|
63
|
-
command = () => null,
|
|
64
|
-
items = () => [],
|
|
65
|
-
render = () => ({}),
|
|
66
|
-
allow = () => true,
|
|
67
|
-
findSuggestionMatch: findSuggestionMatch2 = findSuggestionMatch,
|
|
68
|
-
shouldShow
|
|
69
|
-
}) {
|
|
70
|
-
let props;
|
|
71
|
-
const renderer = render == null ? void 0 : render();
|
|
72
|
-
const getAnchorClientRect = () => {
|
|
73
|
-
const pos = editor.state.selection.$anchor.pos;
|
|
74
|
-
const coords = editor.view.coordsAtPos(pos);
|
|
75
|
-
const { top, right, bottom, left } = coords;
|
|
76
|
-
try {
|
|
77
|
-
return new DOMRect(left, top, right - left, bottom - top);
|
|
78
|
-
} catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
const clientRectFor = (view, decorationNode) => {
|
|
83
|
-
if (!decorationNode) {
|
|
84
|
-
return getAnchorClientRect;
|
|
85
|
-
}
|
|
86
|
-
return () => {
|
|
87
|
-
const state = pluginKey.getState(editor.state);
|
|
88
|
-
const decorationId = state == null ? void 0 : state.decorationId;
|
|
89
|
-
const currentDecorationNode = view.dom.querySelector(`[data-decoration-id="${decorationId}"]`);
|
|
90
|
-
return (currentDecorationNode == null ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
|
|
91
|
-
};
|
|
92
|
-
};
|
|
93
|
-
function dispatchExit(view, pluginKeyRef) {
|
|
94
|
-
var _a;
|
|
95
|
-
try {
|
|
96
|
-
const state = pluginKey.getState(view.state);
|
|
97
|
-
const decorationNode = (state == null ? void 0 : state.decorationId) ? view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`) : null;
|
|
98
|
-
const exitProps = {
|
|
99
|
-
// @ts-ignore editor is available in closure
|
|
100
|
-
editor,
|
|
101
|
-
range: (state == null ? void 0 : state.range) || { from: 0, to: 0 },
|
|
102
|
-
query: (state == null ? void 0 : state.query) || null,
|
|
103
|
-
text: (state == null ? void 0 : state.text) || null,
|
|
104
|
-
items: [],
|
|
105
|
-
command: (commandProps) => {
|
|
106
|
-
return command({ editor, range: (state == null ? void 0 : state.range) || { from: 0, to: 0 }, props: commandProps });
|
|
107
|
-
},
|
|
108
|
-
decorationNode,
|
|
109
|
-
clientRect: clientRectFor(view, decorationNode)
|
|
110
|
-
};
|
|
111
|
-
(_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, exitProps);
|
|
112
|
-
} catch {
|
|
113
|
-
}
|
|
114
|
-
const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true });
|
|
115
|
-
view.dispatch(tr);
|
|
116
|
-
}
|
|
117
|
-
const plugin = new Plugin({
|
|
118
|
-
key: pluginKey,
|
|
119
|
-
view() {
|
|
120
|
-
return {
|
|
121
|
-
update: async (view, prevState) => {
|
|
122
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
123
|
-
const prev = (_a = this.key) == null ? void 0 : _a.getState(prevState);
|
|
124
|
-
const next = (_b = this.key) == null ? void 0 : _b.getState(view.state);
|
|
125
|
-
const moved = prev.active && next.active && prev.range.from !== next.range.from;
|
|
126
|
-
const started = !prev.active && next.active;
|
|
127
|
-
const stopped = prev.active && !next.active;
|
|
128
|
-
const changed = !started && !stopped && prev.query !== next.query;
|
|
129
|
-
const handleStart = started || moved && changed;
|
|
130
|
-
const handleChange = changed || moved;
|
|
131
|
-
const handleExit = stopped || moved && changed;
|
|
132
|
-
if (!handleStart && !handleChange && !handleExit) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const state = handleExit && !handleStart ? prev : next;
|
|
136
|
-
const decorationNode = view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
137
|
-
props = {
|
|
138
|
-
editor,
|
|
139
|
-
range: state.range,
|
|
140
|
-
query: state.query,
|
|
141
|
-
text: state.text,
|
|
142
|
-
items: [],
|
|
143
|
-
command: (commandProps) => {
|
|
144
|
-
return command({
|
|
145
|
-
editor,
|
|
146
|
-
range: state.range,
|
|
147
|
-
props: commandProps
|
|
148
|
-
});
|
|
149
|
-
},
|
|
150
|
-
decorationNode,
|
|
151
|
-
clientRect: clientRectFor(view, decorationNode)
|
|
152
|
-
};
|
|
153
|
-
if (handleStart) {
|
|
154
|
-
(_c = renderer == null ? void 0 : renderer.onBeforeStart) == null ? void 0 : _c.call(renderer, props);
|
|
155
|
-
}
|
|
156
|
-
if (handleChange) {
|
|
157
|
-
(_d = renderer == null ? void 0 : renderer.onBeforeUpdate) == null ? void 0 : _d.call(renderer, props);
|
|
158
|
-
}
|
|
159
|
-
if (handleChange || handleStart) {
|
|
160
|
-
props.items = await items({
|
|
161
|
-
editor,
|
|
162
|
-
query: state.query
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
if (handleExit) {
|
|
166
|
-
(_e = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _e.call(renderer, props);
|
|
167
|
-
}
|
|
168
|
-
if (handleChange) {
|
|
169
|
-
(_f = renderer == null ? void 0 : renderer.onUpdate) == null ? void 0 : _f.call(renderer, props);
|
|
170
|
-
}
|
|
171
|
-
if (handleStart) {
|
|
172
|
-
(_g = renderer == null ? void 0 : renderer.onStart) == null ? void 0 : _g.call(renderer, props);
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
destroy: () => {
|
|
176
|
-
var _a;
|
|
177
|
-
if (!props) {
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
(_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, props);
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
},
|
|
184
|
-
state: {
|
|
185
|
-
// Initialize the plugin's internal state.
|
|
186
|
-
init() {
|
|
187
|
-
const state = {
|
|
188
|
-
active: false,
|
|
189
|
-
range: {
|
|
190
|
-
from: 0,
|
|
191
|
-
to: 0
|
|
192
|
-
},
|
|
193
|
-
query: null,
|
|
194
|
-
text: null,
|
|
195
|
-
composing: false
|
|
196
|
-
};
|
|
197
|
-
return state;
|
|
198
|
-
},
|
|
199
|
-
// Apply changes to the plugin state from a view transaction.
|
|
200
|
-
apply(transaction, prev, _oldState, state) {
|
|
201
|
-
const { isEditable } = editor;
|
|
202
|
-
const { composing } = editor.view;
|
|
203
|
-
const { selection } = transaction;
|
|
204
|
-
const { empty, from } = selection;
|
|
205
|
-
const next = { ...prev };
|
|
206
|
-
const meta = transaction.getMeta(pluginKey);
|
|
207
|
-
if (meta && meta.exit) {
|
|
208
|
-
next.active = false;
|
|
209
|
-
next.decorationId = null;
|
|
210
|
-
next.range = { from: 0, to: 0 };
|
|
211
|
-
next.query = null;
|
|
212
|
-
next.text = null;
|
|
213
|
-
return next;
|
|
214
|
-
}
|
|
215
|
-
next.composing = composing;
|
|
216
|
-
if (isEditable && (empty || editor.view.composing)) {
|
|
217
|
-
if ((from < prev.range.from || from > prev.range.to) && !composing && !prev.composing) {
|
|
218
|
-
next.active = false;
|
|
219
|
-
}
|
|
220
|
-
const match = findSuggestionMatch2({
|
|
221
|
-
char,
|
|
222
|
-
allowSpaces,
|
|
223
|
-
allowToIncludeChar,
|
|
224
|
-
allowedPrefixes,
|
|
225
|
-
startOfLine,
|
|
226
|
-
$position: selection.$from
|
|
227
|
-
});
|
|
228
|
-
const decorationId = `id_${Math.floor(Math.random() * 4294967295)}`;
|
|
229
|
-
if (match && allow({
|
|
230
|
-
editor,
|
|
231
|
-
state,
|
|
232
|
-
range: match.range,
|
|
233
|
-
isActive: prev.active
|
|
234
|
-
}) && (!shouldShow || shouldShow({
|
|
235
|
-
editor,
|
|
236
|
-
range: match.range,
|
|
237
|
-
query: match.query,
|
|
238
|
-
text: match.text,
|
|
239
|
-
transaction
|
|
240
|
-
}))) {
|
|
241
|
-
next.active = true;
|
|
242
|
-
next.decorationId = prev.decorationId ? prev.decorationId : decorationId;
|
|
243
|
-
next.range = match.range;
|
|
244
|
-
next.query = match.query;
|
|
245
|
-
next.text = match.text;
|
|
246
|
-
} else {
|
|
247
|
-
next.active = false;
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
next.active = false;
|
|
251
|
-
}
|
|
252
|
-
if (!next.active) {
|
|
253
|
-
next.decorationId = null;
|
|
254
|
-
next.range = { from: 0, to: 0 };
|
|
255
|
-
next.query = null;
|
|
256
|
-
next.text = null;
|
|
257
|
-
}
|
|
258
|
-
return next;
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
props: {
|
|
262
|
-
// Call the keydown hook if suggestion is active.
|
|
263
|
-
handleKeyDown(view, event) {
|
|
264
|
-
var _a, _b, _c, _d;
|
|
265
|
-
const { active, range } = plugin.getState(view.state);
|
|
266
|
-
if (!active) {
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
if (event.key === "Escape" || event.key === "Esc") {
|
|
270
|
-
const state = plugin.getState(view.state);
|
|
271
|
-
const cachedNode = (_a = props == null ? void 0 : props.decorationNode) != null ? _a : null;
|
|
272
|
-
const decorationNode = cachedNode != null ? cachedNode : (state == null ? void 0 : state.decorationId) ? view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`) : null;
|
|
273
|
-
const handledByKeyDown = ((_b = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _b.call(renderer, { view, event, range: state.range })) || false;
|
|
274
|
-
if (handledByKeyDown) {
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
const exitProps = {
|
|
278
|
-
editor,
|
|
279
|
-
range: state.range,
|
|
280
|
-
query: state.query,
|
|
281
|
-
text: state.text,
|
|
282
|
-
items: [],
|
|
283
|
-
command: (commandProps) => {
|
|
284
|
-
return command({ editor, range: state.range, props: commandProps });
|
|
285
|
-
},
|
|
286
|
-
decorationNode,
|
|
287
|
-
// If we have a cached decoration node, use it for the clientRect
|
|
288
|
-
// to avoid another DOM lookup. If not, leave clientRect null and
|
|
289
|
-
// let consumer decide if they want to query.
|
|
290
|
-
clientRect: decorationNode ? () => {
|
|
291
|
-
return decorationNode.getBoundingClientRect() || null;
|
|
292
|
-
} : null
|
|
293
|
-
};
|
|
294
|
-
(_c = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _c.call(renderer, exitProps);
|
|
295
|
-
dispatchExit(view, pluginKey);
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
const handled = ((_d = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _d.call(renderer, { view, event, range })) || false;
|
|
299
|
-
return handled;
|
|
300
|
-
},
|
|
301
|
-
// Setup decorator on the currently active suggestion.
|
|
302
|
-
decorations(state) {
|
|
303
|
-
const { active, range, decorationId, query } = plugin.getState(state);
|
|
304
|
-
if (!active) {
|
|
305
|
-
return null;
|
|
306
|
-
}
|
|
307
|
-
const isEmpty = !(query == null ? void 0 : query.length);
|
|
308
|
-
const classNames = [decorationClass];
|
|
309
|
-
if (isEmpty) {
|
|
310
|
-
classNames.push(decorationEmptyClass);
|
|
311
|
-
}
|
|
312
|
-
return DecorationSet.create(state.doc, [
|
|
313
|
-
Decoration.inline(range.from, range.to, {
|
|
314
|
-
nodeName: decorationTag,
|
|
315
|
-
class: classNames.join(" "),
|
|
316
|
-
"data-decoration-id": decorationId,
|
|
317
|
-
"data-decoration-content": decorationContent
|
|
318
|
-
})
|
|
319
|
-
]);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
return plugin;
|
|
324
|
-
}
|
|
325
|
-
function exitSuggestion(view, pluginKeyRef = SuggestionPluginKey) {
|
|
326
|
-
const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true });
|
|
327
|
-
view.dispatch(tr);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// src/index.ts
|
|
331
|
-
var index_default = Suggestion;
|
|
332
|
-
export {
|
|
333
|
-
Suggestion,
|
|
334
|
-
SuggestionPluginKey,
|
|
335
|
-
index_default as default,
|
|
336
|
-
exitSuggestion,
|
|
337
|
-
findSuggestionMatch
|
|
338
|
-
};
|
|
339
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/suggestion.ts","../src/findSuggestionMatch.ts","../src/index.ts"],"sourcesContent":["import type { Editor, Range } from '@tiptap/core'\nimport type { EditorState, Transaction } from '@tiptap/pm/state'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nimport { findSuggestionMatch as defaultFindSuggestionMatch } from './findSuggestionMatch.js'\n\nexport interface SuggestionOptions<I = any, TSelected = any> {\n /**\n * The plugin key for the suggestion plugin.\n * @default 'suggestion'\n * @example 'mention'\n */\n pluginKey?: PluginKey\n\n /**\n * A function that returns a boolean to indicate if the suggestion should be active.\n * This is useful to prevent suggestions from opening for remote users in collaborative environments.\n * @param props The props object.\n * @param props.editor The editor instance.\n * @param props.range The range of the suggestion.\n * @param props.query The current suggestion query.\n * @param props.text The current suggestion text.\n * @param props.transaction The current transaction.\n * @returns {boolean}\n * @example ({ transaction }) => isChangeOrigin(transaction)\n */\n shouldShow?: (props: {\n editor: Editor\n range: Range\n query: string\n text: string\n transaction: Transaction\n }) => boolean\n\n /**\n * The editor instance.\n * @default null\n */\n editor: Editor\n\n /**\n * The character that triggers the suggestion.\n * @default '@'\n * @example '#'\n */\n char?: string\n\n /**\n * Allow spaces in the suggestion query. Not compatible with `allowToIncludeChar`. Will be disabled if `allowToIncludeChar` is set to `true`.\n * @default false\n * @example true\n */\n allowSpaces?: boolean\n\n /**\n * Allow the character to be included in the suggestion query. Not compatible with `allowSpaces`.\n * @default false\n */\n allowToIncludeChar?: boolean\n\n /**\n * Allow prefixes in the suggestion query.\n * @default [' ']\n * @example [' ', '@']\n */\n allowedPrefixes?: string[] | null\n\n /**\n * Only match suggestions at the start of the line.\n * @default false\n * @example true\n */\n startOfLine?: boolean\n\n /**\n * The tag name of the decoration node.\n * @default 'span'\n * @example 'div'\n */\n decorationTag?: string\n\n /**\n * The class name of the decoration node.\n * @default 'suggestion'\n * @example 'mention'\n */\n decorationClass?: string\n\n /**\n * Creates a decoration with the provided content.\n * @param decorationContent - The content to display in the decoration\n * @default \"\" - Creates an empty decoration if no content provided\n */\n decorationContent?: string\n\n /**\n * The class name of the decoration node when it is empty.\n * @default 'is-empty'\n * @example 'is-empty'\n */\n decorationEmptyClass?: string\n\n /**\n * A function that is called when a suggestion is selected.\n * @param props The props object.\n * @param props.editor The editor instance.\n * @param props.range The range of the suggestion.\n * @param props.props The props of the selected suggestion.\n * @returns void\n * @example ({ editor, range, props }) => { props.command(props.props) }\n */\n command?: (props: { editor: Editor; range: Range; props: TSelected }) => void\n\n /**\n * A function that returns the suggestion items in form of an array.\n * @param props The props object.\n * @param props.editor The editor instance.\n * @param props.query The current suggestion query.\n * @returns An array of suggestion items.\n * @example ({ editor, query }) => [{ id: 1, label: 'John Doe' }]\n */\n items?: (props: { query: string; editor: Editor }) => I[] | Promise<I[]>\n\n /**\n * The render function for the suggestion.\n * @returns An object with render functions.\n */\n render?: () => {\n onBeforeStart?: (props: SuggestionProps<I, TSelected>) => void\n onStart?: (props: SuggestionProps<I, TSelected>) => void\n onBeforeUpdate?: (props: SuggestionProps<I, TSelected>) => void\n onUpdate?: (props: SuggestionProps<I, TSelected>) => void\n onExit?: (props: SuggestionProps<I, TSelected>) => void\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean\n }\n\n /**\n * A function that returns a boolean to indicate if the suggestion should be active.\n * @param props The props object.\n * @returns {boolean}\n */\n allow?: (props: { editor: Editor; state: EditorState; range: Range; isActive?: boolean }) => boolean\n findSuggestionMatch?: typeof defaultFindSuggestionMatch\n}\n\nexport interface SuggestionProps<I = any, TSelected = any> {\n /**\n * The editor instance.\n */\n editor: Editor\n\n /**\n * The range of the suggestion.\n */\n range: Range\n\n /**\n * The current suggestion query.\n */\n query: string\n\n /**\n * The current suggestion text.\n */\n text: string\n\n /**\n * The suggestion items array.\n */\n items: I[]\n\n /**\n * A function that is called when a suggestion is selected.\n * @param props The props object.\n * @returns void\n */\n command: (props: TSelected) => void\n\n /**\n * The decoration node HTML element\n * @default null\n */\n decorationNode: Element | null\n\n /**\n * The function that returns the client rect\n * @default null\n * @example () => new DOMRect(0, 0, 0, 0)\n */\n clientRect?: (() => DOMRect | null) | null\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView\n event: KeyboardEvent\n range: Range\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\n/**\n * This utility allows you to create suggestions.\n * @see https://tiptap.dev/api/utilities/suggestion\n */\nexport function Suggestion<I = any, TSelected = any>({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n allowToIncludeChar = false,\n allowedPrefixes = [' '],\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n decorationContent = '',\n decorationEmptyClass = 'is-empty',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n findSuggestionMatch = defaultFindSuggestionMatch,\n shouldShow,\n}: SuggestionOptions<I, TSelected>) {\n let props: SuggestionProps<I, TSelected> | undefined\n const renderer = render?.()\n\n // Gets the DOM rectangle corresponding to the current editor cursor anchor position\n // Calculates screen coordinates based on Tiptap's cursor position and converts to a DOMRect object\n const getAnchorClientRect = () => {\n const pos = editor.state.selection.$anchor.pos\n const coords = editor.view.coordsAtPos(pos)\n const { top, right, bottom, left } = coords\n\n try {\n return new DOMRect(left, top, right - left, bottom - top)\n } catch {\n return null\n }\n }\n\n // Helper to create a clientRect callback for a given decoration node.\n // Returns null when no decoration node is present. Uses the pluginKey's\n // state to resolve the current decoration node on demand, avoiding a\n // duplicated implementation in multiple places.\n const clientRectFor = (view: EditorView, decorationNode: Element | null) => {\n if (!decorationNode) {\n return getAnchorClientRect\n }\n\n return () => {\n const state = pluginKey.getState(editor.state)\n const decorationId = state?.decorationId\n const currentDecorationNode = view.dom.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n return currentDecorationNode?.getBoundingClientRect() || null\n }\n }\n // small helper used internally by the view to dispatch an exit\n function dispatchExit(view: EditorView, pluginKeyRef: PluginKey) {\n try {\n const state = pluginKey.getState(view.state)\n const decorationNode = state?.decorationId\n ? view.dom.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n : null\n\n const exitProps: SuggestionProps = {\n // @ts-ignore editor is available in closure\n editor,\n range: state?.range || { from: 0, to: 0 },\n query: state?.query || null,\n text: state?.text || null,\n items: [],\n command: commandProps => {\n return command({ editor, range: state?.range || { from: 0, to: 0 }, props: commandProps as any })\n },\n decorationNode,\n clientRect: clientRectFor(view, decorationNode),\n }\n\n renderer?.onExit?.(exitProps)\n } catch {\n // ignore errors from consumer renderers\n }\n\n const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true })\n // Dispatch a metadata-only transaction to signal the plugin to exit\n view.dispatch(tr)\n }\n\n const plugin: Plugin<any> = new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n\n const handleStart = started || (moved && changed)\n const handleChange = changed || moved\n const handleExit = stopped || (moved && changed)\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart ? prev : next\n const decorationNode = view.dom.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: [],\n command: commandProps => {\n return command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n clientRect: clientRectFor(view, decorationNode),\n }\n\n if (handleStart) {\n renderer?.onBeforeStart?.(props)\n }\n\n if (handleChange) {\n renderer?.onBeforeUpdate?.(props)\n }\n\n if (handleChange || handleStart) {\n props.items = await items({\n editor,\n query: state.query,\n })\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n const state: {\n active: boolean\n range: Range\n query: null | string\n text: null | string\n composing: boolean\n decorationId?: string | null\n } = {\n active: false,\n range: {\n from: 0,\n to: 0,\n },\n query: null,\n text: null,\n composing: false,\n }\n\n return state\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev, _oldState, state) {\n const { isEditable } = editor\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n // If a transaction carries the exit meta for this plugin, immediately\n // deactivate the suggestion. This allows metadata-only transactions\n // (dispatched by escape or programmatic exit) to deterministically\n // clear decorations without changing the document.\n const meta = transaction.getMeta(pluginKey)\n if (meta && meta.exit) {\n next.active = false\n next.decorationId = null\n next.range = { from: 0, to: 0 }\n next.query = null\n next.text = null\n\n return next\n }\n\n next.composing = composing\n\n // We can only be suggesting if the view is editable, and:\n // * there is no selection, or\n // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (isEditable && (empty || editor.view.composing)) {\n // Reset active state if we just left the previous suggestion range\n if ((from < prev.range.from || from > prev.range.to) && !composing && !prev.composing) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n allowToIncludeChar,\n allowedPrefixes,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xffffffff)}`\n\n // If we found a match, update the current state to show it\n if (\n match &&\n allow({\n editor,\n state,\n range: match.range,\n isActive: prev.active,\n }) &&\n (!shouldShow ||\n shouldShow({\n editor,\n range: match.range,\n query: match.query,\n text: match.text,\n transaction,\n }))\n ) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = { from: 0, to: 0 }\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = plugin.getState(view.state)\n\n if (!active) {\n return false\n }\n\n // If Escape is pressed, call onExit and dispatch a metadata-only\n // transaction to unset the suggestion state. This provides a safe\n // and deterministic way to exit the suggestion without altering the\n // document (avoids transaction mapping/mismatch issues).\n if (event.key === 'Escape' || event.key === 'Esc') {\n const state = plugin.getState(view.state)\n const cachedNode = props?.decorationNode ?? null\n const decorationNode =\n cachedNode ??\n (state?.decorationId ? view.dom.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`) : null)\n\n // Give the consumer a chance to handle Escape via onKeyDown first.\n // If the consumer returns `true` we assume they handled the event and\n // we won't call onExit/dispatchExit so they can both prevent\n // propagation and decide whether to close the suggestion themselves.\n const handledByKeyDown = renderer?.onKeyDown?.({ view, event, range: state.range }) || false\n\n if (handledByKeyDown) {\n return true\n }\n\n const exitProps: SuggestionProps = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: [],\n command: commandProps => {\n return command({ editor, range: state.range, props: commandProps as any })\n },\n decorationNode,\n // If we have a cached decoration node, use it for the clientRect\n // to avoid another DOM lookup. If not, leave clientRect null and\n // let consumer decide if they want to query.\n clientRect: decorationNode\n ? () => {\n return decorationNode.getBoundingClientRect() || null\n }\n : null,\n }\n\n renderer?.onExit?.(exitProps)\n\n // dispatch metadata-only transaction to unset the plugin state\n dispatchExit(view, pluginKey)\n\n return true\n }\n\n const handled = renderer?.onKeyDown?.({ view, event, range }) || false\n return handled\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId, query } = plugin.getState(state)\n\n if (!active) {\n return null\n }\n\n const isEmpty = !query?.length\n const classNames = [decorationClass]\n\n if (isEmpty) {\n classNames.push(decorationEmptyClass)\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: classNames.join(' '),\n 'data-decoration-id': decorationId,\n 'data-decoration-content': decorationContent,\n }),\n ])\n },\n },\n })\n\n return plugin\n}\n\n/**\n * Programmatically exit a suggestion plugin by dispatching a metadata-only\n * transaction. This is the safe, recommended API to remove suggestion\n * decorations without touching the document or causing mapping errors.\n */\nexport function exitSuggestion(view: EditorView, pluginKeyRef: PluginKey = SuggestionPluginKey) {\n const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true })\n view.dispatch(tr)\n}\n","import type { Range } from '@tiptap/core'\nimport { escapeForRegEx } from '@tiptap/core'\nimport type { ResolvedPos } from '@tiptap/pm/model'\n\nexport interface Trigger {\n char: string\n allowSpaces: boolean\n allowToIncludeChar: boolean\n allowedPrefixes: string[] | null\n startOfLine: boolean\n $position: ResolvedPos\n}\n\nexport type SuggestionMatch = {\n range: Range\n query: string\n text: string\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const { char, allowSpaces: allowSpacesOption, allowToIncludeChar, allowedPrefixes, startOfLine, $position } = config\n\n const allowSpaces = allowSpacesOption && !allowToIncludeChar\n\n const escapedChar = escapeForRegEx(char)\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const finalEscapedChar = allowToIncludeChar ? '' : escapedChar\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${finalEscapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${finalEscapedChar}]*`, 'gm')\n\n const text = $position.nodeBefore?.isText && $position.nodeBefore.text\n\n if (!text) {\n return null\n }\n\n const textFrom = $position.pos - text.length\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsAllowed = new RegExp(`^[${allowedPrefixes?.join('')}\\0]?$`).test(matchPrefix)\n\n if (allowedPrefixes !== null && !matchPrefixIsAllowed) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = textFrom + match.index\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { exitSuggestion, Suggestion } from './suggestion.js'\n\nexport * from './findSuggestionMatch.js'\nexport * from './suggestion.js'\n\nexport { exitSuggestion }\n\nexport default Suggestion\n"],"mappings":"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","names":["findSuggestionMatch"]}
|