@tiptap/extension-mention 3.0.0-beta.3 → 3.0.0-beta.30

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