@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
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
var prosemirrorState = require('prosemirror-state');
|
|
6
|
-
var prosemirrorView = require('prosemirror-view');
|
|
7
|
-
|
|
8
|
-
function findSuggestionMatch(config) {
|
|
9
|
-
const { char, allowSpaces, startOfLine, $position, } = config;
|
|
10
|
-
// cancel if top level node
|
|
11
|
-
if ($position.depth <= 0) {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
// Matching expressions used for later
|
|
15
|
-
const escapedChar = `\\${char}`;
|
|
16
|
-
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
17
|
-
const prefix = startOfLine ? '^' : '';
|
|
18
|
-
const regexp = allowSpaces
|
|
19
|
-
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
|
|
20
|
-
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm');
|
|
21
|
-
const textFrom = $position.before();
|
|
22
|
-
const textTo = $position.pos;
|
|
23
|
-
const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
|
|
24
|
-
const match = Array.from(text.matchAll(regexp)).pop();
|
|
25
|
-
if (!match || match.input === undefined || match.index === undefined) {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
// JavaScript doesn't have lookbehinds; this hacks a check that first character is " "
|
|
29
|
-
// or the line beginning
|
|
30
|
-
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
|
|
31
|
-
if (!/^[\s\0]?$/.test(matchPrefix)) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
// The absolute position of the match in the document
|
|
35
|
-
const from = match.index + $position.start();
|
|
36
|
-
let to = from + match[0].length;
|
|
37
|
-
// Edge case handling; if spaces are allowed and we're directly in between
|
|
38
|
-
// two triggers
|
|
39
|
-
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
|
40
|
-
match[0] += ' ';
|
|
41
|
-
to += 1;
|
|
42
|
-
}
|
|
43
|
-
// If the $position is located within the matched substring, return that range
|
|
44
|
-
if (from < $position.pos && to >= $position.pos) {
|
|
45
|
-
return {
|
|
46
|
-
range: {
|
|
47
|
-
from,
|
|
48
|
-
to,
|
|
49
|
-
},
|
|
50
|
-
query: match[0].slice(char.length),
|
|
51
|
-
text: match[0],
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function Suggestion({ editor, char = '@', allowSpaces = false, startOfLine = false, decorationTag = 'span', decorationClass = 'suggestion', command = () => null, items = () => [], render = () => ({}), allow = () => true, }) {
|
|
58
|
-
const renderer = render === null || render === void 0 ? void 0 : render();
|
|
59
|
-
return new prosemirrorState.Plugin({
|
|
60
|
-
key: new prosemirrorState.PluginKey('suggestion'),
|
|
61
|
-
view() {
|
|
62
|
-
return {
|
|
63
|
-
update: async (view, prevState) => {
|
|
64
|
-
var _a, _b, _c, _d, _e;
|
|
65
|
-
const prev = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(prevState);
|
|
66
|
-
const next = (_b = this.key) === null || _b === void 0 ? void 0 : _b.getState(view.state);
|
|
67
|
-
// See how the state changed
|
|
68
|
-
const moved = prev.active && next.active && prev.range.from !== next.range.from;
|
|
69
|
-
const started = !prev.active && next.active;
|
|
70
|
-
const stopped = prev.active && !next.active;
|
|
71
|
-
const changed = !started && !stopped && prev.query !== next.query;
|
|
72
|
-
const handleStart = started || moved;
|
|
73
|
-
const handleChange = changed && !moved;
|
|
74
|
-
const handleExit = stopped || moved;
|
|
75
|
-
// Cancel when suggestion isn't active
|
|
76
|
-
if (!handleStart && !handleChange && !handleExit) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const state = handleExit ? prev : next;
|
|
80
|
-
const decorationNode = document.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
81
|
-
const props = {
|
|
82
|
-
editor,
|
|
83
|
-
range: state.range,
|
|
84
|
-
query: state.query,
|
|
85
|
-
text: state.text,
|
|
86
|
-
items: (handleChange || handleStart)
|
|
87
|
-
? await items(state.query)
|
|
88
|
-
: [],
|
|
89
|
-
command: commandProps => {
|
|
90
|
-
command({
|
|
91
|
-
editor,
|
|
92
|
-
range: state.range,
|
|
93
|
-
props: commandProps,
|
|
94
|
-
});
|
|
95
|
-
},
|
|
96
|
-
decorationNode,
|
|
97
|
-
// virtual node for popper.js or tippy.js
|
|
98
|
-
// this can be used for building popups without a DOM node
|
|
99
|
-
clientRect: () => (decorationNode === null || decorationNode === void 0 ? void 0 : decorationNode.getBoundingClientRect()) || null,
|
|
100
|
-
};
|
|
101
|
-
if (handleExit) {
|
|
102
|
-
(_c = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
|
|
103
|
-
}
|
|
104
|
-
if (handleChange) {
|
|
105
|
-
(_d = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
|
|
106
|
-
}
|
|
107
|
-
if (handleStart) {
|
|
108
|
-
(_e = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
},
|
|
113
|
-
state: {
|
|
114
|
-
// Initialize the plugin's internal state.
|
|
115
|
-
init() {
|
|
116
|
-
return {
|
|
117
|
-
active: false,
|
|
118
|
-
range: {},
|
|
119
|
-
query: null,
|
|
120
|
-
text: null,
|
|
121
|
-
};
|
|
122
|
-
},
|
|
123
|
-
// Apply changes to the plugin state from a view transaction.
|
|
124
|
-
apply(transaction, prev) {
|
|
125
|
-
const { selection } = transaction;
|
|
126
|
-
const next = { ...prev };
|
|
127
|
-
// We can only be suggesting if there is no selection
|
|
128
|
-
if (selection.from === selection.to) {
|
|
129
|
-
// Reset active state if we just left the previous suggestion range
|
|
130
|
-
if (selection.from < prev.range.from || selection.from > prev.range.to) {
|
|
131
|
-
next.active = false;
|
|
132
|
-
}
|
|
133
|
-
// Try to match against where our cursor currently is
|
|
134
|
-
const match = findSuggestionMatch({
|
|
135
|
-
char,
|
|
136
|
-
allowSpaces,
|
|
137
|
-
startOfLine,
|
|
138
|
-
$position: selection.$from,
|
|
139
|
-
});
|
|
140
|
-
const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`;
|
|
141
|
-
// If we found a match, update the current state to show it
|
|
142
|
-
if (match && allow({ editor, range: match.range })) {
|
|
143
|
-
next.active = true;
|
|
144
|
-
next.decorationId = prev.decorationId ? prev.decorationId : decorationId;
|
|
145
|
-
next.range = match.range;
|
|
146
|
-
next.query = match.query;
|
|
147
|
-
next.text = match.text;
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
next.active = false;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
next.active = false;
|
|
155
|
-
}
|
|
156
|
-
// Make sure to empty the range if suggestion is inactive
|
|
157
|
-
if (!next.active) {
|
|
158
|
-
next.decorationId = null;
|
|
159
|
-
next.range = {};
|
|
160
|
-
next.query = null;
|
|
161
|
-
next.text = null;
|
|
162
|
-
}
|
|
163
|
-
return next;
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
props: {
|
|
167
|
-
// Call the keydown hook if suggestion is active.
|
|
168
|
-
handleKeyDown(view, event) {
|
|
169
|
-
var _a;
|
|
170
|
-
const { active, range } = this.getState(view.state);
|
|
171
|
-
if (!active) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
return ((_a = renderer === null || renderer === void 0 ? void 0 : renderer.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(renderer, { view, event, range })) || false;
|
|
175
|
-
},
|
|
176
|
-
// Setup decorator on the currently active suggestion.
|
|
177
|
-
decorations(state) {
|
|
178
|
-
const { active, range, decorationId } = this.getState(state);
|
|
179
|
-
if (!active) {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
return prosemirrorView.DecorationSet.create(state.doc, [
|
|
183
|
-
prosemirrorView.Decoration.inline(range.from, range.to, {
|
|
184
|
-
nodeName: decorationTag,
|
|
185
|
-
class: decorationClass,
|
|
186
|
-
'data-decoration-id': decorationId,
|
|
187
|
-
}),
|
|
188
|
-
]);
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
exports.Suggestion = Suggestion;
|
|
195
|
-
exports.default = Suggestion;
|
|
196
|
-
exports.findSuggestionMatch = findSuggestionMatch;
|
|
197
|
-
//# sourceMappingURL=tiptap-suggestion.cjs.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-suggestion.cjs.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\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 {\n char,\n allowSpaces,\n startOfLine,\n $position,\n } = config\n\n // cancel if top level node\n if ($position.depth <= 0) {\n return null\n }\n\n // Matching expressions used for later\n const escapedChar = `\\\\${char}`\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const textFrom = $position.before()\n const textTo = $position.pos\n const text = $position.doc.textBetween(textFrom, textTo, '\\0', '\\0')\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 is \" \"\n // or the line beginning\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n\n if (!/^[\\s\\0]?$/.test(matchPrefix)) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = match.index + $position.start()\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 { Editor, Range } from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (query: string) => any[],\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: () => (DOMRect | null),\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport function Suggestion({\n editor,\n char = '@',\n allowSpaces = false,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n const renderer = render?.()\n\n return new Plugin({\n key: new PluginKey('suggestion'),\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 const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit ? prev : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n const props: SuggestionProps = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: (handleChange || handleStart)\n ? await items(state.query)\n : [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: () => decorationNode?.getBoundingClientRect() || null,\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 },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev) {\n const { selection } = transaction\n const next = { ...prev }\n\n // We can only be suggesting if there is no selection\n if (selection.from === selection.to) {\n // Reset active state if we just left the previous suggestion range\n if (selection.from < prev.range.from || selection.from > prev.range.to) {\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 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 (match && allow({ editor, range: match.range })) {\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 = {}\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 } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":["Plugin","PluginKey","DecorationSet","Decoration"],"mappings":"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"}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { Plugin, PluginKey } from 'prosemirror-state';
|
|
2
|
-
import { DecorationSet, Decoration } from 'prosemirror-view';
|
|
3
|
-
|
|
4
|
-
function findSuggestionMatch(config) {
|
|
5
|
-
const { char, allowSpaces, startOfLine, $position, } = config;
|
|
6
|
-
// cancel if top level node
|
|
7
|
-
if ($position.depth <= 0) {
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
// Matching expressions used for later
|
|
11
|
-
const escapedChar = `\\${char}`;
|
|
12
|
-
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
13
|
-
const prefix = startOfLine ? '^' : '';
|
|
14
|
-
const regexp = allowSpaces
|
|
15
|
-
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
|
|
16
|
-
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm');
|
|
17
|
-
const textFrom = $position.before();
|
|
18
|
-
const textTo = $position.pos;
|
|
19
|
-
const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
|
|
20
|
-
const match = Array.from(text.matchAll(regexp)).pop();
|
|
21
|
-
if (!match || match.input === undefined || match.index === undefined) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
// JavaScript doesn't have lookbehinds; this hacks a check that first character is " "
|
|
25
|
-
// or the line beginning
|
|
26
|
-
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
|
|
27
|
-
if (!/^[\s\0]?$/.test(matchPrefix)) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
// The absolute position of the match in the document
|
|
31
|
-
const from = match.index + $position.start();
|
|
32
|
-
let to = from + match[0].length;
|
|
33
|
-
// Edge case handling; if spaces are allowed and we're directly in between
|
|
34
|
-
// two triggers
|
|
35
|
-
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
|
36
|
-
match[0] += ' ';
|
|
37
|
-
to += 1;
|
|
38
|
-
}
|
|
39
|
-
// If the $position is located within the matched substring, return that range
|
|
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
|
-
function Suggestion({ editor, char = '@', allowSpaces = false, startOfLine = false, decorationTag = 'span', decorationClass = 'suggestion', command = () => null, items = () => [], render = () => ({}), allow = () => true, }) {
|
|
54
|
-
const renderer = render === null || render === void 0 ? void 0 : render();
|
|
55
|
-
return new Plugin({
|
|
56
|
-
key: new PluginKey('suggestion'),
|
|
57
|
-
view() {
|
|
58
|
-
return {
|
|
59
|
-
update: async (view, prevState) => {
|
|
60
|
-
var _a, _b, _c, _d, _e;
|
|
61
|
-
const prev = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(prevState);
|
|
62
|
-
const next = (_b = this.key) === null || _b === void 0 ? void 0 : _b.getState(view.state);
|
|
63
|
-
// See how the state changed
|
|
64
|
-
const moved = prev.active && next.active && prev.range.from !== next.range.from;
|
|
65
|
-
const started = !prev.active && next.active;
|
|
66
|
-
const stopped = prev.active && !next.active;
|
|
67
|
-
const changed = !started && !stopped && prev.query !== next.query;
|
|
68
|
-
const handleStart = started || moved;
|
|
69
|
-
const handleChange = changed && !moved;
|
|
70
|
-
const handleExit = stopped || moved;
|
|
71
|
-
// Cancel when suggestion isn't active
|
|
72
|
-
if (!handleStart && !handleChange && !handleExit) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const state = handleExit ? prev : next;
|
|
76
|
-
const decorationNode = document.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
77
|
-
const props = {
|
|
78
|
-
editor,
|
|
79
|
-
range: state.range,
|
|
80
|
-
query: state.query,
|
|
81
|
-
text: state.text,
|
|
82
|
-
items: (handleChange || handleStart)
|
|
83
|
-
? await items(state.query)
|
|
84
|
-
: [],
|
|
85
|
-
command: commandProps => {
|
|
86
|
-
command({
|
|
87
|
-
editor,
|
|
88
|
-
range: state.range,
|
|
89
|
-
props: commandProps,
|
|
90
|
-
});
|
|
91
|
-
},
|
|
92
|
-
decorationNode,
|
|
93
|
-
// virtual node for popper.js or tippy.js
|
|
94
|
-
// this can be used for building popups without a DOM node
|
|
95
|
-
clientRect: () => (decorationNode === null || decorationNode === void 0 ? void 0 : decorationNode.getBoundingClientRect()) || null,
|
|
96
|
-
};
|
|
97
|
-
if (handleExit) {
|
|
98
|
-
(_c = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
|
|
99
|
-
}
|
|
100
|
-
if (handleChange) {
|
|
101
|
-
(_d = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
|
|
102
|
-
}
|
|
103
|
-
if (handleStart) {
|
|
104
|
-
(_e = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
},
|
|
109
|
-
state: {
|
|
110
|
-
// Initialize the plugin's internal state.
|
|
111
|
-
init() {
|
|
112
|
-
return {
|
|
113
|
-
active: false,
|
|
114
|
-
range: {},
|
|
115
|
-
query: null,
|
|
116
|
-
text: null,
|
|
117
|
-
};
|
|
118
|
-
},
|
|
119
|
-
// Apply changes to the plugin state from a view transaction.
|
|
120
|
-
apply(transaction, prev) {
|
|
121
|
-
const { selection } = transaction;
|
|
122
|
-
const next = { ...prev };
|
|
123
|
-
// We can only be suggesting if there is no selection
|
|
124
|
-
if (selection.from === selection.to) {
|
|
125
|
-
// Reset active state if we just left the previous suggestion range
|
|
126
|
-
if (selection.from < prev.range.from || selection.from > prev.range.to) {
|
|
127
|
-
next.active = false;
|
|
128
|
-
}
|
|
129
|
-
// Try to match against where our cursor currently is
|
|
130
|
-
const match = findSuggestionMatch({
|
|
131
|
-
char,
|
|
132
|
-
allowSpaces,
|
|
133
|
-
startOfLine,
|
|
134
|
-
$position: selection.$from,
|
|
135
|
-
});
|
|
136
|
-
const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`;
|
|
137
|
-
// If we found a match, update the current state to show it
|
|
138
|
-
if (match && allow({ editor, range: match.range })) {
|
|
139
|
-
next.active = true;
|
|
140
|
-
next.decorationId = prev.decorationId ? prev.decorationId : decorationId;
|
|
141
|
-
next.range = match.range;
|
|
142
|
-
next.query = match.query;
|
|
143
|
-
next.text = match.text;
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
next.active = false;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
next.active = false;
|
|
151
|
-
}
|
|
152
|
-
// Make sure to empty the range if suggestion is inactive
|
|
153
|
-
if (!next.active) {
|
|
154
|
-
next.decorationId = null;
|
|
155
|
-
next.range = {};
|
|
156
|
-
next.query = null;
|
|
157
|
-
next.text = null;
|
|
158
|
-
}
|
|
159
|
-
return next;
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
props: {
|
|
163
|
-
// Call the keydown hook if suggestion is active.
|
|
164
|
-
handleKeyDown(view, event) {
|
|
165
|
-
var _a;
|
|
166
|
-
const { active, range } = this.getState(view.state);
|
|
167
|
-
if (!active) {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
return ((_a = renderer === null || renderer === void 0 ? void 0 : renderer.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(renderer, { view, event, range })) || false;
|
|
171
|
-
},
|
|
172
|
-
// Setup decorator on the currently active suggestion.
|
|
173
|
-
decorations(state) {
|
|
174
|
-
const { active, range, decorationId } = this.getState(state);
|
|
175
|
-
if (!active) {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
return DecorationSet.create(state.doc, [
|
|
179
|
-
Decoration.inline(range.from, range.to, {
|
|
180
|
-
nodeName: decorationTag,
|
|
181
|
-
class: decorationClass,
|
|
182
|
-
'data-decoration-id': decorationId,
|
|
183
|
-
}),
|
|
184
|
-
]);
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export default Suggestion;
|
|
191
|
-
export { Suggestion, findSuggestionMatch };
|
|
192
|
-
//# sourceMappingURL=tiptap-suggestion.esm.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-suggestion.esm.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\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 {\n char,\n allowSpaces,\n startOfLine,\n $position,\n } = config\n\n // cancel if top level node\n if ($position.depth <= 0) {\n return null\n }\n\n // Matching expressions used for later\n const escapedChar = `\\\\${char}`\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const textFrom = $position.before()\n const textTo = $position.pos\n const text = $position.doc.textBetween(textFrom, textTo, '\\0', '\\0')\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 is \" \"\n // or the line beginning\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n\n if (!/^[\\s\\0]?$/.test(matchPrefix)) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = match.index + $position.start()\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 { Editor, Range } from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (query: string) => any[],\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: () => (DOMRect | null),\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport function Suggestion({\n editor,\n char = '@',\n allowSpaces = false,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n const renderer = render?.()\n\n return new Plugin({\n key: new PluginKey('suggestion'),\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 const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit ? prev : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n const props: SuggestionProps = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: (handleChange || handleStart)\n ? await items(state.query)\n : [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: () => decorationNode?.getBoundingClientRect() || null,\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 },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev) {\n const { selection } = transaction\n const next = { ...prev }\n\n // We can only be suggesting if there is no selection\n if (selection.from === selection.to) {\n // Reset active state if we just left the previous suggestion range\n if (selection.from < prev.range.from || selection.from > prev.range.to) {\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 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 (match && allow({ editor, range: match.range })) {\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 = {}\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 } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('prosemirror-state'), require('prosemirror-view')) :
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', 'prosemirror-state', 'prosemirror-view'], factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global['@tiptap/suggestion'] = {}, global.prosemirrorState, global.prosemirrorView));
|
|
5
|
-
}(this, (function (exports, prosemirrorState, prosemirrorView) { 'use strict';
|
|
6
|
-
|
|
7
|
-
function findSuggestionMatch(config) {
|
|
8
|
-
const { char, allowSpaces, startOfLine, $position, } = config;
|
|
9
|
-
// cancel if top level node
|
|
10
|
-
if ($position.depth <= 0) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
// Matching expressions used for later
|
|
14
|
-
const escapedChar = `\\${char}`;
|
|
15
|
-
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
16
|
-
const prefix = startOfLine ? '^' : '';
|
|
17
|
-
const regexp = allowSpaces
|
|
18
|
-
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
|
|
19
|
-
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm');
|
|
20
|
-
const textFrom = $position.before();
|
|
21
|
-
const textTo = $position.pos;
|
|
22
|
-
const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
|
|
23
|
-
const match = Array.from(text.matchAll(regexp)).pop();
|
|
24
|
-
if (!match || match.input === undefined || match.index === undefined) {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
// JavaScript doesn't have lookbehinds; this hacks a check that first character is " "
|
|
28
|
-
// or the line beginning
|
|
29
|
-
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
|
|
30
|
-
if (!/^[\s\0]?$/.test(matchPrefix)) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
// The absolute position of the match in the document
|
|
34
|
-
const from = match.index + $position.start();
|
|
35
|
-
let to = from + match[0].length;
|
|
36
|
-
// Edge case handling; if spaces are allowed and we're directly in between
|
|
37
|
-
// two triggers
|
|
38
|
-
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
|
39
|
-
match[0] += ' ';
|
|
40
|
-
to += 1;
|
|
41
|
-
}
|
|
42
|
-
// If the $position is located within the matched substring, return that range
|
|
43
|
-
if (from < $position.pos && to >= $position.pos) {
|
|
44
|
-
return {
|
|
45
|
-
range: {
|
|
46
|
-
from,
|
|
47
|
-
to,
|
|
48
|
-
},
|
|
49
|
-
query: match[0].slice(char.length),
|
|
50
|
-
text: match[0],
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function Suggestion({ editor, char = '@', allowSpaces = false, startOfLine = false, decorationTag = 'span', decorationClass = 'suggestion', command = () => null, items = () => [], render = () => ({}), allow = () => true, }) {
|
|
57
|
-
const renderer = render === null || render === void 0 ? void 0 : render();
|
|
58
|
-
return new prosemirrorState.Plugin({
|
|
59
|
-
key: new prosemirrorState.PluginKey('suggestion'),
|
|
60
|
-
view() {
|
|
61
|
-
return {
|
|
62
|
-
update: async (view, prevState) => {
|
|
63
|
-
var _a, _b, _c, _d, _e;
|
|
64
|
-
const prev = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(prevState);
|
|
65
|
-
const next = (_b = this.key) === null || _b === void 0 ? void 0 : _b.getState(view.state);
|
|
66
|
-
// See how the state changed
|
|
67
|
-
const moved = prev.active && next.active && prev.range.from !== next.range.from;
|
|
68
|
-
const started = !prev.active && next.active;
|
|
69
|
-
const stopped = prev.active && !next.active;
|
|
70
|
-
const changed = !started && !stopped && prev.query !== next.query;
|
|
71
|
-
const handleStart = started || moved;
|
|
72
|
-
const handleChange = changed && !moved;
|
|
73
|
-
const handleExit = stopped || moved;
|
|
74
|
-
// Cancel when suggestion isn't active
|
|
75
|
-
if (!handleStart && !handleChange && !handleExit) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const state = handleExit ? prev : next;
|
|
79
|
-
const decorationNode = document.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
80
|
-
const props = {
|
|
81
|
-
editor,
|
|
82
|
-
range: state.range,
|
|
83
|
-
query: state.query,
|
|
84
|
-
text: state.text,
|
|
85
|
-
items: (handleChange || handleStart)
|
|
86
|
-
? await items(state.query)
|
|
87
|
-
: [],
|
|
88
|
-
command: commandProps => {
|
|
89
|
-
command({
|
|
90
|
-
editor,
|
|
91
|
-
range: state.range,
|
|
92
|
-
props: commandProps,
|
|
93
|
-
});
|
|
94
|
-
},
|
|
95
|
-
decorationNode,
|
|
96
|
-
// virtual node for popper.js or tippy.js
|
|
97
|
-
// this can be used for building popups without a DOM node
|
|
98
|
-
clientRect: () => (decorationNode === null || decorationNode === void 0 ? void 0 : decorationNode.getBoundingClientRect()) || null,
|
|
99
|
-
};
|
|
100
|
-
if (handleExit) {
|
|
101
|
-
(_c = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
|
|
102
|
-
}
|
|
103
|
-
if (handleChange) {
|
|
104
|
-
(_d = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
|
|
105
|
-
}
|
|
106
|
-
if (handleStart) {
|
|
107
|
-
(_e = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
},
|
|
112
|
-
state: {
|
|
113
|
-
// Initialize the plugin's internal state.
|
|
114
|
-
init() {
|
|
115
|
-
return {
|
|
116
|
-
active: false,
|
|
117
|
-
range: {},
|
|
118
|
-
query: null,
|
|
119
|
-
text: null,
|
|
120
|
-
};
|
|
121
|
-
},
|
|
122
|
-
// Apply changes to the plugin state from a view transaction.
|
|
123
|
-
apply(transaction, prev) {
|
|
124
|
-
const { selection } = transaction;
|
|
125
|
-
const next = { ...prev };
|
|
126
|
-
// We can only be suggesting if there is no selection
|
|
127
|
-
if (selection.from === selection.to) {
|
|
128
|
-
// Reset active state if we just left the previous suggestion range
|
|
129
|
-
if (selection.from < prev.range.from || selection.from > prev.range.to) {
|
|
130
|
-
next.active = false;
|
|
131
|
-
}
|
|
132
|
-
// Try to match against where our cursor currently is
|
|
133
|
-
const match = findSuggestionMatch({
|
|
134
|
-
char,
|
|
135
|
-
allowSpaces,
|
|
136
|
-
startOfLine,
|
|
137
|
-
$position: selection.$from,
|
|
138
|
-
});
|
|
139
|
-
const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`;
|
|
140
|
-
// If we found a match, update the current state to show it
|
|
141
|
-
if (match && allow({ editor, range: match.range })) {
|
|
142
|
-
next.active = true;
|
|
143
|
-
next.decorationId = prev.decorationId ? prev.decorationId : decorationId;
|
|
144
|
-
next.range = match.range;
|
|
145
|
-
next.query = match.query;
|
|
146
|
-
next.text = match.text;
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
next.active = false;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
next.active = false;
|
|
154
|
-
}
|
|
155
|
-
// Make sure to empty the range if suggestion is inactive
|
|
156
|
-
if (!next.active) {
|
|
157
|
-
next.decorationId = null;
|
|
158
|
-
next.range = {};
|
|
159
|
-
next.query = null;
|
|
160
|
-
next.text = null;
|
|
161
|
-
}
|
|
162
|
-
return next;
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
props: {
|
|
166
|
-
// Call the keydown hook if suggestion is active.
|
|
167
|
-
handleKeyDown(view, event) {
|
|
168
|
-
var _a;
|
|
169
|
-
const { active, range } = this.getState(view.state);
|
|
170
|
-
if (!active) {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
return ((_a = renderer === null || renderer === void 0 ? void 0 : renderer.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(renderer, { view, event, range })) || false;
|
|
174
|
-
},
|
|
175
|
-
// Setup decorator on the currently active suggestion.
|
|
176
|
-
decorations(state) {
|
|
177
|
-
const { active, range, decorationId } = this.getState(state);
|
|
178
|
-
if (!active) {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
return prosemirrorView.DecorationSet.create(state.doc, [
|
|
182
|
-
prosemirrorView.Decoration.inline(range.from, range.to, {
|
|
183
|
-
nodeName: decorationTag,
|
|
184
|
-
class: decorationClass,
|
|
185
|
-
'data-decoration-id': decorationId,
|
|
186
|
-
}),
|
|
187
|
-
]);
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
exports.Suggestion = Suggestion;
|
|
194
|
-
exports.default = Suggestion;
|
|
195
|
-
exports.findSuggestionMatch = findSuggestionMatch;
|
|
196
|
-
|
|
197
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
198
|
-
|
|
199
|
-
})));
|
|
200
|
-
//# sourceMappingURL=tiptap-suggestion.umd.js.map
|