@tiptap/suggestion 2.0.0-beta.21 → 2.0.0-beta.211
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/README.md +6 -6
- package/dist/index.cjs +235 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.js +235 -0
- package/package.json +38 -12
- package/src/findSuggestionMatch.ts +24 -27
- package/src/suggestion.ts +118 -59
- package/CHANGELOG.md +0 -264
- package/LICENSE.md +0 -21
- package/dist/packages/suggestion/src/findSuggestionMatch.d.ts +0 -14
- package/dist/packages/suggestion/src/index.d.ts +0 -4
- package/dist/packages/suggestion/src/suggestion.d.ts +0 -43
- package/dist/tiptap-suggestion.bundle.umd.min.js +0 -2
- package/dist/tiptap-suggestion.bundle.umd.min.js.map +0 -1
- package/dist/tiptap-suggestion.cjs.js +0 -197
- package/dist/tiptap-suggestion.cjs.js.map +0 -1
- package/dist/tiptap-suggestion.esm.js +0 -192
- package/dist/tiptap-suggestion.esm.js.map +0 -1
- package/dist/tiptap-suggestion.umd.js +0 -200
- package/dist/tiptap-suggestion.umd.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# @tiptap/
|
|
2
|
-
[](https://www.npmjs.com/package/@tiptap/suggestion)
|
|
3
|
+
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
|
4
|
+
[](https://www.npmjs.com/package/@tiptap/suggestion)
|
|
5
5
|
[](https://github.com/sponsors/ueberdosis)
|
|
6
6
|
|
|
7
7
|
## Introduction
|
|
8
8
|
tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Official Documentation
|
|
11
11
|
Documentation can be found on the [tiptap website](https://tiptap.dev).
|
|
12
12
|
|
|
13
13
|
## License
|
|
14
|
-
tiptap is open
|
|
14
|
+
tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/suggestion.ts
|
|
2
|
+
var _state = require('@tiptap/pm/state');
|
|
3
|
+
var _view = require('@tiptap/pm/view');
|
|
4
|
+
|
|
5
|
+
// src/findSuggestionMatch.ts
|
|
6
|
+
var _core = require('@tiptap/core');
|
|
7
|
+
function findSuggestionMatch(config) {
|
|
8
|
+
var _a;
|
|
9
|
+
const {
|
|
10
|
+
char,
|
|
11
|
+
allowSpaces,
|
|
12
|
+
allowedPrefixes,
|
|
13
|
+
startOfLine,
|
|
14
|
+
$position
|
|
15
|
+
} = config;
|
|
16
|
+
const escapedChar = _core.escapeForRegEx.call(void 0, char);
|
|
17
|
+
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
18
|
+
const prefix = startOfLine ? "^" : "";
|
|
19
|
+
const regexp = allowSpaces ? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, "gm") : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, "gm");
|
|
20
|
+
const text = ((_a = $position.nodeBefore) == null ? void 0 : _a.isText) && $position.nodeBefore.text;
|
|
21
|
+
if (!text) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const textFrom = $position.pos - text.length;
|
|
25
|
+
const match = Array.from(text.matchAll(regexp)).pop();
|
|
26
|
+
if (!match || match.input === void 0 || match.index === void 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
|
|
30
|
+
const matchPrefixIsAllowed = new RegExp(`^[${allowedPrefixes == null ? void 0 : allowedPrefixes.join("")}\0]?$`).test(matchPrefix);
|
|
31
|
+
if (allowedPrefixes !== null && !matchPrefixIsAllowed) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const from = textFrom + match.index;
|
|
35
|
+
let to = from + match[0].length;
|
|
36
|
+
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
|
37
|
+
match[0] += " ";
|
|
38
|
+
to += 1;
|
|
39
|
+
}
|
|
40
|
+
if (from < $position.pos && to >= $position.pos) {
|
|
41
|
+
return {
|
|
42
|
+
range: {
|
|
43
|
+
from,
|
|
44
|
+
to
|
|
45
|
+
},
|
|
46
|
+
query: match[0].slice(char.length),
|
|
47
|
+
text: match[0]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/suggestion.ts
|
|
54
|
+
var SuggestionPluginKey = new (0, _state.PluginKey)("suggestion");
|
|
55
|
+
function Suggestion({
|
|
56
|
+
pluginKey = SuggestionPluginKey,
|
|
57
|
+
editor,
|
|
58
|
+
char = "@",
|
|
59
|
+
allowSpaces = false,
|
|
60
|
+
allowedPrefixes = [" "],
|
|
61
|
+
startOfLine = false,
|
|
62
|
+
decorationTag = "span",
|
|
63
|
+
decorationClass = "suggestion",
|
|
64
|
+
command = () => null,
|
|
65
|
+
items = () => [],
|
|
66
|
+
render = () => ({}),
|
|
67
|
+
allow = () => true
|
|
68
|
+
}) {
|
|
69
|
+
let props;
|
|
70
|
+
const renderer = render == null ? void 0 : render();
|
|
71
|
+
const plugin = new (0, _state.Plugin)({
|
|
72
|
+
key: pluginKey,
|
|
73
|
+
view() {
|
|
74
|
+
return {
|
|
75
|
+
update: async (view, prevState) => {
|
|
76
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
77
|
+
const prev = (_a = this.key) == null ? void 0 : _a.getState(prevState);
|
|
78
|
+
const next = (_b = this.key) == null ? void 0 : _b.getState(view.state);
|
|
79
|
+
const moved = prev.active && next.active && prev.range.from !== next.range.from;
|
|
80
|
+
const started = !prev.active && next.active;
|
|
81
|
+
const stopped = prev.active && !next.active;
|
|
82
|
+
const changed = !started && !stopped && prev.query !== next.query;
|
|
83
|
+
const handleStart = started || moved;
|
|
84
|
+
const handleChange = changed && !moved;
|
|
85
|
+
const handleExit = stopped || moved;
|
|
86
|
+
if (!handleStart && !handleChange && !handleExit) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const state = handleExit && !handleStart ? prev : next;
|
|
90
|
+
const decorationNode = view.dom.querySelector(
|
|
91
|
+
`[data-decoration-id="${state.decorationId}"]`
|
|
92
|
+
);
|
|
93
|
+
props = {
|
|
94
|
+
editor,
|
|
95
|
+
range: state.range,
|
|
96
|
+
query: state.query,
|
|
97
|
+
text: state.text,
|
|
98
|
+
items: [],
|
|
99
|
+
command: (commandProps) => {
|
|
100
|
+
command({
|
|
101
|
+
editor,
|
|
102
|
+
range: state.range,
|
|
103
|
+
props: commandProps
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
decorationNode,
|
|
107
|
+
clientRect: decorationNode ? () => {
|
|
108
|
+
var _a2;
|
|
109
|
+
const { decorationId } = (_a2 = this.key) == null ? void 0 : _a2.getState(editor.state);
|
|
110
|
+
const currentDecorationNode = view.dom.querySelector(
|
|
111
|
+
`[data-decoration-id="${decorationId}"]`
|
|
112
|
+
);
|
|
113
|
+
return (currentDecorationNode == null ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
|
|
114
|
+
} : null
|
|
115
|
+
};
|
|
116
|
+
if (handleStart) {
|
|
117
|
+
(_c = renderer == null ? void 0 : renderer.onBeforeStart) == null ? void 0 : _c.call(renderer, props);
|
|
118
|
+
}
|
|
119
|
+
if (handleChange) {
|
|
120
|
+
(_d = renderer == null ? void 0 : renderer.onBeforeUpdate) == null ? void 0 : _d.call(renderer, props);
|
|
121
|
+
}
|
|
122
|
+
if (handleChange || handleStart) {
|
|
123
|
+
props.items = await items({
|
|
124
|
+
editor,
|
|
125
|
+
query: state.query
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (handleExit) {
|
|
129
|
+
(_e = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _e.call(renderer, props);
|
|
130
|
+
}
|
|
131
|
+
if (handleChange) {
|
|
132
|
+
(_f = renderer == null ? void 0 : renderer.onUpdate) == null ? void 0 : _f.call(renderer, props);
|
|
133
|
+
}
|
|
134
|
+
if (handleStart) {
|
|
135
|
+
(_g = renderer == null ? void 0 : renderer.onStart) == null ? void 0 : _g.call(renderer, props);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
destroy: () => {
|
|
139
|
+
var _a;
|
|
140
|
+
if (!props) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
(_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, props);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
state: {
|
|
148
|
+
init() {
|
|
149
|
+
const state = {
|
|
150
|
+
active: false,
|
|
151
|
+
range: {
|
|
152
|
+
from: 0,
|
|
153
|
+
to: 0
|
|
154
|
+
},
|
|
155
|
+
query: null,
|
|
156
|
+
text: null,
|
|
157
|
+
composing: false
|
|
158
|
+
};
|
|
159
|
+
return state;
|
|
160
|
+
},
|
|
161
|
+
apply(transaction, prev, oldState, state) {
|
|
162
|
+
const { isEditable } = editor;
|
|
163
|
+
const { composing } = editor.view;
|
|
164
|
+
const { selection } = transaction;
|
|
165
|
+
const { empty, from } = selection;
|
|
166
|
+
const next = { ...prev };
|
|
167
|
+
next.composing = composing;
|
|
168
|
+
if (isEditable && (empty || editor.view.composing)) {
|
|
169
|
+
if ((from < prev.range.from || from > prev.range.to) && !composing && !prev.composing) {
|
|
170
|
+
next.active = false;
|
|
171
|
+
}
|
|
172
|
+
const match = findSuggestionMatch({
|
|
173
|
+
char,
|
|
174
|
+
allowSpaces,
|
|
175
|
+
allowedPrefixes,
|
|
176
|
+
startOfLine,
|
|
177
|
+
$position: selection.$from
|
|
178
|
+
});
|
|
179
|
+
const decorationId = `id_${Math.floor(Math.random() * 4294967295)}`;
|
|
180
|
+
if (match && allow({ editor, state, range: match.range })) {
|
|
181
|
+
next.active = true;
|
|
182
|
+
next.decorationId = prev.decorationId ? prev.decorationId : decorationId;
|
|
183
|
+
next.range = match.range;
|
|
184
|
+
next.query = match.query;
|
|
185
|
+
next.text = match.text;
|
|
186
|
+
} else {
|
|
187
|
+
next.active = false;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
next.active = false;
|
|
191
|
+
}
|
|
192
|
+
if (!next.active) {
|
|
193
|
+
next.decorationId = null;
|
|
194
|
+
next.range = { from: 0, to: 0 };
|
|
195
|
+
next.query = null;
|
|
196
|
+
next.text = null;
|
|
197
|
+
}
|
|
198
|
+
return next;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
props: {
|
|
202
|
+
handleKeyDown(view, event) {
|
|
203
|
+
var _a;
|
|
204
|
+
const { active, range } = plugin.getState(view.state);
|
|
205
|
+
if (!active) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
return ((_a = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _a.call(renderer, { view, event, range })) || false;
|
|
209
|
+
},
|
|
210
|
+
decorations(state) {
|
|
211
|
+
const { active, range, decorationId } = plugin.getState(state);
|
|
212
|
+
if (!active) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return _view.DecorationSet.create(state.doc, [
|
|
216
|
+
_view.Decoration.inline(range.from, range.to, {
|
|
217
|
+
nodeName: decorationTag,
|
|
218
|
+
class: decorationClass,
|
|
219
|
+
"data-decoration-id": decorationId
|
|
220
|
+
})
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return plugin;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/index.ts
|
|
229
|
+
var src_default = Suggestion;
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
exports.Suggestion = Suggestion; exports.SuggestionPluginKey = SuggestionPluginKey; exports.default = src_default; exports.findSuggestionMatch = findSuggestionMatch;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Editor, Range } from '@tiptap/core';
|
|
2
|
+
import { PluginKey, EditorState, Plugin } from '@tiptap/pm/state';
|
|
3
|
+
import { EditorView } from '@tiptap/pm/view';
|
|
4
|
+
import { ResolvedPos } from '@tiptap/pm/model';
|
|
5
|
+
|
|
6
|
+
interface SuggestionOptions<I = any> {
|
|
7
|
+
pluginKey?: PluginKey;
|
|
8
|
+
editor: Editor;
|
|
9
|
+
char?: string;
|
|
10
|
+
allowSpaces?: boolean;
|
|
11
|
+
allowedPrefixes?: string[] | null;
|
|
12
|
+
startOfLine?: boolean;
|
|
13
|
+
decorationTag?: string;
|
|
14
|
+
decorationClass?: string;
|
|
15
|
+
command?: (props: {
|
|
16
|
+
editor: Editor;
|
|
17
|
+
range: Range;
|
|
18
|
+
props: I;
|
|
19
|
+
}) => void;
|
|
20
|
+
items?: (props: {
|
|
21
|
+
query: string;
|
|
22
|
+
editor: Editor;
|
|
23
|
+
}) => I[] | Promise<I[]>;
|
|
24
|
+
render?: () => {
|
|
25
|
+
onBeforeStart?: (props: SuggestionProps<I>) => void;
|
|
26
|
+
onStart?: (props: SuggestionProps<I>) => void;
|
|
27
|
+
onBeforeUpdate?: (props: SuggestionProps<I>) => void;
|
|
28
|
+
onUpdate?: (props: SuggestionProps<I>) => void;
|
|
29
|
+
onExit?: (props: SuggestionProps<I>) => void;
|
|
30
|
+
onKeyDown?: (props: SuggestionKeyDownProps) => boolean;
|
|
31
|
+
};
|
|
32
|
+
allow?: (props: {
|
|
33
|
+
editor: Editor;
|
|
34
|
+
state: EditorState;
|
|
35
|
+
range: Range;
|
|
36
|
+
}) => boolean;
|
|
37
|
+
}
|
|
38
|
+
interface SuggestionProps<I = any> {
|
|
39
|
+
editor: Editor;
|
|
40
|
+
range: Range;
|
|
41
|
+
query: string;
|
|
42
|
+
text: string;
|
|
43
|
+
items: I[];
|
|
44
|
+
command: (props: I) => void;
|
|
45
|
+
decorationNode: Element | null;
|
|
46
|
+
clientRect?: (() => DOMRect | null) | null;
|
|
47
|
+
}
|
|
48
|
+
interface SuggestionKeyDownProps {
|
|
49
|
+
view: EditorView;
|
|
50
|
+
event: KeyboardEvent;
|
|
51
|
+
range: Range;
|
|
52
|
+
}
|
|
53
|
+
declare const SuggestionPluginKey: PluginKey<any>;
|
|
54
|
+
declare function Suggestion<I = any>({ pluginKey, editor, char, allowSpaces, allowedPrefixes, startOfLine, decorationTag, decorationClass, command, items, render, allow, }: SuggestionOptions<I>): Plugin<any>;
|
|
55
|
+
|
|
56
|
+
interface Trigger {
|
|
57
|
+
char: string;
|
|
58
|
+
allowSpaces: boolean;
|
|
59
|
+
allowedPrefixes: string[] | null;
|
|
60
|
+
startOfLine: boolean;
|
|
61
|
+
$position: ResolvedPos;
|
|
62
|
+
}
|
|
63
|
+
declare type SuggestionMatch = {
|
|
64
|
+
range: Range;
|
|
65
|
+
query: string;
|
|
66
|
+
text: string;
|
|
67
|
+
} | null;
|
|
68
|
+
declare function findSuggestionMatch(config: Trigger): SuggestionMatch;
|
|
69
|
+
|
|
70
|
+
export { Suggestion, SuggestionKeyDownProps, SuggestionMatch, SuggestionOptions, SuggestionPluginKey, SuggestionProps, Trigger, Suggestion as default, findSuggestionMatch };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
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 {
|
|
10
|
+
char,
|
|
11
|
+
allowSpaces,
|
|
12
|
+
allowedPrefixes,
|
|
13
|
+
startOfLine,
|
|
14
|
+
$position
|
|
15
|
+
} = config;
|
|
16
|
+
const escapedChar = escapeForRegEx(char);
|
|
17
|
+
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
18
|
+
const prefix = startOfLine ? "^" : "";
|
|
19
|
+
const regexp = allowSpaces ? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, "gm") : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, "gm");
|
|
20
|
+
const text = ((_a = $position.nodeBefore) == null ? void 0 : _a.isText) && $position.nodeBefore.text;
|
|
21
|
+
if (!text) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const textFrom = $position.pos - text.length;
|
|
25
|
+
const match = Array.from(text.matchAll(regexp)).pop();
|
|
26
|
+
if (!match || match.input === void 0 || match.index === void 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
|
|
30
|
+
const matchPrefixIsAllowed = new RegExp(`^[${allowedPrefixes == null ? void 0 : allowedPrefixes.join("")}\0]?$`).test(matchPrefix);
|
|
31
|
+
if (allowedPrefixes !== null && !matchPrefixIsAllowed) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const from = textFrom + match.index;
|
|
35
|
+
let to = from + match[0].length;
|
|
36
|
+
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
|
37
|
+
match[0] += " ";
|
|
38
|
+
to += 1;
|
|
39
|
+
}
|
|
40
|
+
if (from < $position.pos && to >= $position.pos) {
|
|
41
|
+
return {
|
|
42
|
+
range: {
|
|
43
|
+
from,
|
|
44
|
+
to
|
|
45
|
+
},
|
|
46
|
+
query: match[0].slice(char.length),
|
|
47
|
+
text: match[0]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/suggestion.ts
|
|
54
|
+
var SuggestionPluginKey = new PluginKey("suggestion");
|
|
55
|
+
function Suggestion({
|
|
56
|
+
pluginKey = SuggestionPluginKey,
|
|
57
|
+
editor,
|
|
58
|
+
char = "@",
|
|
59
|
+
allowSpaces = false,
|
|
60
|
+
allowedPrefixes = [" "],
|
|
61
|
+
startOfLine = false,
|
|
62
|
+
decorationTag = "span",
|
|
63
|
+
decorationClass = "suggestion",
|
|
64
|
+
command = () => null,
|
|
65
|
+
items = () => [],
|
|
66
|
+
render = () => ({}),
|
|
67
|
+
allow = () => true
|
|
68
|
+
}) {
|
|
69
|
+
let props;
|
|
70
|
+
const renderer = render == null ? void 0 : render();
|
|
71
|
+
const plugin = new Plugin({
|
|
72
|
+
key: pluginKey,
|
|
73
|
+
view() {
|
|
74
|
+
return {
|
|
75
|
+
update: async (view, prevState) => {
|
|
76
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
77
|
+
const prev = (_a = this.key) == null ? void 0 : _a.getState(prevState);
|
|
78
|
+
const next = (_b = this.key) == null ? void 0 : _b.getState(view.state);
|
|
79
|
+
const moved = prev.active && next.active && prev.range.from !== next.range.from;
|
|
80
|
+
const started = !prev.active && next.active;
|
|
81
|
+
const stopped = prev.active && !next.active;
|
|
82
|
+
const changed = !started && !stopped && prev.query !== next.query;
|
|
83
|
+
const handleStart = started || moved;
|
|
84
|
+
const handleChange = changed && !moved;
|
|
85
|
+
const handleExit = stopped || moved;
|
|
86
|
+
if (!handleStart && !handleChange && !handleExit) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const state = handleExit && !handleStart ? prev : next;
|
|
90
|
+
const decorationNode = view.dom.querySelector(
|
|
91
|
+
`[data-decoration-id="${state.decorationId}"]`
|
|
92
|
+
);
|
|
93
|
+
props = {
|
|
94
|
+
editor,
|
|
95
|
+
range: state.range,
|
|
96
|
+
query: state.query,
|
|
97
|
+
text: state.text,
|
|
98
|
+
items: [],
|
|
99
|
+
command: (commandProps) => {
|
|
100
|
+
command({
|
|
101
|
+
editor,
|
|
102
|
+
range: state.range,
|
|
103
|
+
props: commandProps
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
decorationNode,
|
|
107
|
+
clientRect: decorationNode ? () => {
|
|
108
|
+
var _a2;
|
|
109
|
+
const { decorationId } = (_a2 = this.key) == null ? void 0 : _a2.getState(editor.state);
|
|
110
|
+
const currentDecorationNode = view.dom.querySelector(
|
|
111
|
+
`[data-decoration-id="${decorationId}"]`
|
|
112
|
+
);
|
|
113
|
+
return (currentDecorationNode == null ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
|
|
114
|
+
} : null
|
|
115
|
+
};
|
|
116
|
+
if (handleStart) {
|
|
117
|
+
(_c = renderer == null ? void 0 : renderer.onBeforeStart) == null ? void 0 : _c.call(renderer, props);
|
|
118
|
+
}
|
|
119
|
+
if (handleChange) {
|
|
120
|
+
(_d = renderer == null ? void 0 : renderer.onBeforeUpdate) == null ? void 0 : _d.call(renderer, props);
|
|
121
|
+
}
|
|
122
|
+
if (handleChange || handleStart) {
|
|
123
|
+
props.items = await items({
|
|
124
|
+
editor,
|
|
125
|
+
query: state.query
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (handleExit) {
|
|
129
|
+
(_e = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _e.call(renderer, props);
|
|
130
|
+
}
|
|
131
|
+
if (handleChange) {
|
|
132
|
+
(_f = renderer == null ? void 0 : renderer.onUpdate) == null ? void 0 : _f.call(renderer, props);
|
|
133
|
+
}
|
|
134
|
+
if (handleStart) {
|
|
135
|
+
(_g = renderer == null ? void 0 : renderer.onStart) == null ? void 0 : _g.call(renderer, props);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
destroy: () => {
|
|
139
|
+
var _a;
|
|
140
|
+
if (!props) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
(_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, props);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
state: {
|
|
148
|
+
init() {
|
|
149
|
+
const state = {
|
|
150
|
+
active: false,
|
|
151
|
+
range: {
|
|
152
|
+
from: 0,
|
|
153
|
+
to: 0
|
|
154
|
+
},
|
|
155
|
+
query: null,
|
|
156
|
+
text: null,
|
|
157
|
+
composing: false
|
|
158
|
+
};
|
|
159
|
+
return state;
|
|
160
|
+
},
|
|
161
|
+
apply(transaction, prev, oldState, state) {
|
|
162
|
+
const { isEditable } = editor;
|
|
163
|
+
const { composing } = editor.view;
|
|
164
|
+
const { selection } = transaction;
|
|
165
|
+
const { empty, from } = selection;
|
|
166
|
+
const next = { ...prev };
|
|
167
|
+
next.composing = composing;
|
|
168
|
+
if (isEditable && (empty || editor.view.composing)) {
|
|
169
|
+
if ((from < prev.range.from || from > prev.range.to) && !composing && !prev.composing) {
|
|
170
|
+
next.active = false;
|
|
171
|
+
}
|
|
172
|
+
const match = findSuggestionMatch({
|
|
173
|
+
char,
|
|
174
|
+
allowSpaces,
|
|
175
|
+
allowedPrefixes,
|
|
176
|
+
startOfLine,
|
|
177
|
+
$position: selection.$from
|
|
178
|
+
});
|
|
179
|
+
const decorationId = `id_${Math.floor(Math.random() * 4294967295)}`;
|
|
180
|
+
if (match && allow({ editor, state, range: match.range })) {
|
|
181
|
+
next.active = true;
|
|
182
|
+
next.decorationId = prev.decorationId ? prev.decorationId : decorationId;
|
|
183
|
+
next.range = match.range;
|
|
184
|
+
next.query = match.query;
|
|
185
|
+
next.text = match.text;
|
|
186
|
+
} else {
|
|
187
|
+
next.active = false;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
next.active = false;
|
|
191
|
+
}
|
|
192
|
+
if (!next.active) {
|
|
193
|
+
next.decorationId = null;
|
|
194
|
+
next.range = { from: 0, to: 0 };
|
|
195
|
+
next.query = null;
|
|
196
|
+
next.text = null;
|
|
197
|
+
}
|
|
198
|
+
return next;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
props: {
|
|
202
|
+
handleKeyDown(view, event) {
|
|
203
|
+
var _a;
|
|
204
|
+
const { active, range } = plugin.getState(view.state);
|
|
205
|
+
if (!active) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
return ((_a = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _a.call(renderer, { view, event, range })) || false;
|
|
209
|
+
},
|
|
210
|
+
decorations(state) {
|
|
211
|
+
const { active, range, decorationId } = plugin.getState(state);
|
|
212
|
+
if (!active) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return DecorationSet.create(state.doc, [
|
|
216
|
+
Decoration.inline(range.from, range.to, {
|
|
217
|
+
nodeName: decorationTag,
|
|
218
|
+
class: decorationClass,
|
|
219
|
+
"data-decoration-id": decorationId
|
|
220
|
+
})
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return plugin;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/index.ts
|
|
229
|
+
var src_default = Suggestion;
|
|
230
|
+
export {
|
|
231
|
+
Suggestion,
|
|
232
|
+
SuggestionPluginKey,
|
|
233
|
+
src_default as default,
|
|
234
|
+
findSuggestionMatch
|
|
235
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiptap/suggestion",
|
|
3
3
|
"description": "suggestion plugin for tiptap",
|
|
4
|
-
"version": "2.0.0-beta.
|
|
4
|
+
"version": "2.0.0-beta.211",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -12,20 +12,46 @@
|
|
|
12
12
|
"type": "github",
|
|
13
13
|
"url": "https://github.com/sponsors/ueberdosis"
|
|
14
14
|
},
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"require": "./dist/index.cjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"main": "dist/index.cjs",
|
|
23
|
+
"module": "dist/index.js",
|
|
24
|
+
"types": "dist/index.d.ts",
|
|
25
|
+
"type": "module",
|
|
20
26
|
"files": [
|
|
21
27
|
"src",
|
|
22
28
|
"dist"
|
|
23
29
|
],
|
|
24
|
-
"
|
|
25
|
-
"@tiptap/core": "^2.0.0-beta.
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@tiptap/core": "^2.0.0-beta.209",
|
|
32
|
+
"@tiptap/pm": "^2.0.0-beta.209"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@tiptap/core": "^2.0.0-beta.211",
|
|
36
|
+
"@tiptap/pm": "^2.0.0-beta.211"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/ueberdosis/tiptap",
|
|
41
|
+
"directory": "packages/suggestion"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup"
|
|
29
45
|
},
|
|
30
|
-
"
|
|
46
|
+
"tsup": {
|
|
47
|
+
"entry": [
|
|
48
|
+
"src/index.ts"
|
|
49
|
+
],
|
|
50
|
+
"dts": true,
|
|
51
|
+
"splitting": true,
|
|
52
|
+
"format": [
|
|
53
|
+
"esm",
|
|
54
|
+
"cjs"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
31
57
|
}
|