UNPKG

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
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;