@tiptap/suggestion 2.0.0-beta.83 → 2.0.0-beta.87
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/tiptap-suggestion.cjs.js +11 -6
- package/dist/tiptap-suggestion.cjs.js.map +1 -1
- package/dist/tiptap-suggestion.esm.js +11 -6
- package/dist/tiptap-suggestion.esm.js.map +1 -1
- package/dist/tiptap-suggestion.umd.js +14 -10
- package/dist/tiptap-suggestion.umd.js.map +1 -1
- package/package.json +4 -4
- package/src/findSuggestionMatch.ts +2 -6
- package/src/suggestion.ts +11 -1
|
@@ -4,14 +4,11 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var prosemirrorState = require('prosemirror-state');
|
|
6
6
|
var prosemirrorView = require('prosemirror-view');
|
|
7
|
+
var core = require('@tiptap/core');
|
|
7
8
|
|
|
8
9
|
function findSuggestionMatch(config) {
|
|
9
10
|
const { char, allowSpaces, prefixSpace, startOfLine, $position, } = config;
|
|
10
|
-
|
|
11
|
-
const escapedChar = char
|
|
12
|
-
.split('')
|
|
13
|
-
.map(c => `\\${c}`)
|
|
14
|
-
.join('');
|
|
11
|
+
const escapedChar = core.escapeForRegEx(char);
|
|
15
12
|
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
16
13
|
const prefix = startOfLine ? '^' : '';
|
|
17
14
|
const regexp = allowSpaces
|
|
@@ -59,6 +56,7 @@ function findSuggestionMatch(config) {
|
|
|
59
56
|
|
|
60
57
|
const SuggestionPluginKey = new prosemirrorState.PluginKey('suggestion');
|
|
61
58
|
function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allowSpaces = false, prefixSpace = true, startOfLine = false, decorationTag = 'span', decorationClass = 'suggestion', command = () => null, items = () => [], render = () => ({}), allow = () => true, }) {
|
|
59
|
+
let props;
|
|
62
60
|
const renderer = render === null || render === void 0 ? void 0 : render();
|
|
63
61
|
return new prosemirrorState.Plugin({
|
|
64
62
|
key: pluginKey,
|
|
@@ -84,7 +82,7 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
84
82
|
? prev
|
|
85
83
|
: next;
|
|
86
84
|
const decorationNode = document.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
87
|
-
|
|
85
|
+
props = {
|
|
88
86
|
editor,
|
|
89
87
|
range: state.range,
|
|
90
88
|
query: state.query,
|
|
@@ -126,6 +124,13 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
126
124
|
(_e = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
|
|
127
125
|
}
|
|
128
126
|
},
|
|
127
|
+
destroy: () => {
|
|
128
|
+
var _a;
|
|
129
|
+
if (!props) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
(_a = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _a === void 0 ? void 0 : _a.call(renderer, props);
|
|
133
|
+
},
|
|
129
134
|
};
|
|
130
135
|
},
|
|
131
136
|
state: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-suggestion.cjs.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\n prefixSpace: boolean,\n startOfLine: boolean,\n $position: ResolvedPos,\n}\n\nexport type SuggestionMatch = {\n range: Range,\n query: string,\n text: string,\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const {\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position,\n } = config\n\n // Matching expressions used for later\n const escapedChar = char\n .split('')\n .map(c => `\\\\${c}`)\n .join('')\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const isTopLevelNode = $position.depth <= 0\n const textFrom = isTopLevelNode\n ? 0\n : $position.before()\n const textTo = $position.pos\n const text = $position.doc.textBetween(textFrom, textTo, '\\0', '\\0')\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsSpace = /^[\\s\\0]?$/.test(matchPrefix)\n\n if (prefixSpace && !matchPrefixIsSpace) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = match.index + $position.start()\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => any[] | Promise<any[]>,\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: (() => DOMRect) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n const renderer = render?.()\n\n return new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart\n ? prev\n : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n const props: SuggestionProps = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: (handleChange || handleStart)\n ? await items({\n editor,\n query: state.query,\n })\n : [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: decorationNode\n ? () => {\n // because of `items` can be asynchrounous we’ll search for the current docoration node\n const { decorationId } = this.key?.getState(editor.state)\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n // @ts-ignore-error\n return currentDecorationNode.getBoundingClientRect()\n }\n : null,\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n composing: false,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev) {\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if there is no selection\n // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (empty || editor.view.composing) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":["PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
|
|
1
|
+
{"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 { Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => any[] | Promise<any[]>,\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: (() => DOMRect) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n 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) {\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if there is no selection\n // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (empty || editor.view.composing) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":["escapeForRegEx","PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { PluginKey, Plugin } from 'prosemirror-state';
|
|
2
2
|
import { DecorationSet, Decoration } from 'prosemirror-view';
|
|
3
|
+
import { escapeForRegEx } from '@tiptap/core';
|
|
3
4
|
|
|
4
5
|
function findSuggestionMatch(config) {
|
|
5
6
|
const { char, allowSpaces, prefixSpace, startOfLine, $position, } = config;
|
|
6
|
-
|
|
7
|
-
const escapedChar = char
|
|
8
|
-
.split('')
|
|
9
|
-
.map(c => `\\${c}`)
|
|
10
|
-
.join('');
|
|
7
|
+
const escapedChar = escapeForRegEx(char);
|
|
11
8
|
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
12
9
|
const prefix = startOfLine ? '^' : '';
|
|
13
10
|
const regexp = allowSpaces
|
|
@@ -55,6 +52,7 @@ function findSuggestionMatch(config) {
|
|
|
55
52
|
|
|
56
53
|
const SuggestionPluginKey = new PluginKey('suggestion');
|
|
57
54
|
function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allowSpaces = false, prefixSpace = true, startOfLine = false, decorationTag = 'span', decorationClass = 'suggestion', command = () => null, items = () => [], render = () => ({}), allow = () => true, }) {
|
|
55
|
+
let props;
|
|
58
56
|
const renderer = render === null || render === void 0 ? void 0 : render();
|
|
59
57
|
return new Plugin({
|
|
60
58
|
key: pluginKey,
|
|
@@ -80,7 +78,7 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
80
78
|
? prev
|
|
81
79
|
: next;
|
|
82
80
|
const decorationNode = document.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
83
|
-
|
|
81
|
+
props = {
|
|
84
82
|
editor,
|
|
85
83
|
range: state.range,
|
|
86
84
|
query: state.query,
|
|
@@ -122,6 +120,13 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
|
|
|
122
120
|
(_e = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
|
|
123
121
|
}
|
|
124
122
|
},
|
|
123
|
+
destroy: () => {
|
|
124
|
+
var _a;
|
|
125
|
+
if (!props) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
(_a = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _a === void 0 ? void 0 : _a.call(renderer, props);
|
|
129
|
+
},
|
|
125
130
|
};
|
|
126
131
|
},
|
|
127
132
|
state: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-suggestion.esm.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\n prefixSpace: boolean,\n startOfLine: boolean,\n $position: ResolvedPos,\n}\n\nexport type SuggestionMatch = {\n range: Range,\n query: string,\n text: string,\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const {\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position,\n } = config\n\n // Matching expressions used for later\n const escapedChar = char\n .split('')\n .map(c => `\\\\${c}`)\n .join('')\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const isTopLevelNode = $position.depth <= 0\n const textFrom = isTopLevelNode\n ? 0\n : $position.before()\n const textTo = $position.pos\n const text = $position.doc.textBetween(textFrom, textTo, '\\0', '\\0')\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsSpace = /^[\\s\\0]?$/.test(matchPrefix)\n\n if (prefixSpace && !matchPrefixIsSpace) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = match.index + $position.start()\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => any[] | Promise<any[]>,\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: (() => DOMRect) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n const renderer = render?.()\n\n return new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart\n ? prev\n : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n const props: SuggestionProps = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: (handleChange || handleStart)\n ? await items({\n editor,\n query: state.query,\n })\n : [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: decorationNode\n ? () => {\n // because of `items` can be asynchrounous we’ll search for the current docoration node\n const { decorationId } = this.key?.getState(editor.state)\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n // @ts-ignore-error\n return currentDecorationNode.getBoundingClientRect()\n }\n : null,\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n composing: false,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev) {\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if there is no selection\n // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (empty || editor.view.composing) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":[],"mappings":";;;SAiBgB,mBAAmB,CAAC,MAAe;IACjD,MAAM,EACJ,IAAI,EACJ,WAAW,EACX,WAAW,EACX,WAAW,EACX,SAAS,GACV,GAAG,MAAM,CAAA;;IAGV,MAAM,WAAW,GAAG,IAAI;SACrB,KAAK,CAAC,EAAE,CAAC;SACT,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;SAClB,IAAI,CAAC,EAAE,CAAC,CAAA;IACX,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAA;IAC/C,MAAM,MAAM,GAAG,WAAW,GAAG,GAAG,GAAG,EAAE,CAAA;IACrC,MAAM,MAAM,GAAG,WAAW;UACtB,IAAI,MAAM,CAAC,GAAG,MAAM,GAAG,WAAW,YAAY,WAAW,KAAK,EAAE,IAAI,CAAC;UACrE,IAAI,MAAM,CAAC,GAAG,MAAM,SAAS,WAAW,QAAQ,WAAW,IAAI,EAAE,IAAI,CAAC,CAAA;IAE1E,MAAM,cAAc,GAAG,SAAS,CAAC,KAAK,IAAI,CAAC,CAAA;IAC3C,MAAM,QAAQ,GAAG,cAAc;UAC3B,CAAC;UACD,SAAS,CAAC,MAAM,EAAE,CAAA;IACtB,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAA;IAC5B,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IACpE,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;IAErD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE;QACpE,OAAO,IAAI,CAAA;KACZ;;;IAID,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;IAChF,MAAM,kBAAkB,GAAG,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAExD,IAAI,WAAW,IAAI,CAAC,kBAAkB,EAAE;QACtC,OAAO,IAAI,CAAA;KACZ;;IAGD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,EAAE,CAAA;IAC5C,IAAI,EAAE,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;;;IAI/B,IAAI,WAAW,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE;QAC1D,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAA;QACf,EAAE,IAAI,CAAC,CAAA;KACR;;IAGD,IAAI,IAAI,GAAG,SAAS,CAAC,GAAG,IAAI,EAAE,IAAI,SAAS,CAAC,GAAG,EAAE;QAC/C,OAAO;YACL,KAAK,EAAE;gBACL,IAAI;gBACJ,EAAE;aACH;YACD,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;YAClC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;SACf,CAAA;KACF;IAED,OAAO,IAAI,CAAA;AACb;;MC9Ba,mBAAmB,GAAG,IAAI,SAAS,CAAC,YAAY,EAAC;SAE9C,UAAU,CAAC,EACzB,SAAS,GAAG,mBAAmB,EAC/B,MAAM,EACN,IAAI,GAAG,GAAG,EACV,WAAW,GAAG,KAAK,EACnB,WAAW,GAAG,IAAI,EAClB,WAAW,GAAG,KAAK,EACnB,aAAa,GAAG,MAAM,EACtB,eAAe,GAAG,YAAY,EAC9B,OAAO,GAAG,MAAM,IAAI,EACpB,KAAK,GAAG,MAAM,EAAE,EAChB,MAAM,GAAG,OAAO,EAAE,CAAC,EACnB,KAAK,GAAG,MAAM,IAAI,GACA;IAElB,MAAM,QAAQ,GAAG,MAAM,aAAN,MAAM,uBAAN,MAAM,EAAI,CAAA;IAE3B,OAAO,IAAI,MAAM,CAAC;QAChB,GAAG,EAAE,SAAS;QAEd,IAAI;YACF,OAAO;gBACL,MAAM,EAAE,OAAO,IAAI,EAAE,SAAS;;oBAC5B,MAAM,IAAI,GAAG,MAAA,IAAI,CAAC,GAAG,0CAAE,QAAQ,CAAC,SAAS,CAAC,CAAA;oBAC1C,MAAM,IAAI,GAAG,MAAA,IAAI,CAAC,GAAG,0CAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;;oBAG3C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA;oBAC/E,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAA;oBAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAA;oBAC3C,MAAM,OAAO,GAAG,CAAC,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAA;oBACjE,MAAM,WAAW,GAAG,OAAO,IAAI,KAAK,CAAA;oBACpC,MAAM,YAAY,GAAG,OAAO,IAAI,CAAC,KAAK,CAAA;oBACtC,MAAM,UAAU,GAAG,OAAO,IAAI,KAAK,CAAA;;oBAGnC,IAAI,CAAC,WAAW,IAAI,CAAC,YAAY,IAAI,CAAC,UAAU,EAAE;wBAChD,OAAM;qBACP;oBAED,MAAM,KAAK,GAAG,UAAU,IAAI,CAAC,WAAW;0BACpC,IAAI;0BACJ,IAAI,CAAA;oBACR,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC,wBAAwB,KAAK,CAAC,YAAY,IAAI,CAAC,CAAA;oBAC7F,MAAM,KAAK,GAAoB;wBAC7B,MAAM;wBACN,KAAK,EAAE,KAAK,CAAC,KAAK;wBAClB,KAAK,EAAE,KAAK,CAAC,KAAK;wBAClB,IAAI,EAAE,KAAK,CAAC,IAAI;wBAChB,KAAK,EAAE,CAAC,YAAY,IAAI,WAAW;8BAC/B,MAAM,KAAK,CAAC;gCACZ,MAAM;gCACN,KAAK,EAAE,KAAK,CAAC,KAAK;6BACnB,CAAC;8BACA,EAAE;wBACN,OAAO,EAAE,YAAY;4BACnB,OAAO,CAAC;gCACN,MAAM;gCACN,KAAK,EAAE,KAAK,CAAC,KAAK;gCAClB,KAAK,EAAE,YAAY;6BACpB,CAAC,CAAA;yBACH;wBACD,cAAc;;;wBAGd,UAAU,EAAE,cAAc;8BACtB;;;gCAEA,MAAM,EAAE,YAAY,EAAE,GAAG,MAAA,IAAI,CAAC,GAAG,0CAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;gCACzD,MAAM,qBAAqB,GAAG,QAAQ,CAAC,aAAa,CAAC,wBAAwB,YAAY,IAAI,CAAC,CAAA;;gCAG9F,OAAO,qBAAqB,CAAC,qBAAqB,EAAE,CAAA;6BACrD;8BACC,IAAI;qBACT,CAAA;oBAED,IAAI,UAAU,EAAE;wBACd,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,MAAM,+CAAhB,QAAQ,EAAW,KAAK,CAAC,CAAA;qBAC1B;oBAED,IAAI,YAAY,EAAE;wBAChB,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,QAAQ,+CAAlB,QAAQ,EAAa,KAAK,CAAC,CAAA;qBAC5B;oBAED,IAAI,WAAW,EAAE;wBACf,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,OAAO,+CAAjB,QAAQ,EAAY,KAAK,CAAC,CAAA;qBAC3B;iBACF;aACF,CAAA;SACF;QAED,KAAK,EAAE;;YAEL,IAAI;gBACF,OAAO;oBACL,MAAM,EAAE,KAAK;oBACb,KAAK,EAAE,EAAE;oBACT,KAAK,EAAE,IAAI;oBACX,IAAI,EAAE,IAAI;oBACV,SAAS,EAAE,KAAK;iBACjB,CAAA;aACF;;YAGD,KAAK,CAAC,WAAW,EAAE,IAAI;gBACrB,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,IAAI,CAAA;gBACjC,MAAM,EAAE,SAAS,EAAE,GAAG,WAAW,CAAA;gBACjC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,SAAS,CAAA;gBACjC,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,CAAA;gBAExB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;;;gBAI1B,IAAI,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE;;oBAElC,IACE,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE;2BAC5C,CAAC,SAAS;2BACV,CAAC,IAAI,CAAC,SAAS,EAClB;wBACA,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;qBACpB;;oBAGD,MAAM,KAAK,GAAG,mBAAmB,CAAC;wBAChC,IAAI;wBACJ,WAAW;wBACX,WAAW;wBACX,WAAW;wBACX,SAAS,EAAE,SAAS,CAAC,KAAK;qBAC3B,CAAC,CAAA;oBACF,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,EAAE,CAAA;;oBAGnE,IAAI,KAAK,IAAI,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE;wBAClD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;wBAClB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;wBACxE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;wBACxB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;wBACxB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;qBACvB;yBAAM;wBACL,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;qBACpB;iBACF;qBAAM;oBACL,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;iBACpB;;gBAGD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;oBAChB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;oBACxB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAA;oBACf,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;oBACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;iBACjB;gBAED,OAAO,IAAI,CAAA;aACZ;SACF;QAED,KAAK,EAAE;;YAEL,aAAa,CAAC,IAAI,EAAE,KAAK;;gBACvB,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAEnD,IAAI,CAAC,MAAM,EAAE;oBACX,OAAO,KAAK,CAAA;iBACb;gBAED,OAAO,CAAA,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,SAAS,+CAAnB,QAAQ,EAAc,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,KAAI,KAAK,CAAA;aAC9D;;YAGD,WAAW,CAAC,KAAK;gBACf,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBAE5D,IAAI,CAAC,MAAM,EAAE;oBACX,OAAO,IAAI,CAAA;iBACZ;gBAED,OAAO,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;oBACrC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;wBACtC,QAAQ,EAAE,aAAa;wBACvB,KAAK,EAAE,eAAe;wBACtB,oBAAoB,EAAE,YAAY;qBACnC,CAAC;iBACH,CAAC,CAAA;aACH;SACF;KACF,CAAC,CAAA;AACJ;;;;"}
|
|
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 { Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => any[] | Promise<any[]>,\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: (() => DOMRect) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n 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) {\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if there is no selection\n // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (empty || editor.view.composing) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,12 @@
|
|
|
1
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';
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('prosemirror-state'), require('prosemirror-view'), require('@tiptap/core')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'prosemirror-state', 'prosemirror-view', '@tiptap/core'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/suggestion"] = {}, global.prosemirrorState, global.prosemirrorView, global.core));
|
|
5
|
+
})(this, (function (exports, prosemirrorState, prosemirrorView, core) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function findSuggestionMatch(config) {
|
|
8
8
|
const { char, allowSpaces, prefixSpace, startOfLine, $position, } = config;
|
|
9
|
-
|
|
10
|
-
const escapedChar = char
|
|
11
|
-
.split('')
|
|
12
|
-
.map(c => `\\${c}`)
|
|
13
|
-
.join('');
|
|
9
|
+
const escapedChar = core.escapeForRegEx(char);
|
|
14
10
|
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
15
11
|
const prefix = startOfLine ? '^' : '';
|
|
16
12
|
const regexp = allowSpaces
|
|
@@ -58,6 +54,7 @@
|
|
|
58
54
|
|
|
59
55
|
const SuggestionPluginKey = new prosemirrorState.PluginKey('suggestion');
|
|
60
56
|
function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allowSpaces = false, prefixSpace = true, startOfLine = false, decorationTag = 'span', decorationClass = 'suggestion', command = () => null, items = () => [], render = () => ({}), allow = () => true, }) {
|
|
57
|
+
let props;
|
|
61
58
|
const renderer = render === null || render === void 0 ? void 0 : render();
|
|
62
59
|
return new prosemirrorState.Plugin({
|
|
63
60
|
key: pluginKey,
|
|
@@ -83,7 +80,7 @@
|
|
|
83
80
|
? prev
|
|
84
81
|
: next;
|
|
85
82
|
const decorationNode = document.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
86
|
-
|
|
83
|
+
props = {
|
|
87
84
|
editor,
|
|
88
85
|
range: state.range,
|
|
89
86
|
query: state.query,
|
|
@@ -125,6 +122,13 @@
|
|
|
125
122
|
(_e = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
|
|
126
123
|
}
|
|
127
124
|
},
|
|
125
|
+
destroy: () => {
|
|
126
|
+
var _a;
|
|
127
|
+
if (!props) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
(_a = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _a === void 0 ? void 0 : _a.call(renderer, props);
|
|
131
|
+
},
|
|
128
132
|
};
|
|
129
133
|
},
|
|
130
134
|
state: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-suggestion.umd.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\n prefixSpace: boolean,\n startOfLine: boolean,\n $position: ResolvedPos,\n}\n\nexport type SuggestionMatch = {\n range: Range,\n query: string,\n text: string,\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const {\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position,\n } = config\n\n // Matching expressions used for later\n const escapedChar = char\n .split('')\n .map(c => `\\\\${c}`)\n .join('')\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const isTopLevelNode = $position.depth <= 0\n const textFrom = isTopLevelNode\n ? 0\n : $position.before()\n const textTo = $position.pos\n const text = $position.doc.textBetween(textFrom, textTo, '\\0', '\\0')\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsSpace = /^[\\s\\0]?$/.test(matchPrefix)\n\n if (prefixSpace && !matchPrefixIsSpace) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = match.index + $position.start()\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => any[] | Promise<any[]>,\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: (() => DOMRect) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n const renderer = render?.()\n\n return new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart\n ? prev\n : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n const props: SuggestionProps = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: (handleChange || handleStart)\n ? await items({\n editor,\n query: state.query,\n })\n : [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: decorationNode\n ? () => {\n // because of `items` can be asynchrounous we’ll search for the current docoration node\n const { decorationId } = this.key?.getState(editor.state)\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n // @ts-ignore-error\n return currentDecorationNode.getBoundingClientRect()\n }\n : null,\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n composing: false,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev) {\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if there is no selection\n // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (empty || editor.view.composing) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":["PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
|
|
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 { Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => any[] | Promise<any[]>,\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: (() => DOMRect) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n 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) {\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if there is no selection\n // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (empty || editor.view.composing) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":["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.87",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
"@tiptap/core": "^2.0.0-beta.1"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"prosemirror-model": "^1.
|
|
27
|
+
"prosemirror-model": "^1.16.1",
|
|
28
28
|
"prosemirror-state": "^1.3.4",
|
|
29
|
-
"prosemirror-view": "^1.23.
|
|
29
|
+
"prosemirror-view": "^1.23.5"
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
33
33
|
"url": "https://github.com/ueberdosis/tiptap",
|
|
34
34
|
"directory": "packages/suggestion"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "d71c27fe99e34d1906b4f094554beb86fc7287e1"
|
|
37
37
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Range } from '@tiptap/core'
|
|
1
|
+
import { Range, escapeForRegEx } from '@tiptap/core'
|
|
2
2
|
import { ResolvedPos } from 'prosemirror-model'
|
|
3
3
|
|
|
4
4
|
export interface Trigger {
|
|
@@ -24,11 +24,7 @@ export function findSuggestionMatch(config: Trigger): SuggestionMatch {
|
|
|
24
24
|
$position,
|
|
25
25
|
} = config
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
const escapedChar = char
|
|
29
|
-
.split('')
|
|
30
|
-
.map(c => `\\${c}`)
|
|
31
|
-
.join('')
|
|
27
|
+
const escapedChar = escapeForRegEx(char)
|
|
32
28
|
const suffix = new RegExp(`\\s${escapedChar}$`)
|
|
33
29
|
const prefix = startOfLine ? '^' : ''
|
|
34
30
|
const regexp = allowSpaces
|
package/src/suggestion.ts
CHANGED
|
@@ -67,6 +67,7 @@ export function Suggestion({
|
|
|
67
67
|
allow = () => true,
|
|
68
68
|
}: SuggestionOptions) {
|
|
69
69
|
|
|
70
|
+
let props: SuggestionProps | undefined
|
|
70
71
|
const renderer = render?.()
|
|
71
72
|
|
|
72
73
|
return new Plugin({
|
|
@@ -96,7 +97,8 @@ export function Suggestion({
|
|
|
96
97
|
? prev
|
|
97
98
|
: next
|
|
98
99
|
const decorationNode = document.querySelector(`[data-decoration-id="${state.decorationId}"]`)
|
|
99
|
-
|
|
100
|
+
|
|
101
|
+
props = {
|
|
100
102
|
editor,
|
|
101
103
|
range: state.range,
|
|
102
104
|
query: state.query,
|
|
@@ -141,6 +143,14 @@ export function Suggestion({
|
|
|
141
143
|
renderer?.onStart?.(props)
|
|
142
144
|
}
|
|
143
145
|
},
|
|
146
|
+
|
|
147
|
+
destroy: () => {
|
|
148
|
+
if (!props) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
renderer?.onExit?.(props)
|
|
153
|
+
},
|
|
144
154
|
}
|
|
145
155
|
},
|
|
146
156
|
|