@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 +110 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -10
- package/dist/index.d.ts +24 -10
- package/dist/index.js +109 -59
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/mention.ts +131 -73
- package/src/utils/get-default-suggestion-attributes.ts +89 -0
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
|
|
40
|
+
var import_model = require("@tiptap/pm/model");
|
|
42
41
|
var import_suggestion = __toESM(require("@tiptap/suggestion"), 1);
|
|
43
|
-
|
|
42
|
+
|
|
43
|
+
// src/utils/get-default-suggestion-attributes.ts
|
|
44
|
+
var import_state = require("@tiptap/pm/state");
|
|
45
|
+
function getSuggestionOptions({
|
|
46
|
+
editor: tiptapEditor,
|
|
47
|
+
overrideSuggestionOptions,
|
|
48
|
+
extensionName,
|
|
49
|
+
char = "@"
|
|
50
|
+
}) {
|
|
51
|
+
const pluginKey = new import_state.PluginKey();
|
|
52
|
+
return {
|
|
53
|
+
editor: tiptapEditor,
|
|
54
|
+
char,
|
|
55
|
+
pluginKey,
|
|
56
|
+
command: ({ editor, range, props }) => {
|
|
57
|
+
var _a, _b, _c;
|
|
58
|
+
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
59
|
+
const overrideSpace = (_a = nodeAfter == null ? void 0 : nodeAfter.text) == null ? void 0 : _a.startsWith(" ");
|
|
60
|
+
if (overrideSpace) {
|
|
61
|
+
range.to += 1;
|
|
62
|
+
}
|
|
63
|
+
editor.chain().focus().insertContentAt(range, [
|
|
64
|
+
{
|
|
65
|
+
type: extensionName,
|
|
66
|
+
attrs: { ...props, mentionSuggestionChar: char }
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: " "
|
|
71
|
+
}
|
|
72
|
+
]).run();
|
|
73
|
+
(_c = (_b = editor.view.dom.ownerDocument.defaultView) == null ? void 0 : _b.getSelection()) == null ? void 0 : _c.collapseToEnd();
|
|
74
|
+
},
|
|
75
|
+
allow: ({ state, range }) => {
|
|
76
|
+
const $from = state.doc.resolve(range.from);
|
|
77
|
+
const type = state.schema.nodes[extensionName];
|
|
78
|
+
const allow = !!$from.parent.type.contentMatch.matchType(type);
|
|
79
|
+
return allow;
|
|
80
|
+
},
|
|
81
|
+
...overrideSuggestionOptions
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/mention.ts
|
|
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({
|
|
51
|
-
var _a;
|
|
52
|
-
return `${
|
|
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
|
-
`${
|
|
124
|
+
`${(_a = suggestion == null ? void 0 : suggestion.char) != null ? _a : "@"}${(_b = node.attrs.label) != null ? _b : node.attrs.id}`
|
|
61
125
|
];
|
|
62
126
|
},
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
pluginKey: MentionPluginKey,
|
|
66
|
-
command: ({ editor, range, props }) => {
|
|
67
|
-
var _a, _b, _c;
|
|
68
|
-
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
69
|
-
const overrideSpace = (_a = nodeAfter == null ? void 0 : nodeAfter.text) == null ? void 0 : _a.startsWith(" ");
|
|
70
|
-
if (overrideSpace) {
|
|
71
|
-
range.to += 1;
|
|
72
|
-
}
|
|
73
|
-
editor.chain().focus().insertContentAt(range, [
|
|
74
|
-
{
|
|
75
|
-
type: this.name,
|
|
76
|
-
attrs: props
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
type: "text",
|
|
80
|
-
text: " "
|
|
81
|
-
}
|
|
82
|
-
]).run();
|
|
83
|
-
(_c = (_b = editor.view.dom.ownerDocument.defaultView) == null ? void 0 : _b.getSelection()) == null ? void 0 : _c.collapseToEnd();
|
|
84
|
-
},
|
|
85
|
-
allow: ({ state, range }) => {
|
|
86
|
-
const $from = state.doc.resolve(range.from);
|
|
87
|
-
const type = state.schema.nodes[this.name];
|
|
88
|
-
const allow = !!$from.parent.type.contentMatch.matchType(type);
|
|
89
|
-
return allow;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
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
|
-
|
|
186
|
-
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/mention.ts"],"sourcesContent":["import { Mention } from './mention.js'\n\nexport * from './mention.js'\n\nexport default Mention\n","import { mergeAttributes, Node } from '@tiptap/core'\nimport type { DOMOutputSpec, Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { PluginKey } from '@tiptap/pm/state'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\nimport Suggestion from '@tiptap/suggestion'\n\n// See `addAttributes` below\nexport interface MentionNodeAttrs {\n /**\n * The identifier for the selected item that was mentioned, stored as a `data-id`\n * attribute.\n */\n id: string | null\n /**\n * The label to be rendered by the editor as the displayed text for this mentioned\n * item, if provided. Stored as a `data-label` attribute. See `renderLabel`.\n */\n label?: string | null\n}\n\nexport type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> = {\n /**\n * The HTML attributes for a mention node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * A function to render the label of a mention.\n * @deprecated use renderText and renderHTML instead\n * @param props The render props\n * @returns The label\n * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n */\n renderLabel?: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => string\n\n /**\n * A function to render the text of a mention.\n * @param props The render props\n * @returns The text\n * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n */\n renderText: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => string\n\n /**\n * A function to render the HTML of a mention.\n * @param props The render props\n * @returns The HTML as a ProseMirror DOM Output Spec\n * @example ({ options, node }) => ['span', { 'data-type': 'mention' }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]\n */\n renderHTML: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => DOMOutputSpec\n\n /**\n * Whether to delete the trigger character with backspace.\n * @default false\n */\n deleteTriggerWithBackspace: boolean\n\n /**\n * The suggestion options.\n * @default {}\n * @example { char: '@', pluginKey: MentionPluginKey, command: ({ editor, range, props }) => { ... } }\n */\n suggestion: Omit<SuggestionOptions<SuggestionItem, Attrs>, 'editor'>\n}\n\n/**\n * The plugin key for the mention plugin.\n * @default 'mention'\n */\nexport const MentionPluginKey = new PluginKey('mention')\n\n/**\n * This extension allows you to insert mentions into the editor.\n * @see https://www.tiptap.dev/api/extensions/mention\n */\nexport const Mention = Node.create<MentionOptions>({\n name: 'mention',\n\n priority: 101,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n renderText({ options, node }) {\n return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`\n },\n deleteTriggerWithBackspace: false,\n renderHTML({ options, node }) {\n return [\n 'span',\n mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),\n `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`,\n ]\n },\n suggestion: {\n char: '@',\n pluginKey: MentionPluginKey,\n command: ({ editor, range, props }) => {\n // increase range.to by one when the next node is of type \"text\"\n // and starts with a space character\n const nodeAfter = editor.view.state.selection.$to.nodeAfter\n const overrideSpace = nodeAfter?.text?.startsWith(' ')\n\n if (overrideSpace) {\n range.to += 1\n }\n\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: this.name,\n attrs: props,\n },\n {\n type: 'text',\n text: ' ',\n },\n ])\n .run()\n\n // get reference to `window` object from editor element, to support cross-frame JS usage\n editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd()\n },\n allow: ({ state, range }) => {\n const $from = state.doc.resolve(range.from)\n const type = state.schema.nodes[this.name]\n const allow = !!$from.parent.type.contentMatch.matchType(type)\n\n return allow\n },\n },\n }\n },\n\n group: 'inline',\n\n inline: true,\n\n selectable: false,\n\n atom: true,\n\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: element => element.getAttribute('data-id'),\n renderHTML: attributes => {\n if (!attributes.id) {\n return {}\n }\n\n return {\n 'data-id': attributes.id,\n }\n },\n },\n\n label: {\n default: null,\n parseHTML: element => element.getAttribute('data-label'),\n renderHTML: attributes => {\n if (!attributes.label) {\n return {}\n }\n\n return {\n 'data-label': attributes.label,\n }\n },\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: `span[data-type=\"${this.name}\"]`,\n },\n ]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n if (this.options.renderLabel !== undefined) {\n console.warn('renderLabel is deprecated use renderText and renderHTML instead')\n return [\n 'span',\n mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes),\n this.options.renderLabel({\n options: this.options,\n node,\n }),\n ]\n }\n const mergedOptions = { ...this.options }\n\n mergedOptions.HTMLAttributes = mergeAttributes(\n { 'data-type': this.name },\n this.options.HTMLAttributes,\n HTMLAttributes,\n )\n const html = this.options.renderHTML({\n options: mergedOptions,\n node,\n })\n\n if (typeof html === 'string') {\n return ['span', mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes), html]\n }\n return html\n },\n\n renderText({ node }) {\n if (this.options.renderLabel !== undefined) {\n console.warn('renderLabel is deprecated use renderText and renderHTML instead')\n return this.options.renderLabel({\n options: this.options,\n node,\n })\n }\n return this.options.renderText({\n options: this.options,\n node,\n })\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: () =>\n this.editor.commands.command(({ tr, state }) => {\n let isMention = false\n const { selection } = state\n const { empty, anchor } = selection\n\n if (!empty) {\n return false\n }\n\n state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {\n if (node.type.name === this.name) {\n isMention = true\n tr.insertText(\n this.options.deleteTriggerWithBackspace ? '' : this.options.suggestion.char || '',\n pos,\n pos + node.nodeSize,\n )\n\n return false\n }\n })\n\n return isMention\n }),\n }\n },\n\n addProseMirrorPlugins() {\n return [\n Suggestion({\n editor: this.editor,\n ...this.options.suggestion,\n }),\n ]\n },\n})\n"],"mappings":"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","names":["Suggestion"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/mention.ts","../src/utils/get-default-suggestion-attributes.ts"],"sourcesContent":["import { Mention } from './mention.js'\n\nexport * from './mention.js'\n\nexport default Mention\n","import 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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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 {
|
|
3
|
+
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
|
4
4
|
import Suggestion from "@tiptap/suggestion";
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
// src/utils/get-default-suggestion-attributes.ts
|
|
7
|
+
import { PluginKey } from "@tiptap/pm/state";
|
|
8
|
+
function getSuggestionOptions({
|
|
9
|
+
editor: tiptapEditor,
|
|
10
|
+
overrideSuggestionOptions,
|
|
11
|
+
extensionName,
|
|
12
|
+
char = "@"
|
|
13
|
+
}) {
|
|
14
|
+
const pluginKey = new PluginKey();
|
|
15
|
+
return {
|
|
16
|
+
editor: tiptapEditor,
|
|
17
|
+
char,
|
|
18
|
+
pluginKey,
|
|
19
|
+
command: ({ editor, range, props }) => {
|
|
20
|
+
var _a, _b, _c;
|
|
21
|
+
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
22
|
+
const overrideSpace = (_a = nodeAfter == null ? void 0 : nodeAfter.text) == null ? void 0 : _a.startsWith(" ");
|
|
23
|
+
if (overrideSpace) {
|
|
24
|
+
range.to += 1;
|
|
25
|
+
}
|
|
26
|
+
editor.chain().focus().insertContentAt(range, [
|
|
27
|
+
{
|
|
28
|
+
type: extensionName,
|
|
29
|
+
attrs: { ...props, mentionSuggestionChar: char }
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: " "
|
|
34
|
+
}
|
|
35
|
+
]).run();
|
|
36
|
+
(_c = (_b = editor.view.dom.ownerDocument.defaultView) == null ? void 0 : _b.getSelection()) == null ? void 0 : _c.collapseToEnd();
|
|
37
|
+
},
|
|
38
|
+
allow: ({ state, range }) => {
|
|
39
|
+
const $from = state.doc.resolve(range.from);
|
|
40
|
+
const type = state.schema.nodes[extensionName];
|
|
41
|
+
const allow = !!$from.parent.type.contentMatch.matchType(type);
|
|
42
|
+
return allow;
|
|
43
|
+
},
|
|
44
|
+
...overrideSuggestionOptions
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/mention.ts
|
|
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({
|
|
13
|
-
var _a;
|
|
14
|
-
return `${
|
|
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
|
-
`${
|
|
87
|
+
`${(_a = suggestion == null ? void 0 : suggestion.char) != null ? _a : "@"}${(_b = node.attrs.label) != null ? _b : node.attrs.id}`
|
|
23
88
|
];
|
|
24
89
|
},
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
pluginKey: MentionPluginKey,
|
|
28
|
-
command: ({ editor, range, props }) => {
|
|
29
|
-
var _a, _b, _c;
|
|
30
|
-
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
31
|
-
const overrideSpace = (_a = nodeAfter == null ? void 0 : nodeAfter.text) == null ? void 0 : _a.startsWith(" ");
|
|
32
|
-
if (overrideSpace) {
|
|
33
|
-
range.to += 1;
|
|
34
|
-
}
|
|
35
|
-
editor.chain().focus().insertContentAt(range, [
|
|
36
|
-
{
|
|
37
|
-
type: this.name,
|
|
38
|
-
attrs: props
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
type: "text",
|
|
42
|
-
text: " "
|
|
43
|
-
}
|
|
44
|
-
]).run();
|
|
45
|
-
(_c = (_b = editor.view.dom.ownerDocument.defaultView) == null ? void 0 : _b.getSelection()) == null ? void 0 : _c.collapseToEnd();
|
|
46
|
-
},
|
|
47
|
-
allow: ({ state, range }) => {
|
|
48
|
-
const $from = state.doc.resolve(range.from);
|
|
49
|
-
const type = state.schema.nodes[this.name];
|
|
50
|
-
const allow = !!$from.parent.type.contentMatch.matchType(type);
|
|
51
|
-
return allow;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
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
|
-
|
|
148
|
-
|
|
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.
|
|
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.
|
|
35
|
-
"@tiptap/
|
|
36
|
-
"@tiptap/
|
|
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.
|
|
40
|
-
"@tiptap/
|
|
41
|
-
"@tiptap/
|
|
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
|
|
3
|
-
import {
|
|
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
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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
|
-
*
|
|
70
|
-
*
|
|
108
|
+
* Returns the suggestions for the mention extension.
|
|
109
|
+
*
|
|
110
|
+
* @param options The extension options
|
|
111
|
+
* @returns the suggestions
|
|
71
112
|
*/
|
|
72
|
-
|
|
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({
|
|
87
|
-
return `${
|
|
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
|
-
`${
|
|
169
|
+
`${suggestion?.char ?? '@'}${node.attrs.label ?? node.attrs.id}`,
|
|
95
170
|
]
|
|
96
171
|
},
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
pluginKey: MentionPluginKey,
|
|
100
|
-
command: ({ editor, range, props }) => {
|
|
101
|
-
// increase range.to by one when the next node is of type "text"
|
|
102
|
-
// and starts with a space character
|
|
103
|
-
const nodeAfter = editor.view.state.selection.$to.nodeAfter
|
|
104
|
-
const overrideSpace = nodeAfter?.text?.startsWith(' ')
|
|
105
|
-
|
|
106
|
-
if (overrideSpace) {
|
|
107
|
-
range.to += 1
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
editor
|
|
111
|
-
.chain()
|
|
112
|
-
.focus()
|
|
113
|
-
.insertContentAt(range, [
|
|
114
|
-
{
|
|
115
|
-
type: this.name,
|
|
116
|
-
attrs: props,
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
type: 'text',
|
|
120
|
-
text: ' ',
|
|
121
|
-
},
|
|
122
|
-
])
|
|
123
|
-
.run()
|
|
124
|
-
|
|
125
|
-
// get reference to `window` object from editor element, to support cross-frame JS usage
|
|
126
|
-
editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd()
|
|
127
|
-
},
|
|
128
|
-
allow: ({ state, range }) => {
|
|
129
|
-
const $from = state.doc.resolve(range.from)
|
|
130
|
-
const type = state.schema.nodes[this.name]
|
|
131
|
-
const allow = !!$from.parent.type.contentMatch.matchType(type)
|
|
132
|
-
|
|
133
|
-
return allow
|
|
134
|
-
},
|
|
135
|
-
},
|
|
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
|
-
|
|
226
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
+
}
|