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