@tiptap/suggestion 2.0.0-beta.90 → 2.0.0-beta.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/suggestion/src/suggestion.d.ts +13 -11
- package/dist/tiptap-suggestion.cjs.js +31 -23
- package/dist/tiptap-suggestion.cjs.js.map +1 -1
- package/dist/tiptap-suggestion.esm.js +31 -23
- package/dist/tiptap-suggestion.esm.js.map +1 -1
- package/dist/tiptap-suggestion.umd.js +31 -23
- package/dist/tiptap-suggestion.umd.js.map +1 -1
- package/package.json +7 -2
- package/src/findSuggestionMatch.ts +9 -8
- package/src/suggestion.ts +39 -25
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Editor, Range } from '@tiptap/core';
|
|
2
2
|
import { EditorState, Plugin, PluginKey } from 'prosemirror-state';
|
|
3
3
|
import { EditorView } from 'prosemirror-view';
|
|
4
|
-
export interface SuggestionOptions {
|
|
4
|
+
export interface SuggestionOptions<I = any> {
|
|
5
5
|
pluginKey?: PluginKey;
|
|
6
6
|
editor: Editor;
|
|
7
7
|
char?: string;
|
|
@@ -13,16 +13,18 @@ export interface SuggestionOptions {
|
|
|
13
13
|
command?: (props: {
|
|
14
14
|
editor: Editor;
|
|
15
15
|
range: Range;
|
|
16
|
-
props:
|
|
16
|
+
props: I;
|
|
17
17
|
}) => void;
|
|
18
18
|
items?: (props: {
|
|
19
19
|
query: string;
|
|
20
20
|
editor: Editor;
|
|
21
|
-
}) =>
|
|
21
|
+
}) => I[] | Promise<I[]>;
|
|
22
22
|
render?: () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
onBeforeStart?: (props: SuggestionProps<I>) => void;
|
|
24
|
+
onStart?: (props: SuggestionProps<I>) => void;
|
|
25
|
+
onBeforeUpdate?: (props: SuggestionProps<I>) => void;
|
|
26
|
+
onUpdate?: (props: SuggestionProps<I>) => void;
|
|
27
|
+
onExit?: (props: SuggestionProps<I>) => void;
|
|
26
28
|
onKeyDown?: (props: SuggestionKeyDownProps) => boolean;
|
|
27
29
|
};
|
|
28
30
|
allow?: (props: {
|
|
@@ -31,15 +33,15 @@ export interface SuggestionOptions {
|
|
|
31
33
|
range: Range;
|
|
32
34
|
}) => boolean;
|
|
33
35
|
}
|
|
34
|
-
export interface SuggestionProps {
|
|
36
|
+
export interface SuggestionProps<I = any> {
|
|
35
37
|
editor: Editor;
|
|
36
38
|
range: Range;
|
|
37
39
|
query: string;
|
|
38
40
|
text: string;
|
|
39
|
-
items:
|
|
40
|
-
command: (props:
|
|
41
|
+
items: I[];
|
|
42
|
+
command: (props: I) => void;
|
|
41
43
|
decorationNode: Element | null;
|
|
42
|
-
clientRect
|
|
44
|
+
clientRect?: (() => DOMRect | null) | null;
|
|
43
45
|
}
|
|
44
46
|
export interface SuggestionKeyDownProps {
|
|
45
47
|
view: EditorView;
|
|
@@ -47,4 +49,4 @@ export interface SuggestionKeyDownProps {
|
|
|
47
49
|
range: Range;
|
|
48
50
|
}
|
|
49
51
|
export declare const SuggestionPluginKey: PluginKey<any, any>;
|
|
50
|
-
export declare function Suggestion({ pluginKey, editor, char, allowSpaces, prefixSpace, startOfLine, decorationTag, decorationClass, command, items, render, allow, }: SuggestionOptions): Plugin<any, any>;
|
|
52
|
+
export declare function Suggestion<I = any>({ pluginKey, editor, char, allowSpaces, prefixSpace, startOfLine, decorationTag, decorationClass, command, items, render, allow, }: SuggestionOptions<I>): Plugin<any, any>;
|
|
@@ -7,6 +7,7 @@ var prosemirrorView = require('prosemirror-view');
|
|
|
7
7
|
var core = require('@tiptap/core');
|
|
8
8
|
|
|
9
9
|
function findSuggestionMatch(config) {
|
|
10
|
+
var _a;
|
|
10
11
|
const { char, allowSpaces, prefixSpace, startOfLine, $position, } = config;
|
|
11
12
|
const escapedChar = core.escapeForRegEx(char);
|
|
12
13
|
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
@@ -14,12 +15,11 @@ function findSuggestionMatch(config) {
|
|
|
14
15
|
const regexp = allowSpaces
|
|
15
16
|
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
|
|
16
17
|
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm');
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
|
|
18
|
+
const text = ((_a = $position.nodeBefore) === null || _a === void 0 ? void 0 : _a.isText) && $position.nodeBefore.text;
|
|
19
|
+
if (!text) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const textFrom = $position.pos - text.length;
|
|
23
23
|
const match = Array.from(text.matchAll(regexp)).pop();
|
|
24
24
|
if (!match || match.input === undefined || match.index === undefined) {
|
|
25
25
|
return null;
|
|
@@ -32,7 +32,7 @@ function findSuggestionMatch(config) {
|
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
34
|
// The absolute position of the match in the document
|
|
35
|
-
const from =
|
|
35
|
+
const from = textFrom + match.index;
|
|
36
36
|
let to = from + match[0].length;
|
|
37
37
|
// Edge case handling; if spaces are allowed and we're directly in between
|
|
38
38
|
// two triggers
|
|
@@ -63,7 +63,7 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
63
63
|
view() {
|
|
64
64
|
return {
|
|
65
65
|
update: async (view, prevState) => {
|
|
66
|
-
var _a, _b, _c, _d, _e;
|
|
66
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
67
67
|
const prev = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(prevState);
|
|
68
68
|
const next = (_b = this.key) === null || _b === void 0 ? void 0 : _b.getState(view.state);
|
|
69
69
|
// See how the state changed
|
|
@@ -87,12 +87,7 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
87
87
|
range: state.range,
|
|
88
88
|
query: state.query,
|
|
89
89
|
text: state.text,
|
|
90
|
-
items:
|
|
91
|
-
? await items({
|
|
92
|
-
editor,
|
|
93
|
-
query: state.query,
|
|
94
|
-
})
|
|
95
|
-
: [],
|
|
90
|
+
items: [],
|
|
96
91
|
command: commandProps => {
|
|
97
92
|
command({
|
|
98
93
|
editor,
|
|
@@ -107,21 +102,32 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
107
102
|
? () => {
|
|
108
103
|
var _a;
|
|
109
104
|
// because of `items` can be asynchrounous we’ll search for the current docoration node
|
|
110
|
-
const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state);
|
|
105
|
+
const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state); // eslint-disable-line
|
|
111
106
|
const currentDecorationNode = document.querySelector(`[data-decoration-id="${decorationId}"]`);
|
|
112
|
-
|
|
113
|
-
return currentDecorationNode.getBoundingClientRect();
|
|
107
|
+
return (currentDecorationNode === null || currentDecorationNode === void 0 ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
|
|
114
108
|
}
|
|
115
109
|
: null,
|
|
116
110
|
};
|
|
111
|
+
if (handleStart) {
|
|
112
|
+
(_c = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeStart) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
|
|
113
|
+
}
|
|
114
|
+
if (handleChange) {
|
|
115
|
+
(_d = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
|
|
116
|
+
}
|
|
117
|
+
if (handleChange || handleStart) {
|
|
118
|
+
props.items = await items({
|
|
119
|
+
editor,
|
|
120
|
+
query: state.query,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
117
123
|
if (handleExit) {
|
|
118
|
-
(
|
|
124
|
+
(_e = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
|
|
119
125
|
}
|
|
120
126
|
if (handleChange) {
|
|
121
|
-
(
|
|
127
|
+
(_f = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _f === void 0 ? void 0 : _f.call(renderer, props);
|
|
122
128
|
}
|
|
123
129
|
if (handleStart) {
|
|
124
|
-
(
|
|
130
|
+
(_g = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _g === void 0 ? void 0 : _g.call(renderer, props);
|
|
125
131
|
}
|
|
126
132
|
},
|
|
127
133
|
destroy: () => {
|
|
@@ -146,14 +152,16 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
146
152
|
},
|
|
147
153
|
// Apply changes to the plugin state from a view transaction.
|
|
148
154
|
apply(transaction, prev, oldState, state) {
|
|
155
|
+
const { isEditable } = editor;
|
|
149
156
|
const { composing } = editor.view;
|
|
150
157
|
const { selection } = transaction;
|
|
151
158
|
const { empty, from } = selection;
|
|
152
159
|
const next = { ...prev };
|
|
153
160
|
next.composing = composing;
|
|
154
|
-
// We can only be suggesting if
|
|
155
|
-
//
|
|
156
|
-
|
|
161
|
+
// We can only be suggesting if the view is editable, and:
|
|
162
|
+
// * there is no selection, or
|
|
163
|
+
// * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
|
|
164
|
+
if (isEditable && (empty || editor.view.composing)) {
|
|
157
165
|
// Reset active state if we just left the previous suggestion range
|
|
158
166
|
if ((from < prev.range.from || from > prev.range.to)
|
|
159
167
|
&& !composing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-suggestion.cjs.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range, escapeForRegEx } 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 const escapedChar = escapeForRegEx(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 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 { EditorState, 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 state: EditorState,\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 let props: SuggestionProps | undefined\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\n props = {\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 destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n 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, oldState, state) {\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, state, 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":["escapeForRegEx","PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tiptap-suggestion.cjs.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { escapeForRegEx, 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 const escapedChar = escapeForRegEx(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 text = $position.nodeBefore?.isText && $position.nodeBefore.text\n\n if (!text) {\n return null\n }\n\n const textFrom = $position.pos - text.length\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const 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 = textFrom + match.index\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { EditorState, Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\n\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions<I = any> {\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: I,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => I[] | Promise<I[]>,\n render?: () => {\n onBeforeStart?: (props: SuggestionProps<I>) => void\n onStart?: (props: SuggestionProps<I>) => void,\n onBeforeUpdate?: (props: SuggestionProps<I>) => void\n onUpdate?: (props: SuggestionProps<I>) => void,\n onExit?: (props: SuggestionProps<I>) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n state: EditorState,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps<I = any> {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: I[],\n command: (props: I) => void,\n decorationNode: Element | null,\n clientRect?: (() => DOMRect | null) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion<I = any>({\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<I>) {\n\n let props: SuggestionProps<I> | undefined\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\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: [],\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) // eslint-disable-line\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n return currentDecorationNode?.getBoundingClientRect() || null\n }\n : null,\n }\n\n if (handleStart) {\n renderer?.onBeforeStart?.(props)\n }\n\n if (handleChange) {\n renderer?.onBeforeUpdate?.(props)\n }\n\n if (handleChange || handleStart) {\n props.items = await items({\n editor,\n query: state.query,\n })\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n 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, oldState, state) {\n const { isEditable } = editor\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if the view is editable, and:\n // * there is no selection, or\n // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (isEditable && (empty || editor.view.composing)) {\n // Reset active state if we just left the previous suggestion range\n if (\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, state, 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":["escapeForRegEx","PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { DecorationSet, Decoration } from 'prosemirror-view';
|
|
|
3
3
|
import { escapeForRegEx } from '@tiptap/core';
|
|
4
4
|
|
|
5
5
|
function findSuggestionMatch(config) {
|
|
6
|
+
var _a;
|
|
6
7
|
const { char, allowSpaces, prefixSpace, startOfLine, $position, } = config;
|
|
7
8
|
const escapedChar = escapeForRegEx(char);
|
|
8
9
|
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
@@ -10,12 +11,11 @@ function findSuggestionMatch(config) {
|
|
|
10
11
|
const regexp = allowSpaces
|
|
11
12
|
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
|
|
12
13
|
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm');
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
|
|
14
|
+
const text = ((_a = $position.nodeBefore) === null || _a === void 0 ? void 0 : _a.isText) && $position.nodeBefore.text;
|
|
15
|
+
if (!text) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const textFrom = $position.pos - text.length;
|
|
19
19
|
const match = Array.from(text.matchAll(regexp)).pop();
|
|
20
20
|
if (!match || match.input === undefined || match.index === undefined) {
|
|
21
21
|
return null;
|
|
@@ -28,7 +28,7 @@ function findSuggestionMatch(config) {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
// The absolute position of the match in the document
|
|
31
|
-
const from =
|
|
31
|
+
const from = textFrom + match.index;
|
|
32
32
|
let to = from + match[0].length;
|
|
33
33
|
// Edge case handling; if spaces are allowed and we're directly in between
|
|
34
34
|
// two triggers
|
|
@@ -59,7 +59,7 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
59
59
|
view() {
|
|
60
60
|
return {
|
|
61
61
|
update: async (view, prevState) => {
|
|
62
|
-
var _a, _b, _c, _d, _e;
|
|
62
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
63
63
|
const prev = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(prevState);
|
|
64
64
|
const next = (_b = this.key) === null || _b === void 0 ? void 0 : _b.getState(view.state);
|
|
65
65
|
// See how the state changed
|
|
@@ -83,12 +83,7 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
83
83
|
range: state.range,
|
|
84
84
|
query: state.query,
|
|
85
85
|
text: state.text,
|
|
86
|
-
items:
|
|
87
|
-
? await items({
|
|
88
|
-
editor,
|
|
89
|
-
query: state.query,
|
|
90
|
-
})
|
|
91
|
-
: [],
|
|
86
|
+
items: [],
|
|
92
87
|
command: commandProps => {
|
|
93
88
|
command({
|
|
94
89
|
editor,
|
|
@@ -103,21 +98,32 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
103
98
|
? () => {
|
|
104
99
|
var _a;
|
|
105
100
|
// because of `items` can be asynchrounous we’ll search for the current docoration node
|
|
106
|
-
const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state);
|
|
101
|
+
const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state); // eslint-disable-line
|
|
107
102
|
const currentDecorationNode = document.querySelector(`[data-decoration-id="${decorationId}"]`);
|
|
108
|
-
|
|
109
|
-
return currentDecorationNode.getBoundingClientRect();
|
|
103
|
+
return (currentDecorationNode === null || currentDecorationNode === void 0 ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
|
|
110
104
|
}
|
|
111
105
|
: null,
|
|
112
106
|
};
|
|
107
|
+
if (handleStart) {
|
|
108
|
+
(_c = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeStart) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
|
|
109
|
+
}
|
|
110
|
+
if (handleChange) {
|
|
111
|
+
(_d = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
|
|
112
|
+
}
|
|
113
|
+
if (handleChange || handleStart) {
|
|
114
|
+
props.items = await items({
|
|
115
|
+
editor,
|
|
116
|
+
query: state.query,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
113
119
|
if (handleExit) {
|
|
114
|
-
(
|
|
120
|
+
(_e = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
|
|
115
121
|
}
|
|
116
122
|
if (handleChange) {
|
|
117
|
-
(
|
|
123
|
+
(_f = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _f === void 0 ? void 0 : _f.call(renderer, props);
|
|
118
124
|
}
|
|
119
125
|
if (handleStart) {
|
|
120
|
-
(
|
|
126
|
+
(_g = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _g === void 0 ? void 0 : _g.call(renderer, props);
|
|
121
127
|
}
|
|
122
128
|
},
|
|
123
129
|
destroy: () => {
|
|
@@ -142,14 +148,16 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
142
148
|
},
|
|
143
149
|
// Apply changes to the plugin state from a view transaction.
|
|
144
150
|
apply(transaction, prev, oldState, state) {
|
|
151
|
+
const { isEditable } = editor;
|
|
145
152
|
const { composing } = editor.view;
|
|
146
153
|
const { selection } = transaction;
|
|
147
154
|
const { empty, from } = selection;
|
|
148
155
|
const next = { ...prev };
|
|
149
156
|
next.composing = composing;
|
|
150
|
-
// We can only be suggesting if
|
|
151
|
-
//
|
|
152
|
-
|
|
157
|
+
// We can only be suggesting if the view is editable, and:
|
|
158
|
+
// * there is no selection, or
|
|
159
|
+
// * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
|
|
160
|
+
if (isEditable && (empty || editor.view.composing)) {
|
|
153
161
|
// Reset active state if we just left the previous suggestion range
|
|
154
162
|
if ((from < prev.range.from || from > prev.range.to)
|
|
155
163
|
&& !composing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-suggestion.esm.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range, escapeForRegEx } 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 const escapedChar = escapeForRegEx(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 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 { EditorState, 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 state: EditorState,\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 let props: SuggestionProps | undefined\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\n props = {\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 destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n 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, oldState, state) {\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, state, 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
|
+
{"version":3,"file":"tiptap-suggestion.esm.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { escapeForRegEx, 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 const escapedChar = escapeForRegEx(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 text = $position.nodeBefore?.isText && $position.nodeBefore.text\n\n if (!text) {\n return null\n }\n\n const textFrom = $position.pos - text.length\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const 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 = textFrom + match.index\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { EditorState, Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\n\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions<I = any> {\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: I,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => I[] | Promise<I[]>,\n render?: () => {\n onBeforeStart?: (props: SuggestionProps<I>) => void\n onStart?: (props: SuggestionProps<I>) => void,\n onBeforeUpdate?: (props: SuggestionProps<I>) => void\n onUpdate?: (props: SuggestionProps<I>) => void,\n onExit?: (props: SuggestionProps<I>) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n state: EditorState,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps<I = any> {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: I[],\n command: (props: I) => void,\n decorationNode: Element | null,\n clientRect?: (() => DOMRect | null) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion<I = any>({\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<I>) {\n\n let props: SuggestionProps<I> | undefined\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\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: [],\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) // eslint-disable-line\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n return currentDecorationNode?.getBoundingClientRect() || null\n }\n : null,\n }\n\n if (handleStart) {\n renderer?.onBeforeStart?.(props)\n }\n\n if (handleChange) {\n renderer?.onBeforeUpdate?.(props)\n }\n\n if (handleChange || handleStart) {\n props.items = await items({\n editor,\n query: state.query,\n })\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n 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, oldState, state) {\n const { isEditable } = editor\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if the view is editable, and:\n // * there is no selection, or\n // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (isEditable && (empty || editor.view.composing)) {\n // Reset active state if we just left the previous suggestion range\n if (\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, state, 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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"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
})(this, (function (exports, prosemirrorState, prosemirrorView, core) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function findSuggestionMatch(config) {
|
|
8
|
+
var _a;
|
|
8
9
|
const { char, allowSpaces, prefixSpace, startOfLine, $position, } = config;
|
|
9
10
|
const escapedChar = core.escapeForRegEx(char);
|
|
10
11
|
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
@@ -12,12 +13,11 @@
|
|
|
12
13
|
const regexp = allowSpaces
|
|
13
14
|
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
|
|
14
15
|
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm');
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
|
|
16
|
+
const text = ((_a = $position.nodeBefore) === null || _a === void 0 ? void 0 : _a.isText) && $position.nodeBefore.text;
|
|
17
|
+
if (!text) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const textFrom = $position.pos - text.length;
|
|
21
21
|
const match = Array.from(text.matchAll(regexp)).pop();
|
|
22
22
|
if (!match || match.input === undefined || match.index === undefined) {
|
|
23
23
|
return null;
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
32
32
|
// The absolute position of the match in the document
|
|
33
|
-
const from =
|
|
33
|
+
const from = textFrom + match.index;
|
|
34
34
|
let to = from + match[0].length;
|
|
35
35
|
// Edge case handling; if spaces are allowed and we're directly in between
|
|
36
36
|
// two triggers
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
view() {
|
|
62
62
|
return {
|
|
63
63
|
update: async (view, prevState) => {
|
|
64
|
-
var _a, _b, _c, _d, _e;
|
|
64
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
65
65
|
const prev = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(prevState);
|
|
66
66
|
const next = (_b = this.key) === null || _b === void 0 ? void 0 : _b.getState(view.state);
|
|
67
67
|
// See how the state changed
|
|
@@ -85,12 +85,7 @@
|
|
|
85
85
|
range: state.range,
|
|
86
86
|
query: state.query,
|
|
87
87
|
text: state.text,
|
|
88
|
-
items:
|
|
89
|
-
? await items({
|
|
90
|
-
editor,
|
|
91
|
-
query: state.query,
|
|
92
|
-
})
|
|
93
|
-
: [],
|
|
88
|
+
items: [],
|
|
94
89
|
command: commandProps => {
|
|
95
90
|
command({
|
|
96
91
|
editor,
|
|
@@ -105,21 +100,32 @@
|
|
|
105
100
|
? () => {
|
|
106
101
|
var _a;
|
|
107
102
|
// 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);
|
|
103
|
+
const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state); // eslint-disable-line
|
|
109
104
|
const currentDecorationNode = document.querySelector(`[data-decoration-id="${decorationId}"]`);
|
|
110
|
-
|
|
111
|
-
return currentDecorationNode.getBoundingClientRect();
|
|
105
|
+
return (currentDecorationNode === null || currentDecorationNode === void 0 ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
|
|
112
106
|
}
|
|
113
107
|
: null,
|
|
114
108
|
};
|
|
109
|
+
if (handleStart) {
|
|
110
|
+
(_c = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeStart) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
|
|
111
|
+
}
|
|
112
|
+
if (handleChange) {
|
|
113
|
+
(_d = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
|
|
114
|
+
}
|
|
115
|
+
if (handleChange || handleStart) {
|
|
116
|
+
props.items = await items({
|
|
117
|
+
editor,
|
|
118
|
+
query: state.query,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
115
121
|
if (handleExit) {
|
|
116
|
-
(
|
|
122
|
+
(_e = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
|
|
117
123
|
}
|
|
118
124
|
if (handleChange) {
|
|
119
|
-
(
|
|
125
|
+
(_f = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _f === void 0 ? void 0 : _f.call(renderer, props);
|
|
120
126
|
}
|
|
121
127
|
if (handleStart) {
|
|
122
|
-
(
|
|
128
|
+
(_g = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _g === void 0 ? void 0 : _g.call(renderer, props);
|
|
123
129
|
}
|
|
124
130
|
},
|
|
125
131
|
destroy: () => {
|
|
@@ -144,14 +150,16 @@
|
|
|
144
150
|
},
|
|
145
151
|
// Apply changes to the plugin state from a view transaction.
|
|
146
152
|
apply(transaction, prev, oldState, state) {
|
|
153
|
+
const { isEditable } = editor;
|
|
147
154
|
const { composing } = editor.view;
|
|
148
155
|
const { selection } = transaction;
|
|
149
156
|
const { empty, from } = selection;
|
|
150
157
|
const next = { ...prev };
|
|
151
158
|
next.composing = composing;
|
|
152
|
-
// We can only be suggesting if
|
|
153
|
-
//
|
|
154
|
-
|
|
159
|
+
// We can only be suggesting if the view is editable, and:
|
|
160
|
+
// * there is no selection, or
|
|
161
|
+
// * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
|
|
162
|
+
if (isEditable && (empty || editor.view.composing)) {
|
|
155
163
|
// Reset active state if we just left the previous suggestion range
|
|
156
164
|
if ((from < prev.range.from || from > prev.range.to)
|
|
157
165
|
&& !composing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-suggestion.umd.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range, escapeForRegEx } 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 const escapedChar = escapeForRegEx(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 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 { EditorState, 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 state: EditorState,\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 let props: SuggestionProps | undefined\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\n props = {\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 destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n 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, oldState, state) {\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, state, 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":["escapeForRegEx","PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tiptap-suggestion.umd.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { escapeForRegEx, 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 const escapedChar = escapeForRegEx(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 text = $position.nodeBefore?.isText && $position.nodeBefore.text\n\n if (!text) {\n return null\n }\n\n const textFrom = $position.pos - text.length\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const 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 = textFrom + match.index\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { EditorState, Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\n\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions<I = any> {\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: I,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => I[] | Promise<I[]>,\n render?: () => {\n onBeforeStart?: (props: SuggestionProps<I>) => void\n onStart?: (props: SuggestionProps<I>) => void,\n onBeforeUpdate?: (props: SuggestionProps<I>) => void\n onUpdate?: (props: SuggestionProps<I>) => void,\n onExit?: (props: SuggestionProps<I>) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n state: EditorState,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps<I = any> {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: I[],\n command: (props: I) => void,\n decorationNode: Element | null,\n clientRect?: (() => DOMRect | null) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion<I = any>({\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<I>) {\n\n let props: SuggestionProps<I> | undefined\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\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: [],\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) // eslint-disable-line\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n return currentDecorationNode?.getBoundingClientRect() || null\n }\n : null,\n }\n\n if (handleStart) {\n renderer?.onBeforeStart?.(props)\n }\n\n if (handleChange) {\n renderer?.onBeforeUpdate?.(props)\n }\n\n if (handleChange || handleStart) {\n props.items = await items({\n editor,\n query: state.query,\n })\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n 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, oldState, state) {\n const { isEditable } = editor\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if the view is editable, and:\n // * there is no selection, or\n // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (isEditable && (empty || editor.view.composing)) {\n // Reset active state if we just left the previous suggestion range\n if (\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, state, 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":["escapeForRegEx","PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
|
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.95",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"src",
|
|
21
21
|
"dist"
|
|
22
22
|
],
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/prosemirror-model": "^1.16.1",
|
|
25
|
+
"@types/prosemirror-state": "^1.2.8",
|
|
26
|
+
"@types/prosemirror-view": "^1.23.1"
|
|
27
|
+
},
|
|
23
28
|
"peerDependencies": {
|
|
24
29
|
"@tiptap/core": "^2.0.0-beta.1"
|
|
25
30
|
},
|
|
@@ -33,5 +38,5 @@
|
|
|
33
38
|
"url": "https://github.com/ueberdosis/tiptap",
|
|
34
39
|
"directory": "packages/suggestion"
|
|
35
40
|
},
|
|
36
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "591c0807a2ab5c34b4b7fe12c12511fe4f493ebd"
|
|
37
42
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { escapeForRegEx, Range } from '@tiptap/core'
|
|
2
2
|
import { ResolvedPos } from 'prosemirror-model'
|
|
3
3
|
|
|
4
4
|
export interface Trigger {
|
|
@@ -31,12 +31,13 @@ export function findSuggestionMatch(config: Trigger): SuggestionMatch {
|
|
|
31
31
|
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
|
|
32
32
|
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm')
|
|
33
33
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
const text = $position.nodeBefore?.isText && $position.nodeBefore.text
|
|
35
|
+
|
|
36
|
+
if (!text) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const textFrom = $position.pos - text.length
|
|
40
41
|
const match = Array.from(text.matchAll(regexp)).pop()
|
|
41
42
|
|
|
42
43
|
if (!match || match.input === undefined || match.index === undefined) {
|
|
@@ -53,7 +54,7 @@ export function findSuggestionMatch(config: Trigger): SuggestionMatch {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// The absolute position of the match in the document
|
|
56
|
-
const from =
|
|
57
|
+
const from = textFrom + match.index
|
|
57
58
|
let to = from + match[0].length
|
|
58
59
|
|
|
59
60
|
// Edge case handling; if spaces are allowed and we're directly in between
|
package/src/suggestion.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Editor, Range } from '@tiptap/core'
|
|
2
2
|
import { EditorState, Plugin, PluginKey } from 'prosemirror-state'
|
|
3
3
|
import { Decoration, DecorationSet, EditorView } from 'prosemirror-view'
|
|
4
|
+
|
|
4
5
|
import { findSuggestionMatch } from './findSuggestionMatch'
|
|
5
6
|
|
|
6
|
-
export interface SuggestionOptions {
|
|
7
|
+
export interface SuggestionOptions<I = any> {
|
|
7
8
|
pluginKey?: PluginKey,
|
|
8
9
|
editor: Editor,
|
|
9
10
|
char?: string,
|
|
@@ -15,16 +16,18 @@ export interface SuggestionOptions {
|
|
|
15
16
|
command?: (props: {
|
|
16
17
|
editor: Editor,
|
|
17
18
|
range: Range,
|
|
18
|
-
props:
|
|
19
|
+
props: I,
|
|
19
20
|
}) => void,
|
|
20
21
|
items?: (props: {
|
|
21
22
|
query: string,
|
|
22
23
|
editor: Editor,
|
|
23
|
-
}) =>
|
|
24
|
+
}) => I[] | Promise<I[]>,
|
|
24
25
|
render?: () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
onBeforeStart?: (props: SuggestionProps<I>) => void
|
|
27
|
+
onStart?: (props: SuggestionProps<I>) => void,
|
|
28
|
+
onBeforeUpdate?: (props: SuggestionProps<I>) => void
|
|
29
|
+
onUpdate?: (props: SuggestionProps<I>) => void,
|
|
30
|
+
onExit?: (props: SuggestionProps<I>) => void,
|
|
28
31
|
onKeyDown?: (props: SuggestionKeyDownProps) => boolean,
|
|
29
32
|
},
|
|
30
33
|
allow?: (props: {
|
|
@@ -34,15 +37,15 @@ export interface SuggestionOptions {
|
|
|
34
37
|
}) => boolean,
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
export interface SuggestionProps {
|
|
40
|
+
export interface SuggestionProps<I = any> {
|
|
38
41
|
editor: Editor,
|
|
39
42
|
range: Range,
|
|
40
43
|
query: string,
|
|
41
44
|
text: string,
|
|
42
|
-
items:
|
|
43
|
-
command: (props:
|
|
45
|
+
items: I[],
|
|
46
|
+
command: (props: I) => void,
|
|
44
47
|
decorationNode: Element | null,
|
|
45
|
-
clientRect
|
|
48
|
+
clientRect?: (() => DOMRect | null) | null,
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
export interface SuggestionKeyDownProps {
|
|
@@ -53,7 +56,7 @@ export interface SuggestionKeyDownProps {
|
|
|
53
56
|
|
|
54
57
|
export const SuggestionPluginKey = new PluginKey('suggestion')
|
|
55
58
|
|
|
56
|
-
export function Suggestion({
|
|
59
|
+
export function Suggestion<I = any>({
|
|
57
60
|
pluginKey = SuggestionPluginKey,
|
|
58
61
|
editor,
|
|
59
62
|
char = '@',
|
|
@@ -66,9 +69,9 @@ export function Suggestion({
|
|
|
66
69
|
items = () => [],
|
|
67
70
|
render = () => ({}),
|
|
68
71
|
allow = () => true,
|
|
69
|
-
}: SuggestionOptions) {
|
|
72
|
+
}: SuggestionOptions<I>) {
|
|
70
73
|
|
|
71
|
-
let props: SuggestionProps | undefined
|
|
74
|
+
let props: SuggestionProps<I> | undefined
|
|
72
75
|
const renderer = render?.()
|
|
73
76
|
|
|
74
77
|
return new Plugin({
|
|
@@ -104,12 +107,7 @@ export function Suggestion({
|
|
|
104
107
|
range: state.range,
|
|
105
108
|
query: state.query,
|
|
106
109
|
text: state.text,
|
|
107
|
-
items:
|
|
108
|
-
? await items({
|
|
109
|
-
editor,
|
|
110
|
-
query: state.query,
|
|
111
|
-
})
|
|
112
|
-
: [],
|
|
110
|
+
items: [],
|
|
113
111
|
command: commandProps => {
|
|
114
112
|
command({
|
|
115
113
|
editor,
|
|
@@ -123,15 +121,29 @@ export function Suggestion({
|
|
|
123
121
|
clientRect: decorationNode
|
|
124
122
|
? () => {
|
|
125
123
|
// because of `items` can be asynchrounous we’ll search for the current docoration node
|
|
126
|
-
const { decorationId } = this.key?.getState(editor.state)
|
|
124
|
+
const { decorationId } = this.key?.getState(editor.state) // eslint-disable-line
|
|
127
125
|
const currentDecorationNode = document.querySelector(`[data-decoration-id="${decorationId}"]`)
|
|
128
126
|
|
|
129
|
-
|
|
130
|
-
return currentDecorationNode.getBoundingClientRect()
|
|
127
|
+
return currentDecorationNode?.getBoundingClientRect() || null
|
|
131
128
|
}
|
|
132
129
|
: null,
|
|
133
130
|
}
|
|
134
131
|
|
|
132
|
+
if (handleStart) {
|
|
133
|
+
renderer?.onBeforeStart?.(props)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (handleChange) {
|
|
137
|
+
renderer?.onBeforeUpdate?.(props)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (handleChange || handleStart) {
|
|
141
|
+
props.items = await items({
|
|
142
|
+
editor,
|
|
143
|
+
query: state.query,
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
135
147
|
if (handleExit) {
|
|
136
148
|
renderer?.onExit?.(props)
|
|
137
149
|
}
|
|
@@ -169,6 +181,7 @@ export function Suggestion({
|
|
|
169
181
|
|
|
170
182
|
// Apply changes to the plugin state from a view transaction.
|
|
171
183
|
apply(transaction, prev, oldState, state) {
|
|
184
|
+
const { isEditable } = editor
|
|
172
185
|
const { composing } = editor.view
|
|
173
186
|
const { selection } = transaction
|
|
174
187
|
const { empty, from } = selection
|
|
@@ -176,9 +189,10 @@ export function Suggestion({
|
|
|
176
189
|
|
|
177
190
|
next.composing = composing
|
|
178
191
|
|
|
179
|
-
// We can only be suggesting if
|
|
180
|
-
//
|
|
181
|
-
|
|
192
|
+
// We can only be suggesting if the view is editable, and:
|
|
193
|
+
// * there is no selection, or
|
|
194
|
+
// * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
|
|
195
|
+
if (isEditable && (empty || editor.view.composing)) {
|
|
182
196
|
// Reset active state if we just left the previous suggestion range
|
|
183
197
|
if (
|
|
184
198
|
(from < prev.range.from || from > prev.range.to)
|