@tiptap/extension-mention 3.0.0-beta.3 → 3.0.0-beta.4
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/index.cjs +112 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -9
- package/dist/index.d.ts +40 -9
- package/dist/index.js +111 -55
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/mention.ts +138 -72
- package/src/utils/get-default-suggestion-attributes.ts +89 -0
package/dist/index.cjs
CHANGED
|
@@ -31,64 +31,85 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
Mention: () => Mention,
|
|
34
|
-
MentionPluginKey: () => MentionPluginKey,
|
|
35
34
|
default: () => index_default
|
|
36
35
|
});
|
|
37
36
|
module.exports = __toCommonJS(index_exports);
|
|
38
37
|
|
|
39
38
|
// src/mention.ts
|
|
40
39
|
var import_core = require("@tiptap/core");
|
|
41
|
-
var
|
|
40
|
+
var import_model = require("@tiptap/pm/model");
|
|
42
41
|
var import_suggestion = __toESM(require("@tiptap/suggestion"), 1);
|
|
43
|
-
|
|
42
|
+
|
|
43
|
+
// src/utils/get-default-suggestion-attributes.ts
|
|
44
|
+
var import_state = require("@tiptap/pm/state");
|
|
45
|
+
function getSuggestionOptions({
|
|
46
|
+
editor: tiptapEditor,
|
|
47
|
+
overrideSuggestionOptions,
|
|
48
|
+
extensionName,
|
|
49
|
+
char = "@"
|
|
50
|
+
}) {
|
|
51
|
+
const pluginKey = new import_state.PluginKey();
|
|
52
|
+
return {
|
|
53
|
+
editor: tiptapEditor,
|
|
54
|
+
char,
|
|
55
|
+
pluginKey,
|
|
56
|
+
command: ({ editor, range, props }) => {
|
|
57
|
+
var _a, _b, _c;
|
|
58
|
+
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
59
|
+
const overrideSpace = (_a = nodeAfter == null ? void 0 : nodeAfter.text) == null ? void 0 : _a.startsWith(" ");
|
|
60
|
+
if (overrideSpace) {
|
|
61
|
+
range.to += 1;
|
|
62
|
+
}
|
|
63
|
+
editor.chain().focus().insertContentAt(range, [
|
|
64
|
+
{
|
|
65
|
+
type: extensionName,
|
|
66
|
+
attrs: { ...props, mentionSuggestionChar: char }
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: " "
|
|
71
|
+
}
|
|
72
|
+
]).run();
|
|
73
|
+
(_c = (_b = editor.view.dom.ownerDocument.defaultView) == null ? void 0 : _b.getSelection()) == null ? void 0 : _c.collapseToEnd();
|
|
74
|
+
},
|
|
75
|
+
allow: ({ state, range }) => {
|
|
76
|
+
const $from = state.doc.resolve(range.from);
|
|
77
|
+
const type = state.schema.nodes[extensionName];
|
|
78
|
+
const allow = !!$from.parent.type.contentMatch.matchType(type);
|
|
79
|
+
return allow;
|
|
80
|
+
},
|
|
81
|
+
...overrideSuggestionOptions
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/mention.ts
|
|
44
86
|
var Mention = import_core.Node.create({
|
|
45
87
|
name: "mention",
|
|
46
88
|
priority: 101,
|
|
89
|
+
addStorage() {
|
|
90
|
+
return {
|
|
91
|
+
suggestions: [],
|
|
92
|
+
getSuggestionFromChar: () => null
|
|
93
|
+
};
|
|
94
|
+
},
|
|
47
95
|
addOptions() {
|
|
48
96
|
return {
|
|
49
97
|
HTMLAttributes: {},
|
|
50
|
-
renderText({
|
|
98
|
+
renderText({ node, suggestion }) {
|
|
51
99
|
var _a;
|
|
52
|
-
return `${
|
|
100
|
+
return `${suggestion == null ? void 0 : suggestion.char}${(_a = node.attrs.label) != null ? _a : node.attrs.id}`;
|
|
53
101
|
},
|
|
54
102
|
deleteTriggerWithBackspace: false,
|
|
55
|
-
renderHTML({ options, node }) {
|
|
103
|
+
renderHTML({ options, node, suggestion }) {
|
|
56
104
|
var _a;
|
|
57
105
|
return [
|
|
58
106
|
"span",
|
|
59
107
|
(0, import_core.mergeAttributes)(this.HTMLAttributes, options.HTMLAttributes),
|
|
60
|
-
`${
|
|
108
|
+
`${suggestion == null ? void 0 : suggestion.char}${(_a = node.attrs.label) != null ? _a : node.attrs.id}`
|
|
61
109
|
];
|
|
62
110
|
},
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
pluginKey: MentionPluginKey,
|
|
66
|
-
command: ({ editor, range, props }) => {
|
|
67
|
-
var _a, _b, _c;
|
|
68
|
-
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
69
|
-
const overrideSpace = (_a = nodeAfter == null ? void 0 : nodeAfter.text) == null ? void 0 : _a.startsWith(" ");
|
|
70
|
-
if (overrideSpace) {
|
|
71
|
-
range.to += 1;
|
|
72
|
-
}
|
|
73
|
-
editor.chain().focus().insertContentAt(range, [
|
|
74
|
-
{
|
|
75
|
-
type: this.name,
|
|
76
|
-
attrs: props
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
type: "text",
|
|
80
|
-
text: " "
|
|
81
|
-
}
|
|
82
|
-
]).run();
|
|
83
|
-
(_c = (_b = editor.view.dom.ownerDocument.defaultView) == null ? void 0 : _b.getSelection()) == null ? void 0 : _c.collapseToEnd();
|
|
84
|
-
},
|
|
85
|
-
allow: ({ state, range }) => {
|
|
86
|
-
const $from = state.doc.resolve(range.from);
|
|
87
|
-
const type = state.schema.nodes[this.name];
|
|
88
|
-
const allow = !!$from.parent.type.contentMatch.matchType(type);
|
|
89
|
-
return allow;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
111
|
+
suggestions: [],
|
|
112
|
+
suggestion: {}
|
|
92
113
|
};
|
|
93
114
|
},
|
|
94
115
|
group: "inline",
|
|
@@ -120,6 +141,16 @@ var Mention = import_core.Node.create({
|
|
|
120
141
|
"data-label": attributes.label
|
|
121
142
|
};
|
|
122
143
|
}
|
|
144
|
+
},
|
|
145
|
+
// When there are multiple types of mentions, this attribute helps distinguish them
|
|
146
|
+
mentionSuggestionChar: {
|
|
147
|
+
default: "@",
|
|
148
|
+
parseHTML: (element) => element.getAttribute("data-mention-suggestion-char"),
|
|
149
|
+
renderHTML: (attributes) => {
|
|
150
|
+
return {
|
|
151
|
+
"data-mention-suggestion-char": attributes.mentionSuggestionChar
|
|
152
|
+
};
|
|
153
|
+
}
|
|
123
154
|
}
|
|
124
155
|
};
|
|
125
156
|
},
|
|
@@ -131,6 +162,8 @@ var Mention = import_core.Node.create({
|
|
|
131
162
|
];
|
|
132
163
|
},
|
|
133
164
|
renderHTML({ node, HTMLAttributes }) {
|
|
165
|
+
var _a, _b, _c;
|
|
166
|
+
const suggestion = (_c = (_b = (_a = this.editor) == null ? void 0 : _a.extensionStorage) == null ? void 0 : _b[this.name]) == null ? void 0 : _c.getSuggestionFromChar(node.attrs.mentionSuggestionChar);
|
|
134
167
|
if (this.options.renderLabel !== void 0) {
|
|
135
168
|
console.warn("renderLabel is deprecated use renderText and renderHTML instead");
|
|
136
169
|
return [
|
|
@@ -138,7 +171,8 @@ var Mention = import_core.Node.create({
|
|
|
138
171
|
(0, import_core.mergeAttributes)({ "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes),
|
|
139
172
|
this.options.renderLabel({
|
|
140
173
|
options: this.options,
|
|
141
|
-
node
|
|
174
|
+
node,
|
|
175
|
+
suggestion
|
|
142
176
|
})
|
|
143
177
|
];
|
|
144
178
|
}
|
|
@@ -150,7 +184,8 @@ var Mention = import_core.Node.create({
|
|
|
150
184
|
);
|
|
151
185
|
const html = this.options.renderHTML({
|
|
152
186
|
options: mergedOptions,
|
|
153
|
-
node
|
|
187
|
+
node,
|
|
188
|
+
suggestion
|
|
154
189
|
});
|
|
155
190
|
if (typeof html === "string") {
|
|
156
191
|
return ["span", (0, import_core.mergeAttributes)({ "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes), html];
|
|
@@ -158,17 +193,17 @@ var Mention = import_core.Node.create({
|
|
|
158
193
|
return html;
|
|
159
194
|
},
|
|
160
195
|
renderText({ node }) {
|
|
196
|
+
var _a, _b, _c;
|
|
197
|
+
const args = {
|
|
198
|
+
options: this.options,
|
|
199
|
+
node,
|
|
200
|
+
suggestion: (_c = (_b = (_a = this.editor) == null ? void 0 : _a.extensionStorage) == null ? void 0 : _b[this.name]) == null ? void 0 : _c.getSuggestionFromChar(node.attrs.mentionSuggestionChar)
|
|
201
|
+
};
|
|
161
202
|
if (this.options.renderLabel !== void 0) {
|
|
162
203
|
console.warn("renderLabel is deprecated use renderText and renderHTML instead");
|
|
163
|
-
return this.options.renderLabel(
|
|
164
|
-
options: this.options,
|
|
165
|
-
node
|
|
166
|
-
});
|
|
204
|
+
return this.options.renderLabel(args);
|
|
167
205
|
}
|
|
168
|
-
return this.options.renderText(
|
|
169
|
-
options: this.options,
|
|
170
|
-
node
|
|
171
|
-
});
|
|
206
|
+
return this.options.renderText(args);
|
|
172
207
|
},
|
|
173
208
|
addKeyboardShortcuts() {
|
|
174
209
|
return {
|
|
@@ -179,28 +214,49 @@ var Mention = import_core.Node.create({
|
|
|
179
214
|
if (!empty) {
|
|
180
215
|
return false;
|
|
181
216
|
}
|
|
217
|
+
let mentionNode = new import_model.Node();
|
|
218
|
+
let mentionPos = 0;
|
|
182
219
|
state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {
|
|
183
220
|
if (node.type.name === this.name) {
|
|
184
221
|
isMention = true;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
pos,
|
|
188
|
-
pos + node.nodeSize
|
|
189
|
-
);
|
|
222
|
+
mentionNode = node;
|
|
223
|
+
mentionPos = pos;
|
|
190
224
|
return false;
|
|
191
225
|
}
|
|
192
226
|
});
|
|
227
|
+
if (isMention) {
|
|
228
|
+
tr.insertText(
|
|
229
|
+
this.options.deleteTriggerWithBackspace ? "" : mentionNode.attrs.mentionSuggestionChar,
|
|
230
|
+
mentionPos,
|
|
231
|
+
mentionPos + mentionNode.nodeSize
|
|
232
|
+
);
|
|
233
|
+
}
|
|
193
234
|
return isMention;
|
|
194
235
|
})
|
|
195
236
|
};
|
|
196
237
|
},
|
|
197
238
|
addProseMirrorPlugins() {
|
|
198
|
-
return
|
|
199
|
-
|
|
239
|
+
return this.storage.suggestions.map(import_suggestion.default);
|
|
240
|
+
},
|
|
241
|
+
onBeforeCreate() {
|
|
242
|
+
this.storage.suggestions = (this.options.suggestions.length ? this.options.suggestions : [this.options.suggestion]).map(
|
|
243
|
+
(suggestion) => getSuggestionOptions({
|
|
200
244
|
editor: this.editor,
|
|
201
|
-
|
|
245
|
+
overrideSuggestionOptions: suggestion,
|
|
246
|
+
extensionName: this.name,
|
|
247
|
+
char: suggestion.char
|
|
202
248
|
})
|
|
203
|
-
|
|
249
|
+
);
|
|
250
|
+
this.storage.getSuggestionFromChar = (char) => {
|
|
251
|
+
const suggestion = this.storage.suggestions.find((s) => s.char === char);
|
|
252
|
+
if (suggestion) {
|
|
253
|
+
return suggestion;
|
|
254
|
+
}
|
|
255
|
+
if (this.storage.suggestions.length) {
|
|
256
|
+
return this.storage.suggestions[0];
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
};
|
|
204
260
|
}
|
|
205
261
|
});
|
|
206
262
|
|
|
@@ -208,7 +264,6 @@ var Mention = import_core.Node.create({
|
|
|
208
264
|
var index_default = Mention;
|
|
209
265
|
// Annotate the CommonJS export names for ESM import in node:
|
|
210
266
|
0 && (module.exports = {
|
|
211
|
-
Mention
|
|
212
|
-
MentionPluginKey
|
|
267
|
+
Mention
|
|
213
268
|
});
|
|
214
269
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/mention.ts"],"sourcesContent":["import { Mention } from './mention.js'\n\nexport * from './mention.js'\n\nexport default Mention\n","import { mergeAttributes, Node } from '@tiptap/core'\nimport type { DOMOutputSpec, Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { PluginKey } from '@tiptap/pm/state'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\nimport Suggestion from '@tiptap/suggestion'\n\n// See `addAttributes` below\nexport interface MentionNodeAttrs {\n /**\n * The identifier for the selected item that was mentioned, stored as a `data-id`\n * attribute.\n */\n id: string | null\n /**\n * The label to be rendered by the editor as the displayed text for this mentioned\n * item, if provided. Stored as a `data-label` attribute. See `renderLabel`.\n */\n label?: string | null\n}\n\nexport type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> = {\n /**\n * The HTML attributes for a mention node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * A function to render the label of a mention.\n * @deprecated use renderText and renderHTML instead\n * @param props The render props\n * @returns The label\n * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n */\n renderLabel?: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => string\n\n /**\n * A function to render the text of a mention.\n * @param props The render props\n * @returns The text\n * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n */\n renderText: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => string\n\n /**\n * A function to render the HTML of a mention.\n * @param props The render props\n * @returns The HTML as a ProseMirror DOM Output Spec\n * @example ({ options, node }) => ['span', { 'data-type': 'mention' }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]\n */\n renderHTML: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => DOMOutputSpec\n\n /**\n * Whether to delete the trigger character with backspace.\n * @default false\n */\n deleteTriggerWithBackspace: boolean\n\n /**\n * The suggestion options.\n * @default {}\n * @example { char: '@', pluginKey: MentionPluginKey, command: ({ editor, range, props }) => { ... } }\n */\n suggestion: Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>\n}\n\n/**\n * The plugin key for the mention plugin.\n * @default 'mention'\n */\nexport const MentionPluginKey = new PluginKey('mention')\n\n/**\n * This extension allows you to insert mentions into the editor.\n * @see https://www.tiptap.dev/api/extensions/mention\n */\nexport const Mention = Node.create<MentionOptions>({\n name: 'mention',\n\n priority: 101,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n renderText({ options, node }) {\n return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n },\n deleteTriggerWithBackspace: false,\n renderHTML({ options, node }) {\n return [\n 'span',\n mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),\n `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`,\n ]\n },\n suggestion: {\n char: '@',\n pluginKey: MentionPluginKey,\n command: ({ editor, range, props }) => {\n // increase range.to by one when the next node is of type \"text\"\n // and starts with a space character\n const nodeAfter = editor.view.state.selection.$to.nodeAfter\n const overrideSpace = nodeAfter?.text?.startsWith(' ')\n\n if (overrideSpace) {\n range.to += 1\n }\n\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: this.name,\n attrs: props,\n },\n {\n type: 'text',\n text: ' ',\n },\n ])\n .run()\n\n // get reference to `window` object from editor element, to support cross-frame JS usage\n editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd()\n },\n allow: ({ state, range }) => {\n const $from = state.doc.resolve(range.from)\n const type = state.schema.nodes[this.name]\n const allow = !!$from.parent.type.contentMatch.matchType(type)\n\n return allow\n },\n },\n }\n },\n\n group: 'inline',\n\n inline: true,\n\n selectable: false,\n\n atom: true,\n\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: element => element.getAttribute('data-id'),\n renderHTML: attributes => {\n if (!attributes.id) {\n return {}\n }\n\n return {\n 'data-id': attributes.id,\n }\n },\n },\n\n label: {\n default: null,\n parseHTML: element => element.getAttribute('data-label'),\n renderHTML: attributes => {\n if (!attributes.label) {\n return {}\n }\n\n return {\n 'data-label': attributes.label,\n }\n },\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: `span[data-type=\"${this.name}\"]`,\n },\n ]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n if (this.options.renderLabel !== undefined) {\n console.warn('renderLabel is deprecated use renderText and renderHTML instead')\n return [\n 'span',\n mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes),\n this.options.renderLabel({\n options: this.options,\n node,\n }),\n ]\n }\n const mergedOptions = { ...this.options }\n\n mergedOptions.HTMLAttributes = mergeAttributes(\n { 'data-type': this.name },\n this.options.HTMLAttributes,\n HTMLAttributes,\n )\n const html = this.options.renderHTML({\n options: mergedOptions,\n node,\n })\n\n if (typeof html === 'string') {\n return ['span', mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes), html]\n }\n return html\n },\n\n renderText({ node }) {\n if (this.options.renderLabel !== undefined) {\n console.warn('renderLabel is deprecated use renderText and renderHTML instead')\n return this.options.renderLabel({\n options: this.options,\n node,\n })\n }\n return this.options.renderText({\n options: this.options,\n node,\n })\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: () =>\n this.editor.commands.command(({ tr, state }) => {\n let isMention = false\n const { selection } = state\n const { empty, anchor } = selection\n\n if (!empty) {\n return false\n }\n\n state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {\n if (node.type.name === this.name) {\n isMention = true\n tr.insertText(\n this.options.deleteTriggerWithBackspace ? '' : this.options.suggestion.char || '',\n pos,\n pos + node.nodeSize,\n )\n\n return false\n }\n })\n\n return isMention\n }),\n }\n },\n\n addProseMirrorPlugins() {\n return [\n Suggestion({\n editor: this.editor,\n ...this.options.suggestion,\n }),\n ]\n },\n})\n"],"mappings":"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","names":["Suggestion"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/mention.ts","../src/utils/get-default-suggestion-attributes.ts"],"sourcesContent":["import { Mention } from './mention.js'\n\nexport * from './mention.js'\n\nexport default Mention\n","import { mergeAttributes, Node } from '@tiptap/core'\nimport type { DOMOutputSpec } from '@tiptap/pm/model'\nimport { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\nimport Suggestion from '@tiptap/suggestion'\n\nimport { getSuggestionOptions } from './utils/get-default-suggestion-attributes.js'\n\n// See `addAttributes` below\nexport interface MentionNodeAttrs {\n /**\n * The identifier for the selected item that was mentioned, stored as a `data-id`\n * attribute.\n */\n id: string | null\n /**\n * The label to be rendered by the editor as the displayed text for this mentioned\n * item, if provided. Stored as a `data-label` attribute. See `renderLabel`.\n */\n label?: string | null\n /**\n * The character that triggers the suggestion, stored as\n * `data-mention-suggestion-char` attribute.\n */\n mentionSuggestionChar?: string\n}\n\nexport interface MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> {\n /**\n * The HTML attributes for a mention node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * A function to render the label of a mention.\n * @deprecated use renderText and renderHTML instead\n * @param props The render props\n * @returns The label\n * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n */\n renderLabel?: (props: {\n options: MentionOptions<SuggestionItem, Attrs>\n node: ProseMirrorNode\n suggestion: SuggestionOptions | null\n }) => string\n\n /**\n * A function to render the text of a mention.\n * @param props The render props\n * @returns The text\n * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n */\n renderText: (props: {\n options: MentionOptions<SuggestionItem, Attrs>\n node: ProseMirrorNode\n suggestion: SuggestionOptions | null\n }) => string\n\n /**\n * A function to render the HTML of a mention.\n * @param props The render props\n * @returns The HTML as a ProseMirror DOM Output Spec\n * @example ({ options, node }) => ['span', { 'data-type': 'mention' }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]\n */\n renderHTML: (props: {\n options: MentionOptions<SuggestionItem, Attrs>\n node: ProseMirrorNode\n suggestion: SuggestionOptions | null\n }) => DOMOutputSpec\n\n /**\n * Whether to delete the trigger character with backspace.\n * @default false\n */\n deleteTriggerWithBackspace: boolean\n\n /**\n * The suggestion options, when you want to support multiple triggers.\n *\n * With this parameter, you can define multiple types of mention. For example, you can use the `@` character\n * to mention users and the `#` character to mention tags.\n *\n * @default [{ char: '@', pluginKey: MentionPluginKey }]\n * @example [{ char: '@', pluginKey: MentionPluginKey }, { char: '#', pluginKey: new PluginKey('hashtag') }]\n */\n suggestions: Array<Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>>\n\n /**\n * The suggestion options, when you want to support only one trigger. To support multiple triggers, use the\n * `suggestions` parameter instead.\n *\n * @default {}\n * @example { char: '@', pluginKey: MentionPluginKey, command: ({ editor, range, props }) => { ... } }\n */\n suggestion: Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>\n}\n\n/**\n * Storage properties or the Mention extension\n */\nexport interface MentionStorage<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> {\n /**\n * The list of suggestions that will trigger the mention.\n */\n suggestions: Array<SuggestionOptions<SuggestionItem, Attrs>>\n\n /**\n * Returns the suggestion options of the mention that has a given character trigger. If not\n * found, it returns the first suggestion.\n *\n * @param char The character that triggers the mention\n * @returns The suggestion options\n */\n getSuggestionFromChar: (char: string) => SuggestionOptions<SuggestionItem, Attrs> | null\n}\n\n/**\n * This extension allows you to insert mentions into the editor.\n * @see https://www.tiptap.dev/api/extensions/mention\n */\nexport const Mention = Node.create<MentionOptions, MentionStorage>({\n name: 'mention',\n\n priority: 101,\n\n addStorage() {\n return {\n suggestions: [],\n getSuggestionFromChar: () => null,\n }\n },\n\n addOptions() {\n return {\n HTMLAttributes: {},\n renderText({ node, suggestion }) {\n return `${suggestion?.char}${node.attrs.label ?? node.attrs.id}`\n },\n deleteTriggerWithBackspace: false,\n renderHTML({ options, node, suggestion }) {\n return [\n 'span',\n mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),\n `${suggestion?.char}${node.attrs.label ?? node.attrs.id}`,\n ]\n },\n suggestions: [],\n suggestion: {},\n }\n },\n\n group: 'inline',\n\n inline: true,\n\n selectable: false,\n\n atom: true,\n\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: element => element.getAttribute('data-id'),\n renderHTML: attributes => {\n if (!attributes.id) {\n return {}\n }\n\n return {\n 'data-id': attributes.id,\n }\n },\n },\n\n label: {\n default: null,\n parseHTML: element => element.getAttribute('data-label'),\n renderHTML: attributes => {\n if (!attributes.label) {\n return {}\n }\n\n return {\n 'data-label': attributes.label,\n }\n },\n },\n\n // When there are multiple types of mentions, this attribute helps distinguish them\n mentionSuggestionChar: {\n default: '@',\n parseHTML: element => element.getAttribute('data-mention-suggestion-char'),\n renderHTML: attributes => {\n return {\n 'data-mention-suggestion-char': attributes.mentionSuggestionChar,\n }\n },\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: `span[data-type=\"${this.name}\"]`,\n },\n ]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n // We cannot use the `this.storage` property here because, when accessed this method,\n // it returns the initial value of the extension storage\n const suggestion = (this.editor?.extensionStorage as unknown as Record<string, MentionStorage>)?.[\n this.name\n ]?.getSuggestionFromChar(node.attrs.mentionSuggestionChar)\n\n if (this.options.renderLabel !== undefined) {\n console.warn('renderLabel is deprecated use renderText and renderHTML instead')\n return [\n 'span',\n mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes),\n this.options.renderLabel({\n options: this.options,\n node,\n suggestion,\n }),\n ]\n }\n const mergedOptions = { ...this.options }\n\n mergedOptions.HTMLAttributes = mergeAttributes(\n { 'data-type': this.name },\n this.options.HTMLAttributes,\n HTMLAttributes,\n )\n\n const html = this.options.renderHTML({\n options: mergedOptions,\n node,\n suggestion,\n })\n\n if (typeof html === 'string') {\n return ['span', mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes), html]\n }\n return html\n },\n\n renderText({ node }) {\n const args = {\n options: this.options,\n node,\n suggestion: (this.editor?.extensionStorage as unknown as Record<string, MentionStorage>)?.[\n this.name\n ]?.getSuggestionFromChar(node.attrs.mentionSuggestionChar),\n }\n if (this.options.renderLabel !== undefined) {\n console.warn('renderLabel is deprecated use renderText and renderHTML instead')\n return this.options.renderLabel(args)\n }\n\n return this.options.renderText(args)\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: () =>\n this.editor.commands.command(({ tr, state }) => {\n let isMention = false\n const { selection } = state\n const { empty, anchor } = selection\n\n if (!empty) {\n return false\n }\n\n // Store node and position for later use\n let mentionNode = new ProseMirrorNode()\n let mentionPos = 0\n\n state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {\n if (node.type.name === this.name) {\n isMention = true\n mentionNode = node\n mentionPos = pos\n return false\n }\n })\n\n if (isMention) {\n tr.insertText(\n this.options.deleteTriggerWithBackspace ? '' : mentionNode.attrs.mentionSuggestionChar,\n mentionPos,\n mentionPos + mentionNode.nodeSize,\n )\n }\n\n return isMention\n }),\n }\n },\n\n addProseMirrorPlugins() {\n // Create a plugin for each suggestion configuration\n return this.storage.suggestions.map(Suggestion)\n },\n\n onBeforeCreate() {\n this.storage.suggestions = (\n this.options.suggestions.length ? this.options.suggestions : [this.options.suggestion]\n ).map(suggestion =>\n getSuggestionOptions({\n editor: this.editor,\n overrideSuggestionOptions: suggestion,\n extensionName: this.name,\n char: suggestion.char,\n }),\n )\n\n this.storage.getSuggestionFromChar = char => {\n const suggestion = this.storage.suggestions.find(s => s.char === char)\n if (suggestion) {\n return suggestion\n }\n if (this.storage.suggestions.length) {\n return this.storage.suggestions[0]\n }\n\n return null\n }\n },\n})\n","import type { Editor } from '@tiptap/core'\nimport { PluginKey } from '@tiptap/pm/state'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\n\n/**\n * Arguments for the `getSuggestionOptions` function\n * @see getSuggestionOptions\n */\nexport interface GetSuggestionOptionsOptions {\n /**\n * The Tiptap editor instance.\n */\n editor: Editor\n /**\n * The suggestion options configuration provided to the\n * `Mention` extension.\n */\n overrideSuggestionOptions: Omit<SuggestionOptions, 'editor'>\n /**\n * The name of the Mention extension\n */\n extensionName: string\n /**\n * The character that triggers the suggestion.\n * @default '@'\n */\n char?: string\n}\n\n/**\n * Returns the suggestion options for a trigger of the Mention extension. These\n * options are used to create a `Suggestion` ProseMirror plugin. Each plugin lets\n * you define a different trigger that opens the Mention menu. For example,\n * you can define a `@` trigger to mention users and a `#` trigger to mention\n * tags.\n *\n * @param param0 The configured options for the suggestion\n * @returns\n */\nexport function getSuggestionOptions({\n editor: tiptapEditor,\n overrideSuggestionOptions,\n extensionName,\n char = '@',\n}: GetSuggestionOptionsOptions): SuggestionOptions {\n const pluginKey = new PluginKey()\n\n return {\n editor: tiptapEditor,\n char,\n pluginKey,\n command: ({ editor, range, props }: { editor: any; range: any; props: any }) => {\n // increase range.to by one when the next node is of type \"text\"\n // and starts with a space character\n const nodeAfter = editor.view.state.selection.$to.nodeAfter\n const overrideSpace = nodeAfter?.text?.startsWith(' ')\n\n if (overrideSpace) {\n range.to += 1\n }\n\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: extensionName,\n attrs: { ...props, mentionSuggestionChar: char },\n },\n {\n type: 'text',\n text: ' ',\n },\n ])\n .run()\n\n // get reference to `window` object from editor element, to support cross-frame JS usage\n editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd()\n },\n allow: ({ state, range }: { state: any; range: any }) => {\n const $from = state.doc.resolve(range.from)\n const type = state.schema.nodes[extensionName]\n const allow = !!$from.parent.type.contentMatch.matchType(type)\n\n return allow\n },\n ...overrideSuggestionOptions,\n }\n}\n"],"mappings":"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","names":["ProseMirrorNode","Suggestion"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Node as Node$1 } from '@tiptap/core';
|
|
2
2
|
import { Node, DOMOutputSpec } from '@tiptap/pm/model';
|
|
3
|
-
import { PluginKey } from '@tiptap/pm/state';
|
|
4
3
|
import { SuggestionOptions } from '@tiptap/suggestion';
|
|
5
4
|
|
|
6
5
|
interface MentionNodeAttrs {
|
|
@@ -14,8 +13,13 @@ interface MentionNodeAttrs {
|
|
|
14
13
|
* item, if provided. Stored as a `data-label` attribute. See `renderLabel`.
|
|
15
14
|
*/
|
|
16
15
|
label?: string | null;
|
|
16
|
+
/**
|
|
17
|
+
* The character that triggers the suggestion, stored as
|
|
18
|
+
* `data-mention-suggestion-char` attribute.
|
|
19
|
+
*/
|
|
20
|
+
mentionSuggestionChar?: string;
|
|
17
21
|
}
|
|
18
|
-
|
|
22
|
+
interface MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> {
|
|
19
23
|
/**
|
|
20
24
|
* The HTML attributes for a mention node.
|
|
21
25
|
* @default {}
|
|
@@ -32,6 +36,7 @@ type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = Me
|
|
|
32
36
|
renderLabel?: (props: {
|
|
33
37
|
options: MentionOptions<SuggestionItem, Attrs>;
|
|
34
38
|
node: Node;
|
|
39
|
+
suggestion: SuggestionOptions | null;
|
|
35
40
|
}) => string;
|
|
36
41
|
/**
|
|
37
42
|
* A function to render the text of a mention.
|
|
@@ -42,6 +47,7 @@ type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = Me
|
|
|
42
47
|
renderText: (props: {
|
|
43
48
|
options: MentionOptions<SuggestionItem, Attrs>;
|
|
44
49
|
node: Node;
|
|
50
|
+
suggestion: SuggestionOptions | null;
|
|
45
51
|
}) => string;
|
|
46
52
|
/**
|
|
47
53
|
* A function to render the HTML of a mention.
|
|
@@ -52,6 +58,7 @@ type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = Me
|
|
|
52
58
|
renderHTML: (props: {
|
|
53
59
|
options: MentionOptions<SuggestionItem, Attrs>;
|
|
54
60
|
node: Node;
|
|
61
|
+
suggestion: SuggestionOptions | null;
|
|
55
62
|
}) => DOMOutputSpec;
|
|
56
63
|
/**
|
|
57
64
|
* Whether to delete the trigger character with backspace.
|
|
@@ -59,21 +66,45 @@ type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = Me
|
|
|
59
66
|
*/
|
|
60
67
|
deleteTriggerWithBackspace: boolean;
|
|
61
68
|
/**
|
|
62
|
-
* The suggestion options.
|
|
69
|
+
* The suggestion options, when you want to support multiple triggers.
|
|
70
|
+
*
|
|
71
|
+
* With this parameter, you can define multiple types of mention. For example, you can use the `@` character
|
|
72
|
+
* to mention users and the `#` character to mention tags.
|
|
73
|
+
*
|
|
74
|
+
* @default [{ char: '@', pluginKey: MentionPluginKey }]
|
|
75
|
+
* @example [{ char: '@', pluginKey: MentionPluginKey }, { char: '#', pluginKey: new PluginKey('hashtag') }]
|
|
76
|
+
*/
|
|
77
|
+
suggestions: Array<Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>>;
|
|
78
|
+
/**
|
|
79
|
+
* The suggestion options, when you want to support only one trigger. To support multiple triggers, use the
|
|
80
|
+
* `suggestions` parameter instead.
|
|
81
|
+
*
|
|
63
82
|
* @default {}
|
|
64
83
|
* @example { char: '@', pluginKey: MentionPluginKey, command: ({ editor, range, props }) => { ... } }
|
|
65
84
|
*/
|
|
66
85
|
suggestion: Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>;
|
|
67
|
-
}
|
|
86
|
+
}
|
|
68
87
|
/**
|
|
69
|
-
*
|
|
70
|
-
* @default 'mention'
|
|
88
|
+
* Storage properties or the Mention extension
|
|
71
89
|
*/
|
|
72
|
-
|
|
90
|
+
interface MentionStorage<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> {
|
|
91
|
+
/**
|
|
92
|
+
* The list of suggestions that will trigger the mention.
|
|
93
|
+
*/
|
|
94
|
+
suggestions: Array<SuggestionOptions<SuggestionItem, Attrs>>;
|
|
95
|
+
/**
|
|
96
|
+
* Returns the suggestion options of the mention that has a given character trigger. If not
|
|
97
|
+
* found, it returns the first suggestion.
|
|
98
|
+
*
|
|
99
|
+
* @param char The character that triggers the mention
|
|
100
|
+
* @returns The suggestion options
|
|
101
|
+
*/
|
|
102
|
+
getSuggestionFromChar: (char: string) => SuggestionOptions<SuggestionItem, Attrs> | null;
|
|
103
|
+
}
|
|
73
104
|
/**
|
|
74
105
|
* This extension allows you to insert mentions into the editor.
|
|
75
106
|
* @see https://www.tiptap.dev/api/extensions/mention
|
|
76
107
|
*/
|
|
77
|
-
declare const Mention: Node$1<MentionOptions<any, MentionNodeAttrs>, any
|
|
108
|
+
declare const Mention: Node$1<MentionOptions<any, MentionNodeAttrs>, MentionStorage<any, MentionNodeAttrs>>;
|
|
78
109
|
|
|
79
|
-
export { Mention, type MentionNodeAttrs, type MentionOptions,
|
|
110
|
+
export { Mention, type MentionNodeAttrs, type MentionOptions, type MentionStorage, Mention as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Node as Node$1 } from '@tiptap/core';
|
|
2
2
|
import { Node, DOMOutputSpec } from '@tiptap/pm/model';
|
|
3
|
-
import { PluginKey } from '@tiptap/pm/state';
|
|
4
3
|
import { SuggestionOptions } from '@tiptap/suggestion';
|
|
5
4
|
|
|
6
5
|
interface MentionNodeAttrs {
|
|
@@ -14,8 +13,13 @@ interface MentionNodeAttrs {
|
|
|
14
13
|
* item, if provided. Stored as a `data-label` attribute. See `renderLabel`.
|
|
15
14
|
*/
|
|
16
15
|
label?: string | null;
|
|
16
|
+
/**
|
|
17
|
+
* The character that triggers the suggestion, stored as
|
|
18
|
+
* `data-mention-suggestion-char` attribute.
|
|
19
|
+
*/
|
|
20
|
+
mentionSuggestionChar?: string;
|
|
17
21
|
}
|
|
18
|
-
|
|
22
|
+
interface MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> {
|
|
19
23
|
/**
|
|
20
24
|
* The HTML attributes for a mention node.
|
|
21
25
|
* @default {}
|
|
@@ -32,6 +36,7 @@ type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = Me
|
|
|
32
36
|
renderLabel?: (props: {
|
|
33
37
|
options: MentionOptions<SuggestionItem, Attrs>;
|
|
34
38
|
node: Node;
|
|
39
|
+
suggestion: SuggestionOptions | null;
|
|
35
40
|
}) => string;
|
|
36
41
|
/**
|
|
37
42
|
* A function to render the text of a mention.
|
|
@@ -42,6 +47,7 @@ type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = Me
|
|
|
42
47
|
renderText: (props: {
|
|
43
48
|
options: MentionOptions<SuggestionItem, Attrs>;
|
|
44
49
|
node: Node;
|
|
50
|
+
suggestion: SuggestionOptions | null;
|
|
45
51
|
}) => string;
|
|
46
52
|
/**
|
|
47
53
|
* A function to render the HTML of a mention.
|
|
@@ -52,6 +58,7 @@ type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = Me
|
|
|
52
58
|
renderHTML: (props: {
|
|
53
59
|
options: MentionOptions<SuggestionItem, Attrs>;
|
|
54
60
|
node: Node;
|
|
61
|
+
suggestion: SuggestionOptions | null;
|
|
55
62
|
}) => DOMOutputSpec;
|
|
56
63
|
/**
|
|
57
64
|
* Whether to delete the trigger character with backspace.
|
|
@@ -59,21 +66,45 @@ type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = Me
|
|
|
59
66
|
*/
|
|
60
67
|
deleteTriggerWithBackspace: boolean;
|
|
61
68
|
/**
|
|
62
|
-
* The suggestion options.
|
|
69
|
+
* The suggestion options, when you want to support multiple triggers.
|
|
70
|
+
*
|
|
71
|
+
* With this parameter, you can define multiple types of mention. For example, you can use the `@` character
|
|
72
|
+
* to mention users and the `#` character to mention tags.
|
|
73
|
+
*
|
|
74
|
+
* @default [{ char: '@', pluginKey: MentionPluginKey }]
|
|
75
|
+
* @example [{ char: '@', pluginKey: MentionPluginKey }, { char: '#', pluginKey: new PluginKey('hashtag') }]
|
|
76
|
+
*/
|
|
77
|
+
suggestions: Array<Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>>;
|
|
78
|
+
/**
|
|
79
|
+
* The suggestion options, when you want to support only one trigger. To support multiple triggers, use the
|
|
80
|
+
* `suggestions` parameter instead.
|
|
81
|
+
*
|
|
63
82
|
* @default {}
|
|
64
83
|
* @example { char: '@', pluginKey: MentionPluginKey, command: ({ editor, range, props }) => { ... } }
|
|
65
84
|
*/
|
|
66
85
|
suggestion: Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>;
|
|
67
|
-
}
|
|
86
|
+
}
|
|
68
87
|
/**
|
|
69
|
-
*
|
|
70
|
-
* @default 'mention'
|
|
88
|
+
* Storage properties or the Mention extension
|
|
71
89
|
*/
|
|
72
|
-
|
|
90
|
+
interface MentionStorage<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> {
|
|
91
|
+
/**
|
|
92
|
+
* The list of suggestions that will trigger the mention.
|
|
93
|
+
*/
|
|
94
|
+
suggestions: Array<SuggestionOptions<SuggestionItem, Attrs>>;
|
|
95
|
+
/**
|
|
96
|
+
* Returns the suggestion options of the mention that has a given character trigger. If not
|
|
97
|
+
* found, it returns the first suggestion.
|
|
98
|
+
*
|
|
99
|
+
* @param char The character that triggers the mention
|
|
100
|
+
* @returns The suggestion options
|
|
101
|
+
*/
|
|
102
|
+
getSuggestionFromChar: (char: string) => SuggestionOptions<SuggestionItem, Attrs> | null;
|
|
103
|
+
}
|
|
73
104
|
/**
|
|
74
105
|
* This extension allows you to insert mentions into the editor.
|
|
75
106
|
* @see https://www.tiptap.dev/api/extensions/mention
|
|
76
107
|
*/
|
|
77
|
-
declare const Mention: Node$1<MentionOptions<any, MentionNodeAttrs>, any
|
|
108
|
+
declare const Mention: Node$1<MentionOptions<any, MentionNodeAttrs>, MentionStorage<any, MentionNodeAttrs>>;
|
|
78
109
|
|
|
79
|
-
export { Mention, type MentionNodeAttrs, type MentionOptions,
|
|
110
|
+
export { Mention, type MentionNodeAttrs, type MentionOptions, type MentionStorage, Mention as default };
|
package/dist/index.js
CHANGED
|
@@ -1,56 +1,78 @@
|
|
|
1
1
|
// src/mention.ts
|
|
2
2
|
import { mergeAttributes, Node } from "@tiptap/core";
|
|
3
|
-
import {
|
|
3
|
+
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
|
4
4
|
import Suggestion from "@tiptap/suggestion";
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
// src/utils/get-default-suggestion-attributes.ts
|
|
7
|
+
import { PluginKey } from "@tiptap/pm/state";
|
|
8
|
+
function getSuggestionOptions({
|
|
9
|
+
editor: tiptapEditor,
|
|
10
|
+
overrideSuggestionOptions,
|
|
11
|
+
extensionName,
|
|
12
|
+
char = "@"
|
|
13
|
+
}) {
|
|
14
|
+
const pluginKey = new PluginKey();
|
|
15
|
+
return {
|
|
16
|
+
editor: tiptapEditor,
|
|
17
|
+
char,
|
|
18
|
+
pluginKey,
|
|
19
|
+
command: ({ editor, range, props }) => {
|
|
20
|
+
var _a, _b, _c;
|
|
21
|
+
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
22
|
+
const overrideSpace = (_a = nodeAfter == null ? void 0 : nodeAfter.text) == null ? void 0 : _a.startsWith(" ");
|
|
23
|
+
if (overrideSpace) {
|
|
24
|
+
range.to += 1;
|
|
25
|
+
}
|
|
26
|
+
editor.chain().focus().insertContentAt(range, [
|
|
27
|
+
{
|
|
28
|
+
type: extensionName,
|
|
29
|
+
attrs: { ...props, mentionSuggestionChar: char }
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: " "
|
|
34
|
+
}
|
|
35
|
+
]).run();
|
|
36
|
+
(_c = (_b = editor.view.dom.ownerDocument.defaultView) == null ? void 0 : _b.getSelection()) == null ? void 0 : _c.collapseToEnd();
|
|
37
|
+
},
|
|
38
|
+
allow: ({ state, range }) => {
|
|
39
|
+
const $from = state.doc.resolve(range.from);
|
|
40
|
+
const type = state.schema.nodes[extensionName];
|
|
41
|
+
const allow = !!$from.parent.type.contentMatch.matchType(type);
|
|
42
|
+
return allow;
|
|
43
|
+
},
|
|
44
|
+
...overrideSuggestionOptions
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/mention.ts
|
|
6
49
|
var Mention = Node.create({
|
|
7
50
|
name: "mention",
|
|
8
51
|
priority: 101,
|
|
52
|
+
addStorage() {
|
|
53
|
+
return {
|
|
54
|
+
suggestions: [],
|
|
55
|
+
getSuggestionFromChar: () => null
|
|
56
|
+
};
|
|
57
|
+
},
|
|
9
58
|
addOptions() {
|
|
10
59
|
return {
|
|
11
60
|
HTMLAttributes: {},
|
|
12
|
-
renderText({
|
|
61
|
+
renderText({ node, suggestion }) {
|
|
13
62
|
var _a;
|
|
14
|
-
return `${
|
|
63
|
+
return `${suggestion == null ? void 0 : suggestion.char}${(_a = node.attrs.label) != null ? _a : node.attrs.id}`;
|
|
15
64
|
},
|
|
16
65
|
deleteTriggerWithBackspace: false,
|
|
17
|
-
renderHTML({ options, node }) {
|
|
66
|
+
renderHTML({ options, node, suggestion }) {
|
|
18
67
|
var _a;
|
|
19
68
|
return [
|
|
20
69
|
"span",
|
|
21
70
|
mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),
|
|
22
|
-
`${
|
|
71
|
+
`${suggestion == null ? void 0 : suggestion.char}${(_a = node.attrs.label) != null ? _a : node.attrs.id}`
|
|
23
72
|
];
|
|
24
73
|
},
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
pluginKey: MentionPluginKey,
|
|
28
|
-
command: ({ editor, range, props }) => {
|
|
29
|
-
var _a, _b, _c;
|
|
30
|
-
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
31
|
-
const overrideSpace = (_a = nodeAfter == null ? void 0 : nodeAfter.text) == null ? void 0 : _a.startsWith(" ");
|
|
32
|
-
if (overrideSpace) {
|
|
33
|
-
range.to += 1;
|
|
34
|
-
}
|
|
35
|
-
editor.chain().focus().insertContentAt(range, [
|
|
36
|
-
{
|
|
37
|
-
type: this.name,
|
|
38
|
-
attrs: props
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
type: "text",
|
|
42
|
-
text: " "
|
|
43
|
-
}
|
|
44
|
-
]).run();
|
|
45
|
-
(_c = (_b = editor.view.dom.ownerDocument.defaultView) == null ? void 0 : _b.getSelection()) == null ? void 0 : _c.collapseToEnd();
|
|
46
|
-
},
|
|
47
|
-
allow: ({ state, range }) => {
|
|
48
|
-
const $from = state.doc.resolve(range.from);
|
|
49
|
-
const type = state.schema.nodes[this.name];
|
|
50
|
-
const allow = !!$from.parent.type.contentMatch.matchType(type);
|
|
51
|
-
return allow;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
74
|
+
suggestions: [],
|
|
75
|
+
suggestion: {}
|
|
54
76
|
};
|
|
55
77
|
},
|
|
56
78
|
group: "inline",
|
|
@@ -82,6 +104,16 @@ var Mention = Node.create({
|
|
|
82
104
|
"data-label": attributes.label
|
|
83
105
|
};
|
|
84
106
|
}
|
|
107
|
+
},
|
|
108
|
+
// When there are multiple types of mentions, this attribute helps distinguish them
|
|
109
|
+
mentionSuggestionChar: {
|
|
110
|
+
default: "@",
|
|
111
|
+
parseHTML: (element) => element.getAttribute("data-mention-suggestion-char"),
|
|
112
|
+
renderHTML: (attributes) => {
|
|
113
|
+
return {
|
|
114
|
+
"data-mention-suggestion-char": attributes.mentionSuggestionChar
|
|
115
|
+
};
|
|
116
|
+
}
|
|
85
117
|
}
|
|
86
118
|
};
|
|
87
119
|
},
|
|
@@ -93,6 +125,8 @@ var Mention = Node.create({
|
|
|
93
125
|
];
|
|
94
126
|
},
|
|
95
127
|
renderHTML({ node, HTMLAttributes }) {
|
|
128
|
+
var _a, _b, _c;
|
|
129
|
+
const suggestion = (_c = (_b = (_a = this.editor) == null ? void 0 : _a.extensionStorage) == null ? void 0 : _b[this.name]) == null ? void 0 : _c.getSuggestionFromChar(node.attrs.mentionSuggestionChar);
|
|
96
130
|
if (this.options.renderLabel !== void 0) {
|
|
97
131
|
console.warn("renderLabel is deprecated use renderText and renderHTML instead");
|
|
98
132
|
return [
|
|
@@ -100,7 +134,8 @@ var Mention = Node.create({
|
|
|
100
134
|
mergeAttributes({ "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes),
|
|
101
135
|
this.options.renderLabel({
|
|
102
136
|
options: this.options,
|
|
103
|
-
node
|
|
137
|
+
node,
|
|
138
|
+
suggestion
|
|
104
139
|
})
|
|
105
140
|
];
|
|
106
141
|
}
|
|
@@ -112,7 +147,8 @@ var Mention = Node.create({
|
|
|
112
147
|
);
|
|
113
148
|
const html = this.options.renderHTML({
|
|
114
149
|
options: mergedOptions,
|
|
115
|
-
node
|
|
150
|
+
node,
|
|
151
|
+
suggestion
|
|
116
152
|
});
|
|
117
153
|
if (typeof html === "string") {
|
|
118
154
|
return ["span", mergeAttributes({ "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes), html];
|
|
@@ -120,17 +156,17 @@ var Mention = Node.create({
|
|
|
120
156
|
return html;
|
|
121
157
|
},
|
|
122
158
|
renderText({ node }) {
|
|
159
|
+
var _a, _b, _c;
|
|
160
|
+
const args = {
|
|
161
|
+
options: this.options,
|
|
162
|
+
node,
|
|
163
|
+
suggestion: (_c = (_b = (_a = this.editor) == null ? void 0 : _a.extensionStorage) == null ? void 0 : _b[this.name]) == null ? void 0 : _c.getSuggestionFromChar(node.attrs.mentionSuggestionChar)
|
|
164
|
+
};
|
|
123
165
|
if (this.options.renderLabel !== void 0) {
|
|
124
166
|
console.warn("renderLabel is deprecated use renderText and renderHTML instead");
|
|
125
|
-
return this.options.renderLabel(
|
|
126
|
-
options: this.options,
|
|
127
|
-
node
|
|
128
|
-
});
|
|
167
|
+
return this.options.renderLabel(args);
|
|
129
168
|
}
|
|
130
|
-
return this.options.renderText(
|
|
131
|
-
options: this.options,
|
|
132
|
-
node
|
|
133
|
-
});
|
|
169
|
+
return this.options.renderText(args);
|
|
134
170
|
},
|
|
135
171
|
addKeyboardShortcuts() {
|
|
136
172
|
return {
|
|
@@ -141,28 +177,49 @@ var Mention = Node.create({
|
|
|
141
177
|
if (!empty) {
|
|
142
178
|
return false;
|
|
143
179
|
}
|
|
180
|
+
let mentionNode = new ProseMirrorNode();
|
|
181
|
+
let mentionPos = 0;
|
|
144
182
|
state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {
|
|
145
183
|
if (node.type.name === this.name) {
|
|
146
184
|
isMention = true;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
pos,
|
|
150
|
-
pos + node.nodeSize
|
|
151
|
-
);
|
|
185
|
+
mentionNode = node;
|
|
186
|
+
mentionPos = pos;
|
|
152
187
|
return false;
|
|
153
188
|
}
|
|
154
189
|
});
|
|
190
|
+
if (isMention) {
|
|
191
|
+
tr.insertText(
|
|
192
|
+
this.options.deleteTriggerWithBackspace ? "" : mentionNode.attrs.mentionSuggestionChar,
|
|
193
|
+
mentionPos,
|
|
194
|
+
mentionPos + mentionNode.nodeSize
|
|
195
|
+
);
|
|
196
|
+
}
|
|
155
197
|
return isMention;
|
|
156
198
|
})
|
|
157
199
|
};
|
|
158
200
|
},
|
|
159
201
|
addProseMirrorPlugins() {
|
|
160
|
-
return
|
|
161
|
-
|
|
202
|
+
return this.storage.suggestions.map(Suggestion);
|
|
203
|
+
},
|
|
204
|
+
onBeforeCreate() {
|
|
205
|
+
this.storage.suggestions = (this.options.suggestions.length ? this.options.suggestions : [this.options.suggestion]).map(
|
|
206
|
+
(suggestion) => getSuggestionOptions({
|
|
162
207
|
editor: this.editor,
|
|
163
|
-
|
|
208
|
+
overrideSuggestionOptions: suggestion,
|
|
209
|
+
extensionName: this.name,
|
|
210
|
+
char: suggestion.char
|
|
164
211
|
})
|
|
165
|
-
|
|
212
|
+
);
|
|
213
|
+
this.storage.getSuggestionFromChar = (char) => {
|
|
214
|
+
const suggestion = this.storage.suggestions.find((s) => s.char === char);
|
|
215
|
+
if (suggestion) {
|
|
216
|
+
return suggestion;
|
|
217
|
+
}
|
|
218
|
+
if (this.storage.suggestions.length) {
|
|
219
|
+
return this.storage.suggestions[0];
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
};
|
|
166
223
|
}
|
|
167
224
|
});
|
|
168
225
|
|
|
@@ -170,7 +227,6 @@ var Mention = Node.create({
|
|
|
170
227
|
var index_default = Mention;
|
|
171
228
|
export {
|
|
172
229
|
Mention,
|
|
173
|
-
MentionPluginKey,
|
|
174
230
|
index_default as default
|
|
175
231
|
};
|
|
176
232
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mention.ts","../src/index.ts"],"sourcesContent":["import { mergeAttributes, Node } from '@tiptap/core'\nimport type { DOMOutputSpec, Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { PluginKey } from '@tiptap/pm/state'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\nimport Suggestion from '@tiptap/suggestion'\n\n// See `addAttributes` below\nexport interface MentionNodeAttrs {\n /**\n * The identifier for the selected item that was mentioned, stored as a `data-id`\n * attribute.\n */\n id: string | null\n /**\n * The label to be rendered by the editor as the displayed text for this mentioned\n * item, if provided. Stored as a `data-label` attribute. See `renderLabel`.\n */\n label?: string | null\n}\n\nexport type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> = {\n /**\n * The HTML attributes for a mention node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * A function to render the label of a mention.\n * @deprecated use renderText and renderHTML instead\n * @param props The render props\n * @returns The label\n * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n */\n renderLabel?: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => string\n\n /**\n * A function to render the text of a mention.\n * @param props The render props\n * @returns The text\n * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n */\n renderText: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => string\n\n /**\n * A function to render the HTML of a mention.\n * @param props The render props\n * @returns The HTML as a ProseMirror DOM Output Spec\n * @example ({ options, node }) => ['span', { 'data-type': 'mention' }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]\n */\n renderHTML: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => DOMOutputSpec\n\n /**\n * Whether to delete the trigger character with backspace.\n * @default false\n */\n deleteTriggerWithBackspace: boolean\n\n /**\n * The suggestion options.\n * @default {}\n * @example { char: '@', pluginKey: MentionPluginKey, command: ({ editor, range, props }) => { ... } }\n */\n suggestion: Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>\n}\n\n/**\n * The plugin key for the mention plugin.\n * @default 'mention'\n */\nexport const MentionPluginKey = new PluginKey('mention')\n\n/**\n * This extension allows you to insert mentions into the editor.\n * @see https://www.tiptap.dev/api/extensions/mention\n */\nexport const Mention = Node.create<MentionOptions>({\n name: 'mention',\n\n priority: 101,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n renderText({ options, node }) {\n return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n },\n deleteTriggerWithBackspace: false,\n renderHTML({ options, node }) {\n return [\n 'span',\n mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),\n `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`,\n ]\n },\n suggestion: {\n char: '@',\n pluginKey: MentionPluginKey,\n command: ({ editor, range, props }) => {\n // increase range.to by one when the next node is of type \"text\"\n // and starts with a space character\n const nodeAfter = editor.view.state.selection.$to.nodeAfter\n const overrideSpace = nodeAfter?.text?.startsWith(' ')\n\n if (overrideSpace) {\n range.to += 1\n }\n\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: this.name,\n attrs: props,\n },\n {\n type: 'text',\n text: ' ',\n },\n ])\n .run()\n\n // get reference to `window` object from editor element, to support cross-frame JS usage\n editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd()\n },\n allow: ({ state, range }) => {\n const $from = state.doc.resolve(range.from)\n const type = state.schema.nodes[this.name]\n const allow = !!$from.parent.type.contentMatch.matchType(type)\n\n return allow\n },\n },\n }\n },\n\n group: 'inline',\n\n inline: true,\n\n selectable: false,\n\n atom: true,\n\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: element => element.getAttribute('data-id'),\n renderHTML: attributes => {\n if (!attributes.id) {\n return {}\n }\n\n return {\n 'data-id': attributes.id,\n }\n },\n },\n\n label: {\n default: null,\n parseHTML: element => element.getAttribute('data-label'),\n renderHTML: attributes => {\n if (!attributes.label) {\n return {}\n }\n\n return {\n 'data-label': attributes.label,\n }\n },\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: `span[data-type=\"${this.name}\"]`,\n },\n ]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n if (this.options.renderLabel !== undefined) {\n console.warn('renderLabel is deprecated use renderText and renderHTML instead')\n return [\n 'span',\n mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes),\n this.options.renderLabel({\n options: this.options,\n node,\n }),\n ]\n }\n const mergedOptions = { ...this.options }\n\n mergedOptions.HTMLAttributes = mergeAttributes(\n { 'data-type': this.name },\n this.options.HTMLAttributes,\n HTMLAttributes,\n )\n const html = this.options.renderHTML({\n options: mergedOptions,\n node,\n })\n\n if (typeof html === 'string') {\n return ['span', mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes), html]\n }\n return html\n },\n\n renderText({ node }) {\n if (this.options.renderLabel !== undefined) {\n console.warn('renderLabel is deprecated use renderText and renderHTML instead')\n return this.options.renderLabel({\n options: this.options,\n node,\n })\n }\n return this.options.renderText({\n options: this.options,\n node,\n })\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: () =>\n this.editor.commands.command(({ tr, state }) => {\n let isMention = false\n const { selection } = state\n const { empty, anchor } = selection\n\n if (!empty) {\n return false\n }\n\n state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {\n if (node.type.name === this.name) {\n isMention = true\n tr.insertText(\n this.options.deleteTriggerWithBackspace ? '' : this.options.suggestion.char || '',\n pos,\n pos + node.nodeSize,\n )\n\n return false\n }\n })\n\n return isMention\n }),\n }\n },\n\n addProseMirrorPlugins() {\n return [\n Suggestion({\n editor: this.editor,\n ...this.options.suggestion,\n }),\n ]\n },\n})\n","import { Mention } from './mention.js'\n\nexport * from './mention.js'\n\nexport default Mention\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/mention.ts","../src/utils/get-default-suggestion-attributes.ts","../src/index.ts"],"sourcesContent":["import { mergeAttributes, Node } from '@tiptap/core'\nimport type { DOMOutputSpec } from '@tiptap/pm/model'\nimport { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\nimport Suggestion from '@tiptap/suggestion'\n\nimport { getSuggestionOptions } from './utils/get-default-suggestion-attributes.js'\n\n// See `addAttributes` below\nexport interface MentionNodeAttrs {\n /**\n * The identifier for the selected item that was mentioned, stored as a `data-id`\n * attribute.\n */\n id: string | null\n /**\n * The label to be rendered by the editor as the displayed text for this mentioned\n * item, if provided. Stored as a `data-label` attribute. See `renderLabel`.\n */\n label?: string | null\n /**\n * The character that triggers the suggestion, stored as\n * `data-mention-suggestion-char` attribute.\n */\n mentionSuggestionChar?: string\n}\n\nexport interface MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> {\n /**\n * The HTML attributes for a mention node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * A function to render the label of a mention.\n * @deprecated use renderText and renderHTML instead\n * @param props The render props\n * @returns The label\n * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n */\n renderLabel?: (props: {\n options: MentionOptions<SuggestionItem, Attrs>\n node: ProseMirrorNode\n suggestion: SuggestionOptions | null\n }) => string\n\n /**\n * A function to render the text of a mention.\n * @param props The render props\n * @returns The text\n * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n */\n renderText: (props: {\n options: MentionOptions<SuggestionItem, Attrs>\n node: ProseMirrorNode\n suggestion: SuggestionOptions | null\n }) => string\n\n /**\n * A function to render the HTML of a mention.\n * @param props The render props\n * @returns The HTML as a ProseMirror DOM Output Spec\n * @example ({ options, node }) => ['span', { 'data-type': 'mention' }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]\n */\n renderHTML: (props: {\n options: MentionOptions<SuggestionItem, Attrs>\n node: ProseMirrorNode\n suggestion: SuggestionOptions | null\n }) => DOMOutputSpec\n\n /**\n * Whether to delete the trigger character with backspace.\n * @default false\n */\n deleteTriggerWithBackspace: boolean\n\n /**\n * The suggestion options, when you want to support multiple triggers.\n *\n * With this parameter, you can define multiple types of mention. For example, you can use the `@` character\n * to mention users and the `#` character to mention tags.\n *\n * @default [{ char: '@', pluginKey: MentionPluginKey }]\n * @example [{ char: '@', pluginKey: MentionPluginKey }, { char: '#', pluginKey: new PluginKey('hashtag') }]\n */\n suggestions: Array<Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>>\n\n /**\n * The suggestion options, when you want to support only one trigger. To support multiple triggers, use the\n * `suggestions` parameter instead.\n *\n * @default {}\n * @example { char: '@', pluginKey: MentionPluginKey, command: ({ editor, range, props }) => { ... } }\n */\n suggestion: Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>\n}\n\n/**\n * Storage properties or the Mention extension\n */\nexport interface MentionStorage<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> {\n /**\n * The list of suggestions that will trigger the mention.\n */\n suggestions: Array<SuggestionOptions<SuggestionItem, Attrs>>\n\n /**\n * Returns the suggestion options of the mention that has a given character trigger. If not\n * found, it returns the first suggestion.\n *\n * @param char The character that triggers the mention\n * @returns The suggestion options\n */\n getSuggestionFromChar: (char: string) => SuggestionOptions<SuggestionItem, Attrs> | null\n}\n\n/**\n * This extension allows you to insert mentions into the editor.\n * @see https://www.tiptap.dev/api/extensions/mention\n */\nexport const Mention = Node.create<MentionOptions, MentionStorage>({\n name: 'mention',\n\n priority: 101,\n\n addStorage() {\n return {\n suggestions: [],\n getSuggestionFromChar: () => null,\n }\n },\n\n addOptions() {\n return {\n HTMLAttributes: {},\n renderText({ node, suggestion }) {\n return `${suggestion?.char}${node.attrs.label ?? node.attrs.id}`\n },\n deleteTriggerWithBackspace: false,\n renderHTML({ options, node, suggestion }) {\n return [\n 'span',\n mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),\n `${suggestion?.char}${node.attrs.label ?? node.attrs.id}`,\n ]\n },\n suggestions: [],\n suggestion: {},\n }\n },\n\n group: 'inline',\n\n inline: true,\n\n selectable: false,\n\n atom: true,\n\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: element => element.getAttribute('data-id'),\n renderHTML: attributes => {\n if (!attributes.id) {\n return {}\n }\n\n return {\n 'data-id': attributes.id,\n }\n },\n },\n\n label: {\n default: null,\n parseHTML: element => element.getAttribute('data-label'),\n renderHTML: attributes => {\n if (!attributes.label) {\n return {}\n }\n\n return {\n 'data-label': attributes.label,\n }\n },\n },\n\n // When there are multiple types of mentions, this attribute helps distinguish them\n mentionSuggestionChar: {\n default: '@',\n parseHTML: element => element.getAttribute('data-mention-suggestion-char'),\n renderHTML: attributes => {\n return {\n 'data-mention-suggestion-char': attributes.mentionSuggestionChar,\n }\n },\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: `span[data-type=\"${this.name}\"]`,\n },\n ]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n // We cannot use the `this.storage` property here because, when accessed this method,\n // it returns the initial value of the extension storage\n const suggestion = (this.editor?.extensionStorage as unknown as Record<string, MentionStorage>)?.[\n this.name\n ]?.getSuggestionFromChar(node.attrs.mentionSuggestionChar)\n\n if (this.options.renderLabel !== undefined) {\n console.warn('renderLabel is deprecated use renderText and renderHTML instead')\n return [\n 'span',\n mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes),\n this.options.renderLabel({\n options: this.options,\n node,\n suggestion,\n }),\n ]\n }\n const mergedOptions = { ...this.options }\n\n mergedOptions.HTMLAttributes = mergeAttributes(\n { 'data-type': this.name },\n this.options.HTMLAttributes,\n HTMLAttributes,\n )\n\n const html = this.options.renderHTML({\n options: mergedOptions,\n node,\n suggestion,\n })\n\n if (typeof html === 'string') {\n return ['span', mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes), html]\n }\n return html\n },\n\n renderText({ node }) {\n const args = {\n options: this.options,\n node,\n suggestion: (this.editor?.extensionStorage as unknown as Record<string, MentionStorage>)?.[\n this.name\n ]?.getSuggestionFromChar(node.attrs.mentionSuggestionChar),\n }\n if (this.options.renderLabel !== undefined) {\n console.warn('renderLabel is deprecated use renderText and renderHTML instead')\n return this.options.renderLabel(args)\n }\n\n return this.options.renderText(args)\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: () =>\n this.editor.commands.command(({ tr, state }) => {\n let isMention = false\n const { selection } = state\n const { empty, anchor } = selection\n\n if (!empty) {\n return false\n }\n\n // Store node and position for later use\n let mentionNode = new ProseMirrorNode()\n let mentionPos = 0\n\n state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {\n if (node.type.name === this.name) {\n isMention = true\n mentionNode = node\n mentionPos = pos\n return false\n }\n })\n\n if (isMention) {\n tr.insertText(\n this.options.deleteTriggerWithBackspace ? '' : mentionNode.attrs.mentionSuggestionChar,\n mentionPos,\n mentionPos + mentionNode.nodeSize,\n )\n }\n\n return isMention\n }),\n }\n },\n\n addProseMirrorPlugins() {\n // Create a plugin for each suggestion configuration\n return this.storage.suggestions.map(Suggestion)\n },\n\n onBeforeCreate() {\n this.storage.suggestions = (\n this.options.suggestions.length ? this.options.suggestions : [this.options.suggestion]\n ).map(suggestion =>\n getSuggestionOptions({\n editor: this.editor,\n overrideSuggestionOptions: suggestion,\n extensionName: this.name,\n char: suggestion.char,\n }),\n )\n\n this.storage.getSuggestionFromChar = char => {\n const suggestion = this.storage.suggestions.find(s => s.char === char)\n if (suggestion) {\n return suggestion\n }\n if (this.storage.suggestions.length) {\n return this.storage.suggestions[0]\n }\n\n return null\n }\n },\n})\n","import type { Editor } from '@tiptap/core'\nimport { PluginKey } from '@tiptap/pm/state'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\n\n/**\n * Arguments for the `getSuggestionOptions` function\n * @see getSuggestionOptions\n */\nexport interface GetSuggestionOptionsOptions {\n /**\n * The Tiptap editor instance.\n */\n editor: Editor\n /**\n * The suggestion options configuration provided to the\n * `Mention` extension.\n */\n overrideSuggestionOptions: Omit<SuggestionOptions, 'editor'>\n /**\n * The name of the Mention extension\n */\n extensionName: string\n /**\n * The character that triggers the suggestion.\n * @default '@'\n */\n char?: string\n}\n\n/**\n * Returns the suggestion options for a trigger of the Mention extension. These\n * options are used to create a `Suggestion` ProseMirror plugin. Each plugin lets\n * you define a different trigger that opens the Mention menu. For example,\n * you can define a `@` trigger to mention users and a `#` trigger to mention\n * tags.\n *\n * @param param0 The configured options for the suggestion\n * @returns\n */\nexport function getSuggestionOptions({\n editor: tiptapEditor,\n overrideSuggestionOptions,\n extensionName,\n char = '@',\n}: GetSuggestionOptionsOptions): SuggestionOptions {\n const pluginKey = new PluginKey()\n\n return {\n editor: tiptapEditor,\n char,\n pluginKey,\n command: ({ editor, range, props }: { editor: any; range: any; props: any }) => {\n // increase range.to by one when the next node is of type \"text\"\n // and starts with a space character\n const nodeAfter = editor.view.state.selection.$to.nodeAfter\n const overrideSpace = nodeAfter?.text?.startsWith(' ')\n\n if (overrideSpace) {\n range.to += 1\n }\n\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: extensionName,\n attrs: { ...props, mentionSuggestionChar: char },\n },\n {\n type: 'text',\n text: ' ',\n },\n ])\n .run()\n\n // get reference to `window` object from editor element, to support cross-frame JS usage\n editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd()\n },\n allow: ({ state, range }: { state: any; range: any }) => {\n const $from = state.doc.resolve(range.from)\n const type = state.schema.nodes[extensionName]\n const allow = !!$from.parent.type.contentMatch.matchType(type)\n\n return allow\n },\n ...overrideSuggestionOptions,\n }\n}\n","import { Mention } from './mention.js'\n\nexport * from './mention.js'\n\nexport default Mention\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiptap/extension-mention",
|
|
3
3
|
"description": "mention extension for tiptap",
|
|
4
|
-
"version": "3.0.0-beta.
|
|
4
|
+
"version": "3.0.0-beta.4",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"dist"
|
|
32
32
|
],
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@tiptap/core": "3.0.0-beta.
|
|
35
|
-
"@tiptap/
|
|
36
|
-
"@tiptap/
|
|
34
|
+
"@tiptap/core": "3.0.0-beta.4",
|
|
35
|
+
"@tiptap/pm": "3.0.0-beta.4",
|
|
36
|
+
"@tiptap/suggestion": "3.0.0-beta.4"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@tiptap/core": "3.0.0-beta.
|
|
40
|
-
"@tiptap/suggestion": "3.0.0-beta.
|
|
41
|
-
"@tiptap/pm": "3.0.0-beta.
|
|
39
|
+
"@tiptap/core": "3.0.0-beta.4",
|
|
40
|
+
"@tiptap/suggestion": "3.0.0-beta.4",
|
|
41
|
+
"@tiptap/pm": "3.0.0-beta.4"
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
package/src/mention.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { mergeAttributes, Node } from '@tiptap/core'
|
|
2
|
-
import type { DOMOutputSpec
|
|
3
|
-
import {
|
|
2
|
+
import type { DOMOutputSpec } from '@tiptap/pm/model'
|
|
3
|
+
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
4
4
|
import type { SuggestionOptions } from '@tiptap/suggestion'
|
|
5
5
|
import Suggestion from '@tiptap/suggestion'
|
|
6
6
|
|
|
7
|
+
import { getSuggestionOptions } from './utils/get-default-suggestion-attributes.js'
|
|
8
|
+
|
|
7
9
|
// See `addAttributes` below
|
|
8
10
|
export interface MentionNodeAttrs {
|
|
9
11
|
/**
|
|
@@ -16,9 +18,14 @@ export interface MentionNodeAttrs {
|
|
|
16
18
|
* item, if provided. Stored as a `data-label` attribute. See `renderLabel`.
|
|
17
19
|
*/
|
|
18
20
|
label?: string | null
|
|
21
|
+
/**
|
|
22
|
+
* The character that triggers the suggestion, stored as
|
|
23
|
+
* `data-mention-suggestion-char` attribute.
|
|
24
|
+
*/
|
|
25
|
+
mentionSuggestionChar?: string
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
export
|
|
28
|
+
export interface MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> {
|
|
22
29
|
/**
|
|
23
30
|
* The HTML attributes for a mention node.
|
|
24
31
|
* @default {}
|
|
@@ -33,7 +40,11 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
|
|
|
33
40
|
* @returns The label
|
|
34
41
|
* @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
|
|
35
42
|
*/
|
|
36
|
-
renderLabel?: (props: {
|
|
43
|
+
renderLabel?: (props: {
|
|
44
|
+
options: MentionOptions<SuggestionItem, Attrs>
|
|
45
|
+
node: ProseMirrorNode
|
|
46
|
+
suggestion: SuggestionOptions | null
|
|
47
|
+
}) => string
|
|
37
48
|
|
|
38
49
|
/**
|
|
39
50
|
* A function to render the text of a mention.
|
|
@@ -41,7 +52,11 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
|
|
|
41
52
|
* @returns The text
|
|
42
53
|
* @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
|
|
43
54
|
*/
|
|
44
|
-
renderText: (props: {
|
|
55
|
+
renderText: (props: {
|
|
56
|
+
options: MentionOptions<SuggestionItem, Attrs>
|
|
57
|
+
node: ProseMirrorNode
|
|
58
|
+
suggestion: SuggestionOptions | null
|
|
59
|
+
}) => string
|
|
45
60
|
|
|
46
61
|
/**
|
|
47
62
|
* A function to render the HTML of a mention.
|
|
@@ -49,7 +64,11 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
|
|
|
49
64
|
* @returns The HTML as a ProseMirror DOM Output Spec
|
|
50
65
|
* @example ({ options, node }) => ['span', { 'data-type': 'mention' }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]
|
|
51
66
|
*/
|
|
52
|
-
renderHTML: (props: {
|
|
67
|
+
renderHTML: (props: {
|
|
68
|
+
options: MentionOptions<SuggestionItem, Attrs>
|
|
69
|
+
node: ProseMirrorNode
|
|
70
|
+
suggestion: SuggestionOptions | null
|
|
71
|
+
}) => DOMOutputSpec
|
|
53
72
|
|
|
54
73
|
/**
|
|
55
74
|
* Whether to delete the trigger character with backspace.
|
|
@@ -58,7 +77,20 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
|
|
|
58
77
|
deleteTriggerWithBackspace: boolean
|
|
59
78
|
|
|
60
79
|
/**
|
|
61
|
-
* The suggestion options.
|
|
80
|
+
* The suggestion options, when you want to support multiple triggers.
|
|
81
|
+
*
|
|
82
|
+
* With this parameter, you can define multiple types of mention. For example, you can use the `@` character
|
|
83
|
+
* to mention users and the `#` character to mention tags.
|
|
84
|
+
*
|
|
85
|
+
* @default [{ char: '@', pluginKey: MentionPluginKey }]
|
|
86
|
+
* @example [{ char: '@', pluginKey: MentionPluginKey }, { char: '#', pluginKey: new PluginKey('hashtag') }]
|
|
87
|
+
*/
|
|
88
|
+
suggestions: Array<Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>>
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The suggestion options, when you want to support only one trigger. To support multiple triggers, use the
|
|
92
|
+
* `suggestions` parameter instead.
|
|
93
|
+
*
|
|
62
94
|
* @default {}
|
|
63
95
|
* @example { char: '@', pluginKey: MentionPluginKey, command: ({ editor, range, props }) => { ... } }
|
|
64
96
|
*/
|
|
@@ -66,73 +98,56 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
|
|
|
66
98
|
}
|
|
67
99
|
|
|
68
100
|
/**
|
|
69
|
-
*
|
|
70
|
-
* @default 'mention'
|
|
101
|
+
* Storage properties or the Mention extension
|
|
71
102
|
*/
|
|
72
|
-
export
|
|
103
|
+
export interface MentionStorage<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> {
|
|
104
|
+
/**
|
|
105
|
+
* The list of suggestions that will trigger the mention.
|
|
106
|
+
*/
|
|
107
|
+
suggestions: Array<SuggestionOptions<SuggestionItem, Attrs>>
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Returns the suggestion options of the mention that has a given character trigger. If not
|
|
111
|
+
* found, it returns the first suggestion.
|
|
112
|
+
*
|
|
113
|
+
* @param char The character that triggers the mention
|
|
114
|
+
* @returns The suggestion options
|
|
115
|
+
*/
|
|
116
|
+
getSuggestionFromChar: (char: string) => SuggestionOptions<SuggestionItem, Attrs> | null
|
|
117
|
+
}
|
|
73
118
|
|
|
74
119
|
/**
|
|
75
120
|
* This extension allows you to insert mentions into the editor.
|
|
76
121
|
* @see https://www.tiptap.dev/api/extensions/mention
|
|
77
122
|
*/
|
|
78
|
-
export const Mention = Node.create<MentionOptions>({
|
|
123
|
+
export const Mention = Node.create<MentionOptions, MentionStorage>({
|
|
79
124
|
name: 'mention',
|
|
80
125
|
|
|
81
126
|
priority: 101,
|
|
82
127
|
|
|
128
|
+
addStorage() {
|
|
129
|
+
return {
|
|
130
|
+
suggestions: [],
|
|
131
|
+
getSuggestionFromChar: () => null,
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
83
135
|
addOptions() {
|
|
84
136
|
return {
|
|
85
137
|
HTMLAttributes: {},
|
|
86
|
-
renderText({
|
|
87
|
-
return `${
|
|
138
|
+
renderText({ node, suggestion }) {
|
|
139
|
+
return `${suggestion?.char}${node.attrs.label ?? node.attrs.id}`
|
|
88
140
|
},
|
|
89
141
|
deleteTriggerWithBackspace: false,
|
|
90
|
-
renderHTML({ options, node }) {
|
|
142
|
+
renderHTML({ options, node, suggestion }) {
|
|
91
143
|
return [
|
|
92
144
|
'span',
|
|
93
145
|
mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),
|
|
94
|
-
`${
|
|
146
|
+
`${suggestion?.char}${node.attrs.label ?? node.attrs.id}`,
|
|
95
147
|
]
|
|
96
148
|
},
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
pluginKey: MentionPluginKey,
|
|
100
|
-
command: ({ editor, range, props }) => {
|
|
101
|
-
// increase range.to by one when the next node is of type "text"
|
|
102
|
-
// and starts with a space character
|
|
103
|
-
const nodeAfter = editor.view.state.selection.$to.nodeAfter
|
|
104
|
-
const overrideSpace = nodeAfter?.text?.startsWith(' ')
|
|
105
|
-
|
|
106
|
-
if (overrideSpace) {
|
|
107
|
-
range.to += 1
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
editor
|
|
111
|
-
.chain()
|
|
112
|
-
.focus()
|
|
113
|
-
.insertContentAt(range, [
|
|
114
|
-
{
|
|
115
|
-
type: this.name,
|
|
116
|
-
attrs: props,
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
type: 'text',
|
|
120
|
-
text: ' ',
|
|
121
|
-
},
|
|
122
|
-
])
|
|
123
|
-
.run()
|
|
124
|
-
|
|
125
|
-
// get reference to `window` object from editor element, to support cross-frame JS usage
|
|
126
|
-
editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd()
|
|
127
|
-
},
|
|
128
|
-
allow: ({ state, range }) => {
|
|
129
|
-
const $from = state.doc.resolve(range.from)
|
|
130
|
-
const type = state.schema.nodes[this.name]
|
|
131
|
-
const allow = !!$from.parent.type.contentMatch.matchType(type)
|
|
132
|
-
|
|
133
|
-
return allow
|
|
134
|
-
},
|
|
135
|
-
},
|
|
149
|
+
suggestions: [],
|
|
150
|
+
suggestion: {},
|
|
136
151
|
}
|
|
137
152
|
},
|
|
138
153
|
|
|
@@ -173,6 +188,17 @@ export const Mention = Node.create<MentionOptions>({
|
|
|
173
188
|
}
|
|
174
189
|
},
|
|
175
190
|
},
|
|
191
|
+
|
|
192
|
+
// When there are multiple types of mentions, this attribute helps distinguish them
|
|
193
|
+
mentionSuggestionChar: {
|
|
194
|
+
default: '@',
|
|
195
|
+
parseHTML: element => element.getAttribute('data-mention-suggestion-char'),
|
|
196
|
+
renderHTML: attributes => {
|
|
197
|
+
return {
|
|
198
|
+
'data-mention-suggestion-char': attributes.mentionSuggestionChar,
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
},
|
|
176
202
|
}
|
|
177
203
|
},
|
|
178
204
|
|
|
@@ -185,6 +211,12 @@ export const Mention = Node.create<MentionOptions>({
|
|
|
185
211
|
},
|
|
186
212
|
|
|
187
213
|
renderHTML({ node, HTMLAttributes }) {
|
|
214
|
+
// We cannot use the `this.storage` property here because, when accessed this method,
|
|
215
|
+
// it returns the initial value of the extension storage
|
|
216
|
+
const suggestion = (this.editor?.extensionStorage as unknown as Record<string, MentionStorage>)?.[
|
|
217
|
+
this.name
|
|
218
|
+
]?.getSuggestionFromChar(node.attrs.mentionSuggestionChar)
|
|
219
|
+
|
|
188
220
|
if (this.options.renderLabel !== undefined) {
|
|
189
221
|
console.warn('renderLabel is deprecated use renderText and renderHTML instead')
|
|
190
222
|
return [
|
|
@@ -193,6 +225,7 @@ export const Mention = Node.create<MentionOptions>({
|
|
|
193
225
|
this.options.renderLabel({
|
|
194
226
|
options: this.options,
|
|
195
227
|
node,
|
|
228
|
+
suggestion,
|
|
196
229
|
}),
|
|
197
230
|
]
|
|
198
231
|
}
|
|
@@ -203,9 +236,11 @@ export const Mention = Node.create<MentionOptions>({
|
|
|
203
236
|
this.options.HTMLAttributes,
|
|
204
237
|
HTMLAttributes,
|
|
205
238
|
)
|
|
239
|
+
|
|
206
240
|
const html = this.options.renderHTML({
|
|
207
241
|
options: mergedOptions,
|
|
208
242
|
node,
|
|
243
|
+
suggestion,
|
|
209
244
|
})
|
|
210
245
|
|
|
211
246
|
if (typeof html === 'string') {
|
|
@@ -215,17 +250,19 @@ export const Mention = Node.create<MentionOptions>({
|
|
|
215
250
|
},
|
|
216
251
|
|
|
217
252
|
renderText({ node }) {
|
|
253
|
+
const args = {
|
|
254
|
+
options: this.options,
|
|
255
|
+
node,
|
|
256
|
+
suggestion: (this.editor?.extensionStorage as unknown as Record<string, MentionStorage>)?.[
|
|
257
|
+
this.name
|
|
258
|
+
]?.getSuggestionFromChar(node.attrs.mentionSuggestionChar),
|
|
259
|
+
}
|
|
218
260
|
if (this.options.renderLabel !== undefined) {
|
|
219
261
|
console.warn('renderLabel is deprecated use renderText and renderHTML instead')
|
|
220
|
-
return this.options.renderLabel(
|
|
221
|
-
options: this.options,
|
|
222
|
-
node,
|
|
223
|
-
})
|
|
262
|
+
return this.options.renderLabel(args)
|
|
224
263
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
node,
|
|
228
|
-
})
|
|
264
|
+
|
|
265
|
+
return this.options.renderText(args)
|
|
229
266
|
},
|
|
230
267
|
|
|
231
268
|
addKeyboardShortcuts() {
|
|
@@ -240,30 +277,59 @@ export const Mention = Node.create<MentionOptions>({
|
|
|
240
277
|
return false
|
|
241
278
|
}
|
|
242
279
|
|
|
280
|
+
// Store node and position for later use
|
|
281
|
+
let mentionNode = new ProseMirrorNode()
|
|
282
|
+
let mentionPos = 0
|
|
283
|
+
|
|
243
284
|
state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {
|
|
244
285
|
if (node.type.name === this.name) {
|
|
245
286
|
isMention = true
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
pos,
|
|
249
|
-
pos + node.nodeSize,
|
|
250
|
-
)
|
|
251
|
-
|
|
287
|
+
mentionNode = node
|
|
288
|
+
mentionPos = pos
|
|
252
289
|
return false
|
|
253
290
|
}
|
|
254
291
|
})
|
|
255
292
|
|
|
293
|
+
if (isMention) {
|
|
294
|
+
tr.insertText(
|
|
295
|
+
this.options.deleteTriggerWithBackspace ? '' : mentionNode.attrs.mentionSuggestionChar,
|
|
296
|
+
mentionPos,
|
|
297
|
+
mentionPos + mentionNode.nodeSize,
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
|
|
256
301
|
return isMention
|
|
257
302
|
}),
|
|
258
303
|
}
|
|
259
304
|
},
|
|
260
305
|
|
|
261
306
|
addProseMirrorPlugins() {
|
|
262
|
-
|
|
263
|
-
|
|
307
|
+
// Create a plugin for each suggestion configuration
|
|
308
|
+
return this.storage.suggestions.map(Suggestion)
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
onBeforeCreate() {
|
|
312
|
+
this.storage.suggestions = (
|
|
313
|
+
this.options.suggestions.length ? this.options.suggestions : [this.options.suggestion]
|
|
314
|
+
).map(suggestion =>
|
|
315
|
+
getSuggestionOptions({
|
|
264
316
|
editor: this.editor,
|
|
265
|
-
|
|
317
|
+
overrideSuggestionOptions: suggestion,
|
|
318
|
+
extensionName: this.name,
|
|
319
|
+
char: suggestion.char,
|
|
266
320
|
}),
|
|
267
|
-
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
this.storage.getSuggestionFromChar = char => {
|
|
324
|
+
const suggestion = this.storage.suggestions.find(s => s.char === char)
|
|
325
|
+
if (suggestion) {
|
|
326
|
+
return suggestion
|
|
327
|
+
}
|
|
328
|
+
if (this.storage.suggestions.length) {
|
|
329
|
+
return this.storage.suggestions[0]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return null
|
|
333
|
+
}
|
|
268
334
|
},
|
|
269
335
|
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/core'
|
|
2
|
+
import { PluginKey } from '@tiptap/pm/state'
|
|
3
|
+
import type { SuggestionOptions } from '@tiptap/suggestion'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Arguments for the `getSuggestionOptions` function
|
|
7
|
+
* @see getSuggestionOptions
|
|
8
|
+
*/
|
|
9
|
+
export interface GetSuggestionOptionsOptions {
|
|
10
|
+
/**
|
|
11
|
+
* The Tiptap editor instance.
|
|
12
|
+
*/
|
|
13
|
+
editor: Editor
|
|
14
|
+
/**
|
|
15
|
+
* The suggestion options configuration provided to the
|
|
16
|
+
* `Mention` extension.
|
|
17
|
+
*/
|
|
18
|
+
overrideSuggestionOptions: Omit<SuggestionOptions, 'editor'>
|
|
19
|
+
/**
|
|
20
|
+
* The name of the Mention extension
|
|
21
|
+
*/
|
|
22
|
+
extensionName: string
|
|
23
|
+
/**
|
|
24
|
+
* The character that triggers the suggestion.
|
|
25
|
+
* @default '@'
|
|
26
|
+
*/
|
|
27
|
+
char?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns the suggestion options for a trigger of the Mention extension. These
|
|
32
|
+
* options are used to create a `Suggestion` ProseMirror plugin. Each plugin lets
|
|
33
|
+
* you define a different trigger that opens the Mention menu. For example,
|
|
34
|
+
* you can define a `@` trigger to mention users and a `#` trigger to mention
|
|
35
|
+
* tags.
|
|
36
|
+
*
|
|
37
|
+
* @param param0 The configured options for the suggestion
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
export function getSuggestionOptions({
|
|
41
|
+
editor: tiptapEditor,
|
|
42
|
+
overrideSuggestionOptions,
|
|
43
|
+
extensionName,
|
|
44
|
+
char = '@',
|
|
45
|
+
}: GetSuggestionOptionsOptions): SuggestionOptions {
|
|
46
|
+
const pluginKey = new PluginKey()
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
editor: tiptapEditor,
|
|
50
|
+
char,
|
|
51
|
+
pluginKey,
|
|
52
|
+
command: ({ editor, range, props }: { editor: any; range: any; props: any }) => {
|
|
53
|
+
// increase range.to by one when the next node is of type "text"
|
|
54
|
+
// and starts with a space character
|
|
55
|
+
const nodeAfter = editor.view.state.selection.$to.nodeAfter
|
|
56
|
+
const overrideSpace = nodeAfter?.text?.startsWith(' ')
|
|
57
|
+
|
|
58
|
+
if (overrideSpace) {
|
|
59
|
+
range.to += 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
editor
|
|
63
|
+
.chain()
|
|
64
|
+
.focus()
|
|
65
|
+
.insertContentAt(range, [
|
|
66
|
+
{
|
|
67
|
+
type: extensionName,
|
|
68
|
+
attrs: { ...props, mentionSuggestionChar: char },
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: 'text',
|
|
72
|
+
text: ' ',
|
|
73
|
+
},
|
|
74
|
+
])
|
|
75
|
+
.run()
|
|
76
|
+
|
|
77
|
+
// get reference to `window` object from editor element, to support cross-frame JS usage
|
|
78
|
+
editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd()
|
|
79
|
+
},
|
|
80
|
+
allow: ({ state, range }: { state: any; range: any }) => {
|
|
81
|
+
const $from = state.doc.resolve(range.from)
|
|
82
|
+
const type = state.schema.nodes[extensionName]
|
|
83
|
+
const allow = !!$from.parent.type.contentMatch.matchType(type)
|
|
84
|
+
|
|
85
|
+
return allow
|
|
86
|
+
},
|
|
87
|
+
...overrideSuggestionOptions,
|
|
88
|
+
}
|
|
89
|
+
}
|