feat(editor): 添加blockquote和emoji扩展并重构引用卡片实现
- 添加@tiptap/extension-blockquote和@tiptap/extension-emoji依赖 - 将自定义Quote扩展从Extension改为Node实现 - 简化引用卡片的HTML渲染逻辑 - 改进引用卡片的键盘删除行为 - 优化引用内容的插入位置和格式
This commit is contained in:
parent
8e2c134c90
commit
dd170cb50d
@ -20,6 +20,8 @@
|
||||
"@kangc/v-md-editor": "^2.3.18",
|
||||
"@onlyoffice/document-editor-vue": "^1.5.0",
|
||||
"@tiptap/core": "^2.23.1",
|
||||
"@tiptap/extension-blockquote": "^2.23.1",
|
||||
"@tiptap/extension-emoji": "^2.23.1",
|
||||
"@tiptap/extension-image": "^2.23.1",
|
||||
"@tiptap/extension-link": "^2.23.1",
|
||||
"@tiptap/extension-mention": "^2.23.1",
|
||||
|
@ -26,6 +26,12 @@ importers:
|
||||
'@tiptap/core':
|
||||
specifier: ^2.23.1
|
||||
version: 2.23.1(@tiptap/pm@2.23.1)
|
||||
'@tiptap/extension-blockquote':
|
||||
specifier: ^2.23.1
|
||||
version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
|
||||
'@tiptap/extension-emoji':
|
||||
specifier: ^2.23.1
|
||||
version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)(@tiptap/suggestion@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1))(emojibase@16.0.0)
|
||||
'@tiptap/extension-image':
|
||||
specifier: ^2.23.1
|
||||
version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
|
||||
@ -928,6 +934,13 @@ packages:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
|
||||
'@tiptap/extension-emoji@2.23.1':
|
||||
resolution: {integrity: sha512-bqTn+hbq0bDIcrPIIjVq3GndJ/PYQfReMDlyTv0mUCtRbP7zReJ1oFx02d25RmwgS6XL3U8WW4kEFomhliwWSQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
'@tiptap/suggestion': ^2.7.0
|
||||
|
||||
'@tiptap/extension-floating-menu@2.23.1':
|
||||
resolution: {integrity: sha512-GMWkpH+p/OUOk1Y5UGOnKuHSDEVBN7DhYIJiWt5g9LK/mpPeuqoCmQg3RQDgjtZXb74SlxLK2pS/3YcAnemdfQ==}
|
||||
peerDependencies:
|
||||
@ -2013,9 +2026,21 @@ packages:
|
||||
elkjs@0.9.3:
|
||||
resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==}
|
||||
|
||||
emoji-regex@10.4.0:
|
||||
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
emojibase-data@15.3.2:
|
||||
resolution: {integrity: sha512-TpDyTDDTdqWIJixV5sTA6OQ0P0JfIIeK2tFRR3q56G9LK65ylAZ7z3KyBXokpvTTJ+mLUXQXbLNyVkjvnTLE+A==}
|
||||
peerDependencies:
|
||||
emojibase: '*'
|
||||
|
||||
emojibase@16.0.0:
|
||||
resolution: {integrity: sha512-Nw2m7JLIO4Ou2X/yZPRNscHQXVbbr6SErjkJ7EooG7MbR3yDZszCv9KTizsXFc7yZl0n3WF+qUKIC/Lw6H9xaQ==}
|
||||
engines: {node: '>=18.12.0'}
|
||||
|
||||
enhanced-resolve@5.18.2:
|
||||
resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@ -2454,6 +2479,9 @@ packages:
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
hasBin: true
|
||||
|
||||
is-emoji-supported@0.0.5:
|
||||
resolution: {integrity: sha512-WOlXUhDDHxYqcSmFZis+xWhhqXiK2SU0iYiqmth5Ip0FHLZQAt9rKL5ahnilE8/86WH8tZ3bmNNNC+bTzamqlw==}
|
||||
|
||||
is-extendable@0.1.1:
|
||||
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -4548,6 +4576,17 @@ snapshots:
|
||||
'@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
|
||||
'@tiptap/pm': 2.23.1
|
||||
|
||||
'@tiptap/extension-emoji@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)(@tiptap/suggestion@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1))(emojibase@16.0.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
|
||||
'@tiptap/pm': 2.23.1
|
||||
'@tiptap/suggestion': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
|
||||
emoji-regex: 10.4.0
|
||||
emojibase-data: 15.3.2(emojibase@16.0.0)
|
||||
is-emoji-supported: 0.0.5
|
||||
transitivePeerDependencies:
|
||||
- emojibase
|
||||
|
||||
'@tiptap/extension-floating-menu@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
|
||||
@ -5877,8 +5916,16 @@ snapshots:
|
||||
|
||||
elkjs@0.9.3: {}
|
||||
|
||||
emoji-regex@10.4.0: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
emojibase-data@15.3.2(emojibase@16.0.0):
|
||||
dependencies:
|
||||
emojibase: 16.0.0
|
||||
|
||||
emojibase@16.0.0: {}
|
||||
|
||||
enhanced-resolve@5.18.2:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@ -6353,6 +6400,8 @@ snapshots:
|
||||
|
||||
is-docker@3.0.0: {}
|
||||
|
||||
is-emoji-supported@0.0.5: {}
|
||||
|
||||
is-extendable@0.1.1: {}
|
||||
|
||||
is-extendable@1.0.1:
|
||||
|
@ -121,110 +121,74 @@ const Emoji = Node.create({
|
||||
})
|
||||
|
||||
// 自定义Quote扩展
|
||||
const Quote = Extension.create({
|
||||
const Quote = Node.create({
|
||||
name: 'quote',
|
||||
group: 'block',
|
||||
atom: true,
|
||||
draggable: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
id: {
|
||||
default: null,
|
||||
},
|
||||
title: {
|
||||
default: null,
|
||||
},
|
||||
describe: {
|
||||
default: null,
|
||||
},
|
||||
image: {
|
||||
default: '',
|
||||
},
|
||||
id: { default: null },
|
||||
title: { default: null },
|
||||
describe: { default: null },
|
||||
image: { default: '' }
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'div.quote-card',
|
||||
},
|
||||
]
|
||||
return [{ tag: 'div.quote-card' }]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
const { id, title, describe, image } = HTMLAttributes
|
||||
|
||||
// 创建引用卡片的HTML结构
|
||||
const quoteCardContent = document.createElement('span')
|
||||
quoteCardContent.classList.add('quote-card-content')
|
||||
const titleEl = ['span', { class: 'quote-card-title' }, title || '']
|
||||
let contentChildren = [titleEl]
|
||||
|
||||
const close = document.createElement('span')
|
||||
close.classList.add('quote-card-remove')
|
||||
close.textContent = '×'
|
||||
close.addEventListener('click', (e) => {
|
||||
e.stopPropagation()
|
||||
// 移除引用
|
||||
if (editor.value) {
|
||||
editor.value.commands.deleteNode('quote')
|
||||
}
|
||||
})
|
||||
|
||||
const quoteCardTitle = document.createElement('span')
|
||||
quoteCardTitle.classList.add('quote-card-title')
|
||||
quoteCardTitle.textContent = title
|
||||
quoteCardTitle.appendChild(close)
|
||||
|
||||
quoteCardContent.appendChild(quoteCardTitle)
|
||||
|
||||
if (!image || image.length === 0) {
|
||||
const quoteCardMeta = document.createElement('span')
|
||||
quoteCardMeta.classList.add('quote-card-meta')
|
||||
quoteCardMeta.textContent = describe
|
||||
quoteCardContent.appendChild(quoteCardMeta)
|
||||
} else {
|
||||
const iconImg = document.createElement('img')
|
||||
iconImg.setAttribute('src', image)
|
||||
iconImg.setAttribute('style', 'width:30px;height:30px;margin-right:10px;')
|
||||
quoteCardContent.appendChild(iconImg)
|
||||
if (image && image.length > 0) {
|
||||
contentChildren.push(['img', { src: image, style: 'width:30px;height:30px;margin-right:10px;' }])
|
||||
} else if (describe) {
|
||||
contentChildren.push(['span', { class: 'quote-card-meta' }, describe])
|
||||
}
|
||||
|
||||
const node = document.createElement('div')
|
||||
node.classList.add('quote-card')
|
||||
node.setAttribute('data-id', id)
|
||||
node.setAttribute('data-title', title)
|
||||
node.setAttribute('data-describe', describe)
|
||||
node.setAttribute('data-image', image || '')
|
||||
node.setAttribute('contenteditable', 'false')
|
||||
node.appendChild(quoteCardContent)
|
||||
const cardContent = ['span', { class: 'quote-card-content' }, ...contentChildren]
|
||||
|
||||
return ['div', { class: 'quote-card-wrapper' }, node]
|
||||
return [
|
||||
'div',
|
||||
{
|
||||
class: 'quote-card',
|
||||
'data-id': id,
|
||||
'data-title': title,
|
||||
'data-describe': describe,
|
||||
'data-image': image || '',
|
||||
contenteditable: 'false'
|
||||
},
|
||||
cardContent
|
||||
]
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
'Backspace': () => {
|
||||
Backspace: () => {
|
||||
const { selection } = this.editor.state
|
||||
const { empty, anchor } = selection
|
||||
const { $from, empty } = selection
|
||||
|
||||
if (!empty) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAtStart = anchor === 0
|
||||
|
||||
if (!isAtStart) {
|
||||
return false
|
||||
if ($from.parent.isTextblock && $from.parentOffset === 0) {
|
||||
const nodeBefore = $from.nodeBefore
|
||||
if (nodeBefore && nodeBefore.type.name === this.name) {
|
||||
return this.editor.commands.deleteNode(this.name)
|
||||
}
|
||||
|
||||
// 检查是否有引用节点
|
||||
const quoteNode = this.editor.state.doc.firstChild
|
||||
if (quoteNode && quoteNode.type.name === 'quote') {
|
||||
this.editor.commands.deleteNode('quote')
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 创建自定义键盘处理插件,处理Enter键发送消息
|
||||
@ -709,15 +673,24 @@ function onSubscribeQuote(data) {
|
||||
|
||||
// 检查是否已有引用内容
|
||||
const json = editor.value.getJSON()
|
||||
if (json.content?.some((node) => node.type === 'quote')) {
|
||||
if (json.content?.some(node => node.type === 'quote')) {
|
||||
return // 已有引用则不再添加
|
||||
}
|
||||
|
||||
// 在编辑器开头插入引用
|
||||
editor.value.chain().focus().insertContent({
|
||||
editor.value
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContentAt(0, [
|
||||
{
|
||||
type: 'quote',
|
||||
attrs: data,
|
||||
}).run()
|
||||
attrs: data
|
||||
},
|
||||
{
|
||||
type: 'paragraph'
|
||||
}
|
||||
])
|
||||
.run()
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user