claritykit-svelte
Version:
A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility
193 lines (192 loc) • 6.34 kB
JavaScript
import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core';
import { PluginKey } from '@tiptap/pm/state';
import { Decoration, DecorationSet } from '@tiptap/pm/view';
import { Plugin } from '@tiptap/pm/state';
import Suggestion from '@tiptap/suggestion';
export const MentionExtension = Node.create({
name: 'mention',
addOptions() {
return {
HTMLAttributes: {
class: 'mention',
'data-type': 'mention',
},
renderLabel({ options, node }) {
return `@${node.attrs.label ?? node.attrs.id}`;
},
suggestion: {
char: '@',
pluginKey: new PluginKey('mention'),
command: ({ editor, range, props }) => {
// Increase range.to by one when the next node is of type "text"
// and starts with a space character
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
const overrideSpace = nodeAfter?.text?.startsWith(' ');
if (overrideSpace) {
range.to += 1;
}
editor
.chain()
.focus()
.insertContentAt(range, [
{
type: this.name,
attrs: props,
},
{
type: 'text',
text: ' ',
},
])
.run();
window.getSelection()?.collapseToEnd();
},
allow: ({ state, range }) => {
const $from = state.doc.resolve(range.from);
const type = state.schema.nodes[this.name];
const allow = !!$from.parent.type.contentMatch.matchType(type);
return allow;
},
},
deleteTriggerWithBackspace: false,
};
},
group: 'inline',
inline: true,
selectable: false,
atom: true,
addAttributes() {
return {
id: {
default: null,
parseHTML: element => element.getAttribute('data-id'),
renderHTML: attributes => {
if (!attributes.id) {
return {};
}
return {
'data-id': attributes.id,
};
},
},
label: {
default: null,
parseHTML: element => element.getAttribute('data-label'),
renderHTML: attributes => {
if (!attributes.label) {
return {};
}
return {
'data-label': attributes.label,
};
},
},
userId: {
default: null,
parseHTML: element => element.getAttribute('data-user-id'),
renderHTML: attributes => {
if (!attributes.userId) {
return {};
}
return {
'data-user-id': attributes.userId,
};
},
},
userName: {
default: null,
parseHTML: element => element.getAttribute('data-user-name'),
renderHTML: attributes => {
if (!attributes.userName) {
return {};
}
return {
'data-user-name': attributes.userName,
};
},
},
userAvatar: {
default: null,
parseHTML: element => element.getAttribute('data-user-avatar'),
renderHTML: attributes => {
if (!attributes.userAvatar) {
return {};
}
return {
'data-user-avatar': attributes.userAvatar,
};
},
},
userRole: {
default: null,
parseHTML: element => element.getAttribute('data-user-role'),
renderHTML: attributes => {
if (!attributes.userRole) {
return {};
}
return {
'data-user-role': attributes.userRole,
};
},
},
};
},
parseHTML() {
return [
{
tag: `span[data-type="${this.name}"]`,
},
];
},
renderHTML({ node, HTMLAttributes }) {
return [
'span',
mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes),
this.options.renderLabel({
options: this.options,
node,
}),
];
},
renderText({ node }) {
return this.options.renderLabel({
options: this.options,
node,
});
},
addCommands() {
return {
insertMention: (attributes) => ({ commands }) => {
return commands.insertContent({
type: this.name,
attrs: attributes,
});
},
};
},
addProseMirrorPlugins() {
return [
Suggestion({
editor: this.editor,
...this.options.suggestion,
}),
];
},
addInputRules() {
return [
nodeInputRule({
find: /@([a-zA-Z0-9_-]+)/g,
type: this.type,
getAttributes: (match) => {
return {
id: match[1],
label: match[1],
userId: match[1],
userName: match[1],
};
},
}),
];
},
});
export default MentionExtension;