@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 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 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
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({ options, node }) {
98
+ renderText({ node, suggestion }) {
51
99
  var _a;
52
- return `${options.suggestion.char}${(_a = node.attrs.label) != null ? _a : node.attrs.id}`;
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
- `${options.suggestion.char}${(_a = node.attrs.label) != null ? _a : node.attrs.id}`
108
+ `${suggestion == null ? void 0 : suggestion.char}${(_a = node.attrs.label) != null ? _a : node.attrs.id}`
61
109
  ];
62
110
  },
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
- }
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
- tr.insertText(
186
- this.options.deleteTriggerWithBackspace ? "" : this.options.suggestion.char || "",
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
- (0, import_suggestion.default)({
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
- ...this.options.suggestion
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
@@ -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
- 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,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
- * The plugin key for the mention plugin.
70
- * @default 'mention'
88
+ * Storage properties or the Mention extension
71
89
  */
72
- declare const MentionPluginKey: PluginKey<any>;
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, MentionPluginKey, Mention as default };
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
- 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,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
- * The plugin key for the mention plugin.
70
- * @default 'mention'
88
+ * Storage properties or the Mention extension
71
89
  */
72
- declare const MentionPluginKey: PluginKey<any>;
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, MentionPluginKey, Mention as default };
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 { 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
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({ options, node }) {
61
+ renderText({ node, suggestion }) {
13
62
  var _a;
14
- return `${options.suggestion.char}${(_a = node.attrs.label) != null ? _a : node.attrs.id}`;
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
- `${options.suggestion.char}${(_a = node.attrs.label) != null ? _a : node.attrs.id}`
71
+ `${suggestion == null ? void 0 : suggestion.char}${(_a = node.attrs.label) != null ? _a : node.attrs.id}`
23
72
  ];
24
73
  },
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
- }
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
- tr.insertText(
148
- this.options.deleteTriggerWithBackspace ? "" : this.options.suggestion.char || "",
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
- Suggestion({
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
- ...this.options.suggestion
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":";AAAA,SAAS,iBAAiB,YAAY;AAEtC,SAAS,QAAQ,uBAAuB;AAExC,OAAO,gBAAgB;;;ACHvB,SAAS,iBAAiB;AAsCnB,SAAS,qBAAqB;AAAA,EACnC,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,OAAO;AACT,GAAmD;AACjD,QAAM,YAAY,IAAI,UAAU;AAEhC,SAAO;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,SAAS,CAAC,EAAE,QAAQ,OAAO,MAAM,MAA+C;AAnDpF;AAsDM,YAAM,YAAY,OAAO,KAAK,MAAM,UAAU,IAAI;AAClD,YAAM,iBAAgB,4CAAW,SAAX,mBAAiB,WAAW;AAElD,UAAI,eAAe;AACjB,cAAM,MAAM;AAAA,MACd;AAEA,aACG,MAAM,EACN,MAAM,EACN,gBAAgB,OAAO;AAAA,QACtB;AAAA,UACE,MAAM;AAAA,UACN,OAAO,EAAE,GAAG,OAAO,uBAAuB,KAAK;AAAA,QACjD;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF,CAAC,EACA,IAAI;AAGP,yBAAO,KAAK,IAAI,cAAc,gBAA9B,mBAA2C,mBAA3C,mBAA2D;AAAA,IAC7D;AAAA,IACA,OAAO,CAAC,EAAE,OAAO,MAAM,MAAkC;AACvD,YAAM,QAAQ,MAAM,IAAI,QAAQ,MAAM,IAAI;AAC1C,YAAM,OAAO,MAAM,OAAO,MAAM,aAAa;AAC7C,YAAM,QAAQ,CAAC,CAAC,MAAM,OAAO,KAAK,aAAa,UAAU,IAAI;AAE7D,aAAO;AAAA,IACT;AAAA,IACA,GAAG;AAAA,EACL;AACF;;;ADkCO,IAAM,UAAU,KAAK,OAAuC;AAAA,EACjE,MAAM;AAAA,EAEN,UAAU;AAAA,EAEV,aAAa;AACX,WAAO;AAAA,MACL,aAAa,CAAC;AAAA,MACd,uBAAuB,MAAM;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,MACjB,WAAW,EAAE,MAAM,WAAW,GAAG;AAzIvC;AA0IQ,eAAO,GAAG,yCAAY,IAAI,IAAG,UAAK,MAAM,UAAX,YAAoB,KAAK,MAAM,EAAE;AAAA,MAChE;AAAA,MACA,4BAA4B;AAAA,MAC5B,WAAW,EAAE,SAAS,MAAM,WAAW,GAAG;AA7IhD;AA8IQ,eAAO;AAAA,UACL;AAAA,UACA,gBAAgB,KAAK,gBAAgB,QAAQ,cAAc;AAAA,UAC3D,GAAG,yCAAY,IAAI,IAAG,UAAK,MAAM,UAAX,YAAoB,KAAK,MAAM,EAAE;AAAA,QACzD;AAAA,MACF;AAAA,MACA,aAAa,CAAC;AAAA,MACd,YAAY,CAAC;AAAA,IACf;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,EAEP,QAAQ;AAAA,EAER,YAAY;AAAA,EAEZ,MAAM;AAAA,EAEN,gBAAgB;AACd,WAAO;AAAA,MACL,IAAI;AAAA,QACF,SAAS;AAAA,QACT,WAAW,aAAW,QAAQ,aAAa,SAAS;AAAA,QACpD,YAAY,gBAAc;AACxB,cAAI,CAAC,WAAW,IAAI;AAClB,mBAAO,CAAC;AAAA,UACV;AAEA,iBAAO;AAAA,YACL,WAAW,WAAW;AAAA,UACxB;AAAA,QACF;AAAA,MACF;AAAA,MAEA,OAAO;AAAA,QACL,SAAS;AAAA,QACT,WAAW,aAAW,QAAQ,aAAa,YAAY;AAAA,QACvD,YAAY,gBAAc;AACxB,cAAI,CAAC,WAAW,OAAO;AACrB,mBAAO,CAAC;AAAA,UACV;AAEA,iBAAO;AAAA,YACL,cAAc,WAAW;AAAA,UAC3B;AAAA,QACF;AAAA,MACF;AAAA;AAAA,MAGA,uBAAuB;AAAA,QACrB,SAAS;AAAA,QACT,WAAW,aAAW,QAAQ,aAAa,8BAA8B;AAAA,QACzE,YAAY,gBAAc;AACxB,iBAAO;AAAA,YACL,gCAAgC,WAAW;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAY;AACV,WAAO;AAAA,MACL;AAAA,QACE,KAAK,mBAAmB,KAAK,IAAI;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,WAAW,EAAE,MAAM,eAAe,GAAG;AApNvC;AAuNI,UAAM,cAAc,sBAAK,WAAL,mBAAa,qBAAb,mBAClB,KAAK,UADa,mBAEjB,sBAAsB,KAAK,MAAM;AAEpC,QAAI,KAAK,QAAQ,gBAAgB,QAAW;AAC1C,cAAQ,KAAK,iEAAiE;AAC9E,aAAO;AAAA,QACL;AAAA,QACA,gBAAgB,EAAE,aAAa,KAAK,KAAK,GAAG,KAAK,QAAQ,gBAAgB,cAAc;AAAA,QACvF,KAAK,QAAQ,YAAY;AAAA,UACvB,SAAS,KAAK;AAAA,UACd;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,gBAAgB,EAAE,GAAG,KAAK,QAAQ;AAExC,kBAAc,iBAAiB;AAAA,MAC7B,EAAE,aAAa,KAAK,KAAK;AAAA,MACzB,KAAK,QAAQ;AAAA,MACb;AAAA,IACF;AAEA,UAAM,OAAO,KAAK,QAAQ,WAAW;AAAA,MACnC,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,OAAO,SAAS,UAAU;AAC5B,aAAO,CAAC,QAAQ,gBAAgB,EAAE,aAAa,KAAK,KAAK,GAAG,KAAK,QAAQ,gBAAgB,cAAc,GAAG,IAAI;AAAA,IAChH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,EAAE,KAAK,GAAG;AA3PvB;AA4PI,UAAM,OAAO;AAAA,MACX,SAAS,KAAK;AAAA,MACd;AAAA,MACA,aAAa,sBAAK,WAAL,mBAAa,qBAAb,mBACX,KAAK,UADM,mBAEV,sBAAsB,KAAK,MAAM;AAAA,IACtC;AACA,QAAI,KAAK,QAAQ,gBAAgB,QAAW;AAC1C,cAAQ,KAAK,iEAAiE;AAC9E,aAAO,KAAK,QAAQ,YAAY,IAAI;AAAA,IACtC;AAEA,WAAO,KAAK,QAAQ,WAAW,IAAI;AAAA,EACrC;AAAA,EAEA,uBAAuB;AACrB,WAAO;AAAA,MACL,WAAW,MACT,KAAK,OAAO,SAAS,QAAQ,CAAC,EAAE,IAAI,MAAM,MAAM;AAC9C,YAAI,YAAY;AAChB,cAAM,EAAE,UAAU,IAAI;AACtB,cAAM,EAAE,OAAO,OAAO,IAAI;AAE1B,YAAI,CAAC,OAAO;AACV,iBAAO;AAAA,QACT;AAGA,YAAI,cAAc,IAAI,gBAAgB;AACtC,YAAI,aAAa;AAEjB,cAAM,IAAI,aAAa,SAAS,GAAG,QAAQ,CAAC,MAAM,QAAQ;AACxD,cAAI,KAAK,KAAK,SAAS,KAAK,MAAM;AAChC,wBAAY;AACZ,0BAAc;AACd,yBAAa;AACb,mBAAO;AAAA,UACT;AAAA,QACF,CAAC;AAED,YAAI,WAAW;AACb,aAAG;AAAA,YACD,KAAK,QAAQ,6BAA6B,KAAK,YAAY,MAAM;AAAA,YACjE;AAAA,YACA,aAAa,YAAY;AAAA,UAC3B;AAAA,QACF;AAEA,eAAO;AAAA,MACT,CAAC;AAAA,IACL;AAAA,EACF;AAAA,EAEA,wBAAwB;AAEtB,WAAO,KAAK,QAAQ,YAAY,IAAI,UAAU;AAAA,EAChD;AAAA,EAEA,iBAAiB;AACf,SAAK,QAAQ,eACX,KAAK,QAAQ,YAAY,SAAS,KAAK,QAAQ,cAAc,CAAC,KAAK,QAAQ,UAAU,GACrF;AAAA,MAAI,gBACJ,qBAAqB;AAAA,QACnB,QAAQ,KAAK;AAAA,QACb,2BAA2B;AAAA,QAC3B,eAAe,KAAK;AAAA,QACpB,MAAM,WAAW;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,SAAK,QAAQ,wBAAwB,UAAQ;AAC3C,YAAM,aAAa,KAAK,QAAQ,YAAY,KAAK,OAAK,EAAE,SAAS,IAAI;AACrE,UAAI,YAAY;AACd,eAAO;AAAA,MACT;AACA,UAAI,KAAK,QAAQ,YAAY,QAAQ;AACnC,eAAO,KAAK,QAAQ,YAAY,CAAC;AAAA,MACnC;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF,CAAC;;;AE1UD,IAAO,gBAAQ;","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.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.3",
35
- "@tiptap/suggestion": "3.0.0-beta.3",
36
- "@tiptap/pm": "3.0.0-beta.3"
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.3",
40
- "@tiptap/suggestion": "3.0.0-beta.3",
41
- "@tiptap/pm": "3.0.0-beta.3"
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, Node as ProseMirrorNode } from '@tiptap/pm/model'
3
- import { PluginKey } from '@tiptap/pm/state'
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 type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> = {
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: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => string
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: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => string
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: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => DOMOutputSpec
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
- * The plugin key for the mention plugin.
70
- * @default 'mention'
101
+ * Storage properties or the Mention extension
71
102
  */
72
- export const MentionPluginKey = new PluginKey('mention')
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({ options, node }) {
87
- return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
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
- `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`,
146
+ `${suggestion?.char}${node.attrs.label ?? node.attrs.id}`,
95
147
  ]
96
148
  },
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
- },
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
- return this.options.renderText({
226
- options: this.options,
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
- tr.insertText(
247
- this.options.deleteTriggerWithBackspace ? '' : this.options.suggestion.char || '',
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
- return [
263
- Suggestion({
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
- ...this.options.suggestion,
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
+ }