154 lines
4.1 KiB
JavaScript
154 lines
4.1 KiB
JavaScript
import { reactive } from 'vue'
|
|
import Quill from 'quill'
|
|
const Inline = Quill.import('blots/inline')
|
|
const Delta = Quill.import('delta')
|
|
const Clipboard = Quill.import('modules/clipboard')
|
|
|
|
export default function registerMentionExtension(Quill) {
|
|
// Extend Clipboard to handle pasted content
|
|
class MentionClipboard extends Clipboard {
|
|
convert(html) {
|
|
const delta = super.convert(html)
|
|
// Remove any mention formatting from pasted content
|
|
return delta.reduce((newDelta, op) => {
|
|
if (op.attributes && op.attributes.mention) {
|
|
// Only keep mentions that have valid field IDs
|
|
if (!op.attributes.mention['mention-field-id']) {
|
|
delete op.attributes.mention
|
|
}
|
|
}
|
|
newDelta.push(op)
|
|
return newDelta
|
|
}, new Delta())
|
|
}
|
|
}
|
|
Quill.register('modules/clipboard', MentionClipboard, true)
|
|
|
|
class MentionBlot extends Inline {
|
|
static blotName = 'mention'
|
|
static tagName = 'SPAN'
|
|
|
|
static create(data) {
|
|
// Only create mention if we have valid data
|
|
if (!data || !data.field || !data.field.id) {
|
|
return null
|
|
}
|
|
let node = super.create()
|
|
MentionBlot.setAttributes(node, data)
|
|
return node
|
|
}
|
|
|
|
static setAttributes(node, data) {
|
|
// Only set attributes if we have valid data
|
|
if (!data || !data.field || !data.field.id) {
|
|
return
|
|
}
|
|
|
|
node.setAttribute('contenteditable', 'false')
|
|
node.setAttribute('mention', 'true')
|
|
node.setAttribute('mention-field-id', data.field.id || '')
|
|
node.setAttribute('mention-field-name', data.field.name || '')
|
|
node.setAttribute('mention-fallback', data.fallback || '')
|
|
node.textContent = data.field.name || ''
|
|
}
|
|
|
|
static formats(domNode) {
|
|
return {
|
|
'mention-field-id': domNode.getAttribute('mention-field-id') || '',
|
|
'mention-field-name': domNode.getAttribute('mention-field-name') || '',
|
|
'mention-fallback': domNode.getAttribute('mention-fallback') || ''
|
|
}
|
|
}
|
|
|
|
format(name, value) {
|
|
if (name === 'mention' && value) {
|
|
MentionBlot.setAttributes(this.domNode, value)
|
|
} else {
|
|
super.format(name, value)
|
|
}
|
|
}
|
|
|
|
formats() {
|
|
let formats = super.formats()
|
|
formats['mention'] = MentionBlot.formats(this.domNode)
|
|
return formats
|
|
}
|
|
|
|
static value(domNode) {
|
|
return {
|
|
field: {
|
|
id: domNode.getAttribute('mention-field-id') || '',
|
|
name: domNode.getAttribute('mention-field-name') || ''
|
|
},
|
|
fallback: domNode.getAttribute('mention-fallback') || ''
|
|
}
|
|
}
|
|
|
|
// Override attach to ensure contenteditable is always set
|
|
attach() {
|
|
super.attach()
|
|
this.domNode.setAttribute('contenteditable', 'false')
|
|
}
|
|
|
|
length() {
|
|
return 1
|
|
}
|
|
}
|
|
|
|
Quill.register(MentionBlot)
|
|
|
|
const mentionState = reactive({
|
|
open: false,
|
|
onInsert: null,
|
|
onCancel: null,
|
|
})
|
|
|
|
class MentionModule {
|
|
constructor(quill, options) {
|
|
this.quill = quill
|
|
this.options = options
|
|
|
|
this.setupMentions()
|
|
}
|
|
|
|
setupMentions() {
|
|
const toolbar = this.quill.getModule('toolbar')
|
|
if (toolbar) {
|
|
toolbar.addHandler('mention', () => {
|
|
const range = this.quill.getSelection()
|
|
if (range) {
|
|
mentionState.open = true
|
|
mentionState.onInsert = (mention) => {
|
|
this.insertMention(mention, range.index)
|
|
}
|
|
mentionState.onCancel = () => {
|
|
mentionState.open = false
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
insertMention(mention, index) {
|
|
mentionState.open = false
|
|
|
|
// Insert the mention
|
|
this.quill.insertEmbed(index, 'mention', mention, Quill.sources.USER)
|
|
|
|
// Calculate the length of the inserted mention
|
|
const mentionLength = this.quill.getLength() - index
|
|
|
|
nextTick(() => {
|
|
// Focus the editor
|
|
this.quill.focus()
|
|
|
|
// Set the selection after the mention
|
|
this.quill.setSelection(index + mentionLength, 0, Quill.sources.SILENT)
|
|
})
|
|
}
|
|
}
|
|
|
|
Quill.register('modules/mention', MentionModule)
|
|
|
|
return mentionState
|
|
} |