@tiptap/suggestion 2.0.0-beta.21 → 2.0.0-beta.211

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