From 62cb9c87c25bfb44b91869b76b7aab7fed7d7cf8 Mon Sep 17 00:00:00 2001
From: Phoenix <64720302+Concur-max@users.noreply.github.com>
Date: Tue, 1 Jul 2025 15:04:36 +0800
Subject: [PATCH 1/9] =?UTF-8?q?fix(editor):=20=E7=A7=BB=E9=99=A4=E8=B0=83?=
=?UTF-8?q?=E8=AF=95=E6=97=A5=E5=BF=97=E5=B9=B6=E4=BF=AE=E5=A4=8D=E5=9B=BE?=
=?UTF-8?q?=E7=89=87=E8=A1=A8=E6=83=85=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
移除无用的console.log调试语句
修复parseEditorContent中图片表情解析逻辑,正确处理表情文本占位符
优化insertImageEmoji方法,移除多余的空格节点插入
---
src/components/editor/CustomEditor.vue | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/src/components/editor/CustomEditor.vue b/src/components/editor/CustomEditor.vue
index 6f83bba..035256b 100644
--- a/src/components/editor/CustomEditor.vue
+++ b/src/components/editor/CustomEditor.vue
@@ -33,7 +33,6 @@ const props = defineProps({
const emit = defineEmits(['editor-event'])
const userStore = useUserStore()
const dialogueStore = useDialogueStore()
-console.log('dialogueStore', dialogueStore.talk.talk_type)
const editorDraftStore = useEditorDraftStore()
const editorRef = ref(null)
const content = ref('')
@@ -172,7 +171,6 @@ const updateMentionPosition = (range) => {
}
}
const insertMention = (member, clonedRange) => {
- console.log('插入mention', member);
const selection = window.getSelection();
if (!clonedRange || !selection || !editorRef.value) return;
const range = clonedRange;
@@ -565,20 +563,20 @@ const parseEditorContent = () => {
currentTextBuffer += '\n';
break;
case 'IMG':
- flushTextBufferIfNeeded();
const src = node.getAttribute('src');
const alt = node.getAttribute('alt');
const isEmojiPic = node.classList.contains('editor-emoji');
const isTextEmojiPlaceholder = node.classList.contains('emoji');
- if (isTextEmojiPlaceholder && alt) {
+ if ((isEmojiPic || isTextEmojiPlaceholder) && alt) {
currentTextBuffer += alt;
} else if (src) {
+ flushTextBufferIfNeeded();
items.push({
type: 3,
content: src,
- isEmoji: isEmojiPic,
- width: node.getAttribute('data-original-width') || node.width || null,
- height: node.getAttribute('data-original-height') || node.height || null,
+ isEmoji: false,
+ width: node.getAttribute('data-original-width') || node.width,
+ height: node.getAttribute('data-original-height') || node.height
});
}
break;
@@ -887,12 +885,8 @@ const insertImageEmoji = (imgSrc, altText) => {
img.className = 'editor-emoji';
img.setAttribute('data-role', 'emoji');
range.insertNode(img);
- const spaceNode = document.createTextNode('\u00A0');
range.setStartAfter(img);
range.collapse(true);
- range.insertNode(spaceNode);
- range.setStartAfter(spaceNode);
- range.collapse(true);
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
From 8e2c134c90845294bff834e80f8c704cf3ce2ad9 Mon Sep 17 00:00:00 2001
From: Phoenix <64720302+Concur-max@users.noreply.github.com>
Date: Wed, 2 Jul 2025 11:31:35 +0800
Subject: [PATCH 2/9] =?UTF-8?q?feat(editor):=20=E6=9B=BF=E6=8D=A2=E8=87=AA?=
=?UTF-8?q?=E5=AE=9A=E4=B9=89=E7=BC=96=E8=BE=91=E5=99=A8=E4=B8=BATiptap?=
=?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E5=B9=B6=E6=B7=BB=E5=8A=A0=E7=9B=B8?=
=?UTF-8?q?=E5=85=B3=E4=BE=9D=E8=B5=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增Tiptap编辑器组件及所需依赖包
- 移除原有自定义编辑器实现
- 更新编辑器相关配置和样式
- 添加表情、图片上传等功能支持
- 优化编辑器草稿保存和恢复逻辑
---
package.json | 8 +
pnpm-lock.yaml | 623 +++++++++
src/components/editor/TiptapEditor.vue | 1109 +++++++++++++++++
src/utils/auth.js | 2 +-
src/views/message/inner/panel/PanelFooter.vue | 5 +-
vite.config.ts | 6 +-
6 files changed, 1747 insertions(+), 6 deletions(-)
create mode 100644 src/components/editor/TiptapEditor.vue
diff --git a/package.json b/package.json
index 838fa27..92879b1 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,14 @@
"@iconify-json/ion": "^1.2.3",
"@kangc/v-md-editor": "^2.3.18",
"@onlyoffice/document-editor-vue": "^1.5.0",
+ "@tiptap/core": "^2.23.1",
+ "@tiptap/extension-image": "^2.23.1",
+ "@tiptap/extension-link": "^2.23.1",
+ "@tiptap/extension-mention": "^2.23.1",
+ "@tiptap/extension-placeholder": "^2.23.1",
+ "@tiptap/pm": "^2.23.1",
+ "@tiptap/starter-kit": "^2.23.1",
+ "@tiptap/vue-3": "^2.23.1",
"@vicons/fluent": "^0.13.0",
"@vicons/ionicons4": "^0.13.0",
"@vicons/ionicons5": "^0.13.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7a35f7d..21901b2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,6 +23,30 @@ importers:
'@onlyoffice/document-editor-vue':
specifier: ^1.5.0
version: 1.5.0(vue@3.5.17(typescript@5.2.2))
+ '@tiptap/core':
+ specifier: ^2.23.1
+ version: 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/extension-image':
+ specifier: ^2.23.1
+ version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-link':
+ specifier: ^2.23.1
+ version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
+ '@tiptap/extension-mention':
+ 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))
+ '@tiptap/extension-placeholder':
+ specifier: ^2.23.1
+ version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
+ '@tiptap/pm':
+ specifier: ^2.23.1
+ version: 2.23.1
+ '@tiptap/starter-kit':
+ specifier: ^2.23.1
+ version: 2.23.1
+ '@tiptap/vue-3':
+ specifier: ^2.23.1
+ version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)(vue@3.5.17(typescript@5.2.2))
'@vicons/fluent':
specifier: ^0.13.0
version: 0.13.0
@@ -711,6 +735,12 @@ packages:
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+ '@popperjs/core@2.11.8':
+ resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
+
+ '@remirror/core-constants@3.0.0':
+ resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
+
'@rollup/pluginutils@5.2.0':
resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==}
engines: {node: '>=14.0.0'}
@@ -850,6 +880,166 @@ packages:
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
engines: {node: '>=18'}
+ '@tiptap/core@2.23.1':
+ resolution: {integrity: sha512-EURGKGsEPrwxvOPi9gA+BsczvsECJNV+xgTAGWHmEtU4YJ0AulYrCX3b7FK+aiduVhThIHDoG/Mmvmb/HPLRhQ==}
+ peerDependencies:
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-blockquote@2.23.1':
+ resolution: {integrity: sha512-GI3s+uFU88LWRaDG20Z9yIu2av3Usn8kw2lkm2ntwX1K6/mQBS/zkGhWr/FSwWOlMtTzYFxF4Ttb0e+hn67A/A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-bold@2.23.1':
+ resolution: {integrity: sha512-OM4RxuZeOqpYRN1G/YpXSE8tZ3sVtT2XlO3qKa74qf+htWz8W3x4X0oQCrHrRTDSAA1wbmeZU3QghAIHnbvP/A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-bubble-menu@2.23.1':
+ resolution: {integrity: sha512-tupuvrlZMTziVZXJuCVjLwllUnux/an9BtTYHpoRyLX9Hg0v7Kh39k9x58zJaoW8Q/Oc/qxPhbJpyOqhE1rLeg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-bullet-list@2.23.1':
+ resolution: {integrity: sha512-0g9U42m+boLJZP3x9KoJHDCp9WD5abaVdqNbTg9sFPDNsepb7Zaeu8AEB+yZLP/fuTI1I4ko6qkdr3UaaIYcmA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-code-block@2.23.1':
+ resolution: {integrity: sha512-eYzJVUR13BhSE/TYAMZihGBId+XiwhnTPqGcSFo+zx89It/vxwDLvAUn0PReMNI7ULKPTw8orUt2fVKSarb2DQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-code@2.23.1':
+ resolution: {integrity: sha512-3IOdE40m0UTR2+UXui69o/apLtutAbtzfgmMxD6q0qlRvVqz99QEfk9RPHDNlUqJtYCL4TD+sj7UclBsDdgVXA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-document@2.23.1':
+ resolution: {integrity: sha512-2nkIkGVsaMJkpd024E6vXK+5XNz8VOVWp/pM6bbXpuv0HnGPrfLdh4ruuFc+xTQ3WPOmpSu8ygtujt4I1o9/6g==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-dropcursor@2.23.1':
+ resolution: {integrity: sha512-GyVp+o/RVrKlLdrQvtIpJGphFGogiPjcPCkAFcrfY1vDY1EYxfVZELC96gG1mUT1BO8FUD3hmbpkWi9l8/6O4A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-floating-menu@2.23.1':
+ resolution: {integrity: sha512-GMWkpH+p/OUOk1Y5UGOnKuHSDEVBN7DhYIJiWt5g9LK/mpPeuqoCmQg3RQDgjtZXb74SlxLK2pS/3YcAnemdfQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-gapcursor@2.23.1':
+ resolution: {integrity: sha512-iP+TiFIGZEbOvYAs04pI14mLI4xqbt64Da91TgMF1FNZUrG+9eWKjqbcHLQREuK3Qnjn5f0DI4nOBv61FlnPmA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-hard-break@2.23.1':
+ resolution: {integrity: sha512-YF66EVxnBxt1bHPx6fUUSSXK1Vg+/9baJ0AfJ12hCSPCgSjUclRuNmWIH5ikVfByOmPV1xlrN9wryLoSEBcNRQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-heading@2.23.1':
+ resolution: {integrity: sha512-5BPoli9wudiAOgSyK8309jyRhFyu5vd02lNChfpHwxUudzIJ/L+0E6FcwrDcw+yXh23cx7F5SSjtFQ7AobxlDQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-history@2.23.1':
+ resolution: {integrity: sha512-1rp2CRjM+P58oGEgeUUDSk0ch67ngIGbGJOOjiBGKU9GIVhI2j4uSwsYTAa9qYMjMUI6IyH1xJpsY2hLKcBOtg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-horizontal-rule@2.23.1':
+ resolution: {integrity: sha512-uHEF0jpmhtgAxjKw8/s5ipEeTnu99f9RVMGAlmcthJ5Fx9TzH0MvtH4dtBNEu5MXC7+0bNsnncWo125AAbCohg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-image@2.23.1':
+ resolution: {integrity: sha512-8j2FLBWKq6j27aQcOAlmyKixxHflW8j5FhxLgPPS6hithgFQVET4OYH+1c6r7Qd/T4YoAqt/0PmNZ/gYWI9gzg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-italic@2.23.1':
+ resolution: {integrity: sha512-a+cPzffaC/1AKMmZ1Ka6l81xmTgcalf8NXfBuFCUTf5r7uI9NIgXnLo9hg+jR9F4K+bwhC4/UbMvQQzAjh0c0A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-link@2.23.1':
+ resolution: {integrity: sha512-zMD0V8djkvwRYACzd8EvFXXNLQH5poJt6aHC9/8uest6njRhlrRjSjwG5oa+xHW4A76XfAH0A5BPj6ZxZnAUQg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-list-item@2.23.1':
+ resolution: {integrity: sha512-wVrRp6KAiyjFVFGmn+ojisP64Bsd+ZPdqQBYVbebBx1skZeW0uhG60d7vUkWHi0gCgxHZDfvDbXpfnOD0INRWw==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-mention@2.23.1':
+ resolution: {integrity: sha512-YaHg2ZqNb2Vur5hBoswzu8pJ3kV54PPonR8gHgelD83T1s8vBqdl03WL2NUVciovEbENnirePLDfhKaFjRXSzQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+ '@tiptap/suggestion': ^2.7.0
+
+ '@tiptap/extension-ordered-list@2.23.1':
+ resolution: {integrity: sha512-Zp+qognyNgoaJ9bxkBwIuWJEnQ67RdsHXzv3YOdeGRbkUhd8LT6OL7P0mAuNbMBU8MwHxyJ7C7NsyzwzuVbFzA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-paragraph@2.23.1':
+ resolution: {integrity: sha512-LLEPizt1ALE7Ek6prlJ1uhoUCT8C/a3PdZpCh3DshM1L3Kv9TENlaJL2GhFl8SVUCwHmWHvXg30+4tIRFBedaQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-placeholder@2.23.1':
+ resolution: {integrity: sha512-zSkCljVpxJh3GHW7ppFNYhHPjYKmS3Tw0e74BOGzb5TqP57GRvnPgDGg4fr6kDsSWMo9minnNM3PDA7kNT+PRQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-strike@2.23.1':
+ resolution: {integrity: sha512-hAT9peYkKezRGp/EcPQKtyYQT+2XGUbb26toTr9XIBQIeQCuCpT+FirPrDMrMVWPwcJt7Rv+AzoVjDuBs9wE0A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-text-style@2.23.1':
+ resolution: {integrity: sha512-fZn1GePlL27pUFfKXKoRZo4L4pZP9dUjNNiS/eltLpbi/SenJ15UKhAoHtN1KQvNGJsWkYN49FjnnltU8qvQ+Q==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-text@2.23.1':
+ resolution: {integrity: sha512-XK0D/eyS1Vm5yUrCtkS0AfgyKLJqpi8nJivCOux/JLhhC4x87R1+mI8NoFDYZJ5ic/afREPSBB8jORqOi0qIHg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/pm@2.23.1':
+ resolution: {integrity: sha512-iAx4rP0k4Xd9Ywh+Gpaz5IWfY2CYRpiwVXWekTHLlNRFtrVIWVpMxaQr2mvRU2g0Ca6rz5w3KzkHBMqrI3dIBA==}
+
+ '@tiptap/starter-kit@2.23.1':
+ resolution: {integrity: sha512-rrImwzJbKSHoFa+WdNU4I0evXcMiQ4yRm737sxvNJwYItT6fXIxrbRT7nJDmtYu2TflcfT1KklEnSrzz1hhYRw==}
+
+ '@tiptap/suggestion@2.23.1':
+ resolution: {integrity: sha512-ryAsYhXmehwQLJnGFw9VHfQMANeWgFzDQUfgg0vcjbHJAa0t3qBsSOUP2RKOuErQmCvlgY/cgxO/6Q0l5IzsIA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/vue-3@2.23.1':
+ resolution: {integrity: sha512-wBF9LxI96qMXE0Z+3C8GU4mmDRbCapcnjb/euQ1NeihqBwK6qdd5g8IIgGm+rQCSY8hCJBUTWJtfn7vJAasSDQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+ vue: ^3.0.0
+
'@tsconfig/node18@18.2.4':
resolution: {integrity: sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==}
@@ -883,15 +1073,24 @@ packages:
'@types/katex@0.16.7':
resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
+ '@types/linkify-it@5.0.0':
+ resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
+
'@types/lodash-es@4.17.12':
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
'@types/lodash@4.17.18':
resolution: {integrity: sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g==}
+ '@types/markdown-it@14.1.2':
+ resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
+
'@types/mdast@3.0.15':
resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==}
+ '@types/mdurl@2.0.0':
+ resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
+
'@types/minimatch@5.1.2':
resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
@@ -1485,6 +1684,9 @@ packages:
cose-base@1.0.3:
resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
+ crelt@1.0.6:
+ resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
+
cross-env@7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
@@ -1881,6 +2083,10 @@ packages:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
escape-string-regexp@5.0.0:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
@@ -2442,6 +2648,12 @@ packages:
linkify-it@3.0.3:
resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==}
+ linkify-it@5.0.0:
+ resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+
+ linkifyjs@4.3.1:
+ resolution: {integrity: sha512-DRSlB9DKVW04c4SUdGvKK5FR6be45lTU9M76JnngqPeeGDqPwYc0zdUErtsNVMtxPXgUWV4HbXbnC4sNyBxkYg==}
+
loader-runner@4.3.0:
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
engines: {node: '>=6.11.5'}
@@ -2516,6 +2728,10 @@ packages:
resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==}
hasBin: true
+ markdown-it@14.1.0:
+ resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
+ hasBin: true
+
markdown-it@8.4.2:
resolution: {integrity: sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==}
hasBin: true
@@ -2536,6 +2752,9 @@ packages:
mdurl@1.0.1:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
+ mdurl@2.0.0:
+ resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
+
memorystream@0.3.1:
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
engines: {node: '>= 0.10.0'}
@@ -2766,6 +2985,9 @@ packages:
resolution: {integrity: sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==}
engines: {node: '>=18'}
+ orderedmap@2.1.1:
+ resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
+
package-manager-detector@1.3.0:
resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==}
@@ -2885,12 +3107,74 @@ packages:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'}
+ prosemirror-changeset@2.3.1:
+ resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==}
+
+ prosemirror-collab@1.3.1:
+ resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==}
+
+ prosemirror-commands@1.7.1:
+ resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==}
+
+ prosemirror-dropcursor@1.8.2:
+ resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==}
+
+ prosemirror-gapcursor@1.3.2:
+ resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==}
+
+ prosemirror-history@1.4.1:
+ resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==}
+
+ prosemirror-inputrules@1.5.0:
+ resolution: {integrity: sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==}
+
+ prosemirror-keymap@1.2.3:
+ resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==}
+
+ prosemirror-markdown@1.13.2:
+ resolution: {integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==}
+
+ prosemirror-menu@1.2.5:
+ resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==}
+
+ prosemirror-model@1.25.1:
+ resolution: {integrity: sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==}
+
+ prosemirror-schema-basic@1.2.4:
+ resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==}
+
+ prosemirror-schema-list@1.5.1:
+ resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==}
+
+ prosemirror-state@1.4.3:
+ resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==}
+
+ prosemirror-tables@1.7.1:
+ resolution: {integrity: sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==}
+
+ prosemirror-trailing-node@3.0.0:
+ resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==}
+ peerDependencies:
+ prosemirror-model: ^1.22.1
+ prosemirror-state: ^1.4.2
+ prosemirror-view: ^1.33.8
+
+ prosemirror-transform@1.10.4:
+ resolution: {integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==}
+
+ prosemirror-view@1.40.0:
+ resolution: {integrity: sha512-2G3svX0Cr1sJjkD/DYWSe3cfV5VPVTBOxI9XQEGWJDFEpsZb/gh4MV29ctv+OJx2RFX4BLt09i+6zaGM/ldkCw==}
+
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
+ punycode.js@2.3.1:
+ resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+ engines: {node: '>=6'}
+
quansync@0.2.10:
resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==}
@@ -2980,6 +3264,9 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ rope-sequence@1.3.4:
+ resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
+
run-applescript@7.0.0:
resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==}
engines: {node: '>=18'}
@@ -3223,6 +3510,9 @@ packages:
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
engines: {node: '>=12.0.0'}
+ tippy.js@6.3.7:
+ resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==}
+
to-object-path@0.3.0:
resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==}
engines: {node: '>=0.10.0'}
@@ -3274,6 +3564,9 @@ packages:
uc.micro@1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
+ uc.micro@2.1.0:
+ resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+
ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
@@ -3538,6 +3831,9 @@ packages:
peerDependencies:
vue: ^3.0.11
+ w3c-keyname@2.2.8:
+ resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+
wait-on@6.0.1:
resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==}
engines: {node: '>=10.0.0'}
@@ -4123,6 +4419,10 @@ snapshots:
'@polka/url@1.0.0-next.29': {}
+ '@popperjs/core@2.11.8': {}
+
+ '@remirror/core-constants@3.0.0': {}
+
'@rollup/pluginutils@5.2.0(rollup@4.44.0)':
dependencies:
'@types/estree': 1.0.8
@@ -4208,6 +4508,182 @@ snapshots:
'@sindresorhus/merge-streams@4.0.0': {}
+ '@tiptap/core@2.23.1(@tiptap/pm@2.23.1)':
+ dependencies:
+ '@tiptap/pm': 2.23.1
+
+ '@tiptap/extension-blockquote@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-bold@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-bubble-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)
+ '@tiptap/pm': 2.23.1
+ tippy.js: 6.3.7
+
+ '@tiptap/extension-bullet-list@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-code-block@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)
+ '@tiptap/pm': 2.23.1
+
+ '@tiptap/extension-code@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-document@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-dropcursor@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)
+ '@tiptap/pm': 2.23.1
+
+ '@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)
+ '@tiptap/pm': 2.23.1
+ tippy.js: 6.3.7
+
+ '@tiptap/extension-gapcursor@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)
+ '@tiptap/pm': 2.23.1
+
+ '@tiptap/extension-hard-break@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-heading@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-history@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)
+ '@tiptap/pm': 2.23.1
+
+ '@tiptap/extension-horizontal-rule@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)
+ '@tiptap/pm': 2.23.1
+
+ '@tiptap/extension-image@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-italic@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-link@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)
+ '@tiptap/pm': 2.23.1
+ linkifyjs: 4.3.1
+
+ '@tiptap/extension-list-item@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-mention@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))':
+ 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)
+
+ '@tiptap/extension-ordered-list@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-paragraph@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-placeholder@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)
+ '@tiptap/pm': 2.23.1
+
+ '@tiptap/extension-strike@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-text-style@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/extension-text@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+
+ '@tiptap/pm@2.23.1':
+ dependencies:
+ prosemirror-changeset: 2.3.1
+ prosemirror-collab: 1.3.1
+ prosemirror-commands: 1.7.1
+ prosemirror-dropcursor: 1.8.2
+ prosemirror-gapcursor: 1.3.2
+ prosemirror-history: 1.4.1
+ prosemirror-inputrules: 1.5.0
+ prosemirror-keymap: 1.2.3
+ prosemirror-markdown: 1.13.2
+ prosemirror-menu: 1.2.5
+ prosemirror-model: 1.25.1
+ prosemirror-schema-basic: 1.2.4
+ prosemirror-schema-list: 1.5.1
+ prosemirror-state: 1.4.3
+ prosemirror-tables: 1.7.1
+ prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)
+ prosemirror-transform: 1.10.4
+ prosemirror-view: 1.40.0
+
+ '@tiptap/starter-kit@2.23.1':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/extension-blockquote': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-bold': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-bullet-list': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-code': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-code-block': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
+ '@tiptap/extension-document': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-dropcursor': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
+ '@tiptap/extension-gapcursor': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
+ '@tiptap/extension-hard-break': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-heading': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-history': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
+ '@tiptap/extension-horizontal-rule': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
+ '@tiptap/extension-italic': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-list-item': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-ordered-list': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-paragraph': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-strike': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-text': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ '@tiptap/extension-text-style': 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)':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/pm': 2.23.1
+
+ '@tiptap/vue-3@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)(vue@3.5.17(typescript@5.2.2))':
+ dependencies:
+ '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/extension-bubble-menu': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
+ '@tiptap/extension-floating-menu': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
+ '@tiptap/pm': 2.23.1
+ vue: 3.5.17(typescript@5.2.2)
+
'@tsconfig/node18@18.2.4': {}
'@types/d3-scale-chromatic@3.1.0': {}
@@ -4243,16 +4719,25 @@ snapshots:
'@types/katex@0.16.7': {}
+ '@types/linkify-it@5.0.0': {}
+
'@types/lodash-es@4.17.12':
dependencies:
'@types/lodash': 4.17.18
'@types/lodash@4.17.18': {}
+ '@types/markdown-it@14.1.2':
+ dependencies:
+ '@types/linkify-it': 5.0.0
+ '@types/mdurl': 2.0.0
+
'@types/mdast@3.0.15':
dependencies:
'@types/unist': 2.0.11
+ '@types/mdurl@2.0.0': {}
+
'@types/minimatch@5.1.2': {}
'@types/ms@2.1.0': {}
@@ -5058,6 +5543,8 @@ snapshots:
dependencies:
layout-base: 1.0.2
+ crelt@1.0.6: {}
+
cross-env@7.0.3:
dependencies:
cross-spawn: 7.0.6
@@ -5479,6 +5966,8 @@ snapshots:
escape-string-regexp@1.0.5: {}
+ escape-string-regexp@4.0.0: {}
+
escape-string-regexp@5.0.0: {}
eslint-scope@5.1.1:
@@ -6033,6 +6522,12 @@ snapshots:
dependencies:
uc.micro: 1.0.6
+ linkify-it@5.0.0:
+ dependencies:
+ uc.micro: 2.1.0
+
+ linkifyjs@4.3.1: {}
+
loader-runner@4.3.0: {}
local-pkg@1.1.1:
@@ -6100,6 +6595,15 @@ snapshots:
mdurl: 1.0.1
uc.micro: 1.0.6
+ markdown-it@14.1.0:
+ dependencies:
+ argparse: 2.0.1
+ entities: 4.5.0
+ linkify-it: 5.0.0
+ mdurl: 2.0.0
+ punycode.js: 2.3.1
+ uc.micro: 2.1.0
+
markdown-it@8.4.2:
dependencies:
argparse: 1.0.10
@@ -6135,6 +6639,8 @@ snapshots:
mdurl@1.0.1: {}
+ mdurl@2.0.0: {}
+
memorystream@0.3.1: {}
merge-stream@2.0.0: {}
@@ -6489,6 +6995,8 @@ snapshots:
is-inside-container: 1.0.0
is-wsl: 3.1.0
+ orderedmap@2.1.1: {}
+
package-manager-detector@1.3.0: {}
parchment@1.1.4: {}
@@ -6575,11 +7083,116 @@ snapshots:
prismjs@1.30.0: {}
+ prosemirror-changeset@2.3.1:
+ dependencies:
+ prosemirror-transform: 1.10.4
+
+ prosemirror-collab@1.3.1:
+ dependencies:
+ prosemirror-state: 1.4.3
+
+ prosemirror-commands@1.7.1:
+ dependencies:
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+
+ prosemirror-dropcursor@1.8.2:
+ dependencies:
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+ prosemirror-view: 1.40.0
+
+ prosemirror-gapcursor@1.3.2:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-view: 1.40.0
+
+ prosemirror-history@1.4.1:
+ dependencies:
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+ prosemirror-view: 1.40.0
+ rope-sequence: 1.3.4
+
+ prosemirror-inputrules@1.5.0:
+ dependencies:
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+
+ prosemirror-keymap@1.2.3:
+ dependencies:
+ prosemirror-state: 1.4.3
+ w3c-keyname: 2.2.8
+
+ prosemirror-markdown@1.13.2:
+ dependencies:
+ '@types/markdown-it': 14.1.2
+ markdown-it: 14.1.0
+ prosemirror-model: 1.25.1
+
+ prosemirror-menu@1.2.5:
+ dependencies:
+ crelt: 1.0.6
+ prosemirror-commands: 1.7.1
+ prosemirror-history: 1.4.1
+ prosemirror-state: 1.4.3
+
+ prosemirror-model@1.25.1:
+ dependencies:
+ orderedmap: 2.1.1
+
+ prosemirror-schema-basic@1.2.4:
+ dependencies:
+ prosemirror-model: 1.25.1
+
+ prosemirror-schema-list@1.5.1:
+ dependencies:
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+
+ prosemirror-state@1.4.3:
+ dependencies:
+ prosemirror-model: 1.25.1
+ prosemirror-transform: 1.10.4
+ prosemirror-view: 1.40.0
+
+ prosemirror-tables@1.7.1:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+ prosemirror-view: 1.40.0
+
+ prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0):
+ dependencies:
+ '@remirror/core-constants': 3.0.0
+ escape-string-regexp: 4.0.0
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-view: 1.40.0
+
+ prosemirror-transform@1.10.4:
+ dependencies:
+ prosemirror-model: 1.25.1
+
+ prosemirror-view@1.40.0:
+ dependencies:
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+
proxy-from-env@1.1.0: {}
prr@1.0.1:
optional: true
+ punycode.js@2.3.1: {}
+
quansync@0.2.10: {}
queue-microtask@1.2.3: {}
@@ -6688,6 +7301,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.44.0
fsevents: 2.3.3
+ rope-sequence@1.3.4: {}
+
run-applescript@7.0.0: {}
run-parallel@1.2.0:
@@ -6929,6 +7544,10 @@ snapshots:
fdir: 6.4.6(picomatch@4.0.2)
picomatch: 4.0.2
+ tippy.js@6.3.7:
+ dependencies:
+ '@popperjs/core': 2.11.8
+
to-object-path@0.3.0:
dependencies:
kind-of: 3.2.2
@@ -6969,6 +7588,8 @@ snapshots:
uc.micro@1.0.6: {}
+ uc.micro@2.1.0: {}
+
ufo@1.6.1: {}
unconfig@0.3.13:
@@ -7270,6 +7891,8 @@ snapshots:
vooks: 0.2.12(vue@3.5.17(typescript@5.2.2))
vue: 3.5.17(typescript@5.2.2)
+ w3c-keyname@2.2.8: {}
+
wait-on@6.0.1:
dependencies:
axios: 0.25.0
diff --git a/src/components/editor/TiptapEditor.vue b/src/components/editor/TiptapEditor.vue
new file mode 100644
index 0000000..eaa0742
--- /dev/null
+++ b/src/components/editor/TiptapEditor.vue
@@ -0,0 +1,1109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/utils/auth.js b/src/utils/auth.js
index 9683ec7..ea2b8c1 100644
--- a/src/utils/auth.js
+++ b/src/utils/auth.js
@@ -18,7 +18,7 @@ export function isLoggedIn() {
*/
export function getAccessToken() {
// return storage.get(AccessToken) || ''
- return JSON.parse(localStorage.getItem('token'))||'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b897a4f2416a772eacd03215226020e2e551cdac98368e42541ee3082dc07317d4ecc6a5dfbbe2a28f8c48ccfae7bc6046c3b9b79c0eb3a1ec4c25f5d766a2f8f01f64da8f70f7dbf63e124ffcf72398d86'
+ return JSON.parse(localStorage.getItem('token'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d22f227a3ad4383a2ddef0a2f43b855f869560968fd2dae0412498273e591b78554b2373a17017cdaae7c9ec427325b7d078d54cb00b001c9894c8e1f990747c8db3b62b17eb8ed39e2b2c2b6d63ce26756'
}
/**
diff --git a/src/views/message/inner/panel/PanelFooter.vue b/src/views/message/inner/panel/PanelFooter.vue
index 6ba23fa..7170135 100644
--- a/src/views/message/inner/panel/PanelFooter.vue
+++ b/src/views/message/inner/panel/PanelFooter.vue
@@ -13,6 +13,7 @@ import { ServePublishMessage, ServeSendVote } from '@/api/chat'
import { throttle, getVideoImage } from '@/utils/common'
import { parseTime } from '@/utils/datetime'
import Editor from '@/components/editor/Editor.vue'
+import TiptapEditor from '@/components/editor/TiptapEditor.vue'
import MultiSelectFooter from './MultiSelectFooter.vue'
import HistoryRecord from '@/components/talk/HistoryRecord.vue'
import {scrollToBottom} from '@/utils/dom.ts'
@@ -294,9 +295,9 @@ onMounted(() => {
{
vueJsx({}),
compressPlugin(),
UnoCSS(),
- vueDevTools({
- launchEditor: 'trae',
- })
+ // vueDevTools({
+ // launchEditor: 'trae',
+ // })
],
define: {
__APP_ENV__: env.APP_ENV
From dd170cb50d0243bfef8351f18502af388409d9b5 Mon Sep 17 00:00:00 2001
From: Phoenix <64720302+Concur-max@users.noreply.github.com>
Date: Wed, 2 Jul 2025 13:15:16 +0800
Subject: [PATCH 3/9] =?UTF-8?q?feat(editor):=20=E6=B7=BB=E5=8A=A0blockquot?=
=?UTF-8?q?e=E5=92=8Cemoji=E6=89=A9=E5=B1=95=E5=B9=B6=E9=87=8D=E6=9E=84?=
=?UTF-8?q?=E5=BC=95=E7=94=A8=E5=8D=A1=E7=89=87=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 添加@tiptap/extension-blockquote和@tiptap/extension-emoji依赖
- 将自定义Quote扩展从Extension改为Node实现
- 简化引用卡片的HTML渲染逻辑
- 改进引用卡片的键盘删除行为
- 优化引用内容的插入位置和格式
---
package.json | 2 +
pnpm-lock.yaml | 49 ++++++++
src/components/editor/TiptapEditor.vue | 155 ++++++++++---------------
3 files changed, 115 insertions(+), 91 deletions(-)
diff --git a/package.json b/package.json
index 92879b1..f0a1f0a 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 21901b2..3ded82e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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:
diff --git a/src/components/editor/TiptapEditor.vue b/src/components/editor/TiptapEditor.vue
index eaa0742..0604af1 100644
--- a/src/components/editor/TiptapEditor.vue
+++ b/src/components/editor/TiptapEditor.vue
@@ -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 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)
+
+ const titleEl = ['span', { class: 'quote-card-title' }, title || '']
+ let contentChildren = [titleEl]
+
+ 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)
-
- return ['div', { class: 'quote-card-wrapper' }, node]
+
+ const cardContent = ['span', { class: 'quote-card-content' }, ...contentChildren]
+
+ 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键发送消息
@@ -706,18 +670,27 @@ function onSubscribeMention(data) {
*/
function onSubscribeQuote(data) {
if (!editor.value) return
-
+
// 检查是否已有引用内容
const json = editor.value.getJSON()
- if (json.content?.some((node) => node.type === 'quote')) {
- return // 已有引用则不再添加
+ if (json.content?.some(node => node.type === 'quote')) {
+ return // 已有引用则不再添加
}
// 在编辑器开头插入引用
- editor.value.chain().focus().insertContent({
- type: 'quote',
- attrs: data,
- }).run()
+ editor.value
+ .chain()
+ .focus()
+ .insertContentAt(0, [
+ {
+ type: 'quote',
+ attrs: data
+ },
+ {
+ type: 'paragraph'
+ }
+ ])
+ .run()
}
/**
From 8be8afc6751e980ca769ffb0275faa8832420a9c Mon Sep 17 00:00:00 2001
From: Phoenix <64720302+Concur-max@users.noreply.github.com>
Date: Wed, 2 Jul 2025 13:34:23 +0800
Subject: [PATCH 4/9] =?UTF-8?q?refactor(TiptapEditor):=20=E9=87=8D?=
=?UTF-8?q?=E6=9E=84=E6=B6=88=E6=81=AF=E8=BD=AC=E6=8D=A2=E9=80=BB=E8=BE=91?=
=?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=E5=A4=9A=E6=B6=88=E6=81=AF=E5=88=86?=
=?UTF-8?q?=E6=AE=B5=E5=A4=84=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
重构 tiptapToMessage 函数,将单条消息处理改为支持多条消息分段处理
优化消息内容处理流程,添加文本缓冲区和图片单独处理逻辑
简化消息发送逻辑,移除 msgType 判断改为直接处理不同类型消息
清理已注释的导航功能代码
---
src/components/editor/TiptapEditor.vue | 261 ++++++++++++-------------
1 file changed, 121 insertions(+), 140 deletions(-)
diff --git a/src/components/editor/TiptapEditor.vue b/src/components/editor/TiptapEditor.vue
index 0604af1..e19da1e 100644
--- a/src/components/editor/TiptapEditor.vue
+++ b/src/components/editor/TiptapEditor.vue
@@ -457,71 +457,106 @@ function onRecorderEvent(file) {
// 将Tiptap内容转换为消息格式
function tiptapToMessage() {
- if (!editor.value) return { items: [], mentions: [], mentionUids: [], quoteId: '', msgType: 1 }
-
+ if (!editor.value) return []
+
const json = editor.value.getJSON()
- const resp = {
- items: [],
- mentions: [],
- mentionUids: [],
- quoteId: '',
- msgType: 1
- }
-
- // 处理引用
- const quoteNode = json.content?.find((node) => node.type === 'quote')
- if (quoteNode) {
- resp.quoteId = quoteNode.attrs.id
- }
-
- // 处理内容
- let textContent = ''
- let hasImage = false
-
- const processNode = (node) => {
- if (node.type === 'text') {
- textContent += node.text
- } else if (node.type === 'mention') {
- textContent += ` @${node.attrs.label} `
- resp.mentions.push({
- name: `@${node.attrs.label}`,
- atid: parseInt(node.attrs.id)
- })
- } else if (node.type === 'emoji') {
- textContent += node.attrs.alt
- } else if (node.type === 'image') {
- hasImage = true
- resp.items.push({
- type: 3,
- content: node.attrs.src
- })
- } else if (node.content) {
- node.content.forEach(processNode)
+ const messages = []
+ let quoteId = null
+ let currentTextBuffer = ''
+ let currentMentions = []
+ let currentMentionUids = new Set()
+
+ const flushTextBuffer = () => {
+ const content = currentTextBuffer.trim()
+ if (content) {
+ const data = {
+ items: [{ type: 1, content: content }],
+ mentions: [...currentMentions],
+ mentionUids: Array.from(currentMentionUids)
+ }
+ if (quoteId) {
+ data.quoteId = quoteId
+ quoteId = null
+ }
+ messages.push({ type: 'text', data })
}
+ currentTextBuffer = ''
+ currentMentions = []
+ currentMentionUids.clear()
}
-
- if (json.content) {
- json.content.forEach(processNode)
- }
-
- // 如果有文本内容,添加到items
- if (textContent.trim()) {
- resp.items.unshift({
- type: 1,
- content: textContent.trim()
+
+ const processInlines = nodes => {
+ nodes.forEach(node => {
+ if (node.type === 'text') {
+ currentTextBuffer += node.text
+ } else if (node.type === 'mention') {
+ currentTextBuffer += `@${node.attrs.label} `
+ const uid = parseInt(node.attrs.id)
+ if (!currentMentionUids.has(uid)) {
+ currentMentionUids.add(uid)
+ currentMentions.push({ name: `@${node.attrs.label}`, atid: uid })
+ }
+ } else if (node.type === 'emoji') {
+ currentTextBuffer += node.attrs.alt
+ } else if (node.type === 'hardBreak') {
+ currentTextBuffer += '\n'
+ } else if (node.type === 'image') {
+ // 处理段落内的图片
+ flushTextBuffer()
+ const data = {
+ ...getImageInfo(node.attrs.src),
+ url: node.attrs.src
+ }
+ if (quoteId) {
+ data.quoteId = quoteId
+ quoteId = null
+ }
+ messages.push({ type: 'image', data })
+ }
})
}
-
- // 设置消息类型
- if (resp.items.length > 1) {
- resp.msgType = 12 // 混合消息
- } else if (resp.items.length === 1) {
- resp.msgType = resp.items[0].type
+
+ if (json.content) {
+ const quoteIndex = json.content.findIndex(node => node.type === 'quote')
+ if (quoteIndex > -1) {
+ quoteId = json.content[quoteIndex].attrs.id
+ json.content.splice(quoteIndex, 1)
+ }
+
+ json.content.forEach(node => {
+ if (node.type === 'paragraph') {
+ if (node.content) {
+ processInlines(node.content)
+ }
+ currentTextBuffer += '\n' // Add newline after each paragraph
+ } else if (node.type === 'image') {
+ flushTextBuffer()
+ const data = {
+ ...getImageInfo(node.attrs.src),
+ url: node.attrs.src
+ }
+ if (quoteId) {
+ data.quoteId = quoteId
+ quoteId = null
+ }
+ messages.push({ type: 'image', data })
+ }
+ })
}
-
- resp.mentionUids = resp.mentions.map((item) => item.atid)
-
- return resp
+
+ flushTextBuffer()
+
+ if (messages.length > 0) {
+ const lastMessage = messages[messages.length - 1]
+ if (lastMessage.type === 'text') {
+ lastMessage.data.items[0].content = lastMessage.data.items[0].content.trim()
+ if (!lastMessage.data.items[0].content) {
+ messages.pop()
+ }
+ }
+ }
+
+ return messages
}
// 将Tiptap内容转换为纯文本
@@ -550,52 +585,36 @@ function isEditorEmpty() {
* 根据编辑器内容类型发送不同类型的消息
*/
function onSendMessage() {
- if (!editor.value) return
-
- if (isEditorEmpty()) return
-
- const data = tiptapToMessage()
-
- if (data.items.length === 0 || (data.items[0].type === 1 && !data.items[0].content.trim())) {
- return // 没有内容不发送
+ if (!editor.value || isEditorEmpty()) return
+
+ const messages = tiptapToMessage()
+
+ if (messages.length === 0) {
+ return
}
- switch (data.msgType) {
- case 1: // 文字消息
- if (data.items[0].content.length > 1024) {
- return window['$message'].info('发送内容超长,请分条发送')
+ let canClear = true
+ messages.forEach(msg => {
+ if (msg.type === 'text') {
+ if (msg.data.items[0].content.length > 1024) {
+ window['$message'].info('发送内容超长,请分条发送')
+ canClear = false
+ return
}
+ emit('editor-event', emitCall('text_event', msg.data))
+ } else if (msg.type === 'image') {
+ const data = {
+ height: 0,
+ width: 0,
+ size: 10000,
+ url: msg.data.url,
+ }
+ emit('editor-event', emitCall('image_event', data))
+ }
+ })
- // 发送文本消息
- emit(
- 'editor-event',
- emitCall('text_event', data, (ok) => {
- ok && editor.value?.commands.clearContent(true) // 成功发送后清空编辑器
- })
- )
- break
- case 3: // 图片消息
- // 发送图片消息
- emit(
- 'editor-event',
- emitCall(
- 'image_event',
- { ...getImageInfo(data.items[0].content), url: data.items[0].content, size: 10000 },
- (ok) => {
- ok && editor.value?.commands.clearContent(true) // 成功发送后清空编辑器
- }
- )
- )
- break
- case 12: // 图文混合消息
- // 发送混合消息
- emit(
- 'editor-event',
- emitCall('mixed_event', data, (ok) => {
- ok && editor.value?.commands.clearContent(true) // 成功发送后清空编辑器
- })
- )
- break
+ if (canClear) {
+ editor.value?.commands.clearContent(true)
}
}
@@ -728,45 +747,7 @@ const navs = reactive([
uploadFileRef.value.click() // 触发文件上传
}
},
- // 以下功能已被注释掉,但保留代码
- // {
- // title: '代码',
- // icon: markRaw(SourceCode),
- // show: true,
- // click: () => {
- // isShowEditorCode.value = true
- // }
- // },
- // {
- // title: '语音消息',
- // icon: markRaw(IconVoice),
- // show: true,
- // click: () => {
- // isShowEditorRecorder.value = true
- // }
- // },
- // {
- // title: '地理位置',
- // icon: markRaw(Local),
- // show: true,
- // click: () => {}
- // },
- // {
- // title: '群投票',
- // icon: markRaw(Ranking),
- // show: computed(() => props.vote),
- // click: () => {
- // isShowEditorVote.value = true
- // }
- // },
- // {
- // title: '历史记录',
- // icon: markRaw(History),
- // show: true,
- // click: () => {
- // emit('editor-event', emitCall('history_event'))
- // }
- // }
+
])
// 监听聊天索引变化,切换聊天时加载对应草稿
From 0f161de28f1ddfa1f85327b47256a28236e34edf Mon Sep 17 00:00:00 2001
From: Phoenix <64720302+Concur-max@users.noreply.github.com>
Date: Wed, 2 Jul 2025 15:57:03 +0800
Subject: [PATCH 5/9] =?UTF-8?q?feat(editor):=20=E9=87=8D=E6=9E=84=E5=BC=95?=
=?UTF-8?q?=E7=94=A8=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96=E5=9B=BE?=
=?UTF-8?q?=E7=89=87=E4=B8=8A=E4=BC=A0=E5=A4=84=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 移除旧的Quote节点扩展,改为使用quoteData状态管理引用消息
- 添加图片上传状态跟踪和加载指示器
- 优化提及列表的交互和关闭行为
- 支持粘贴图片自动上传功能
- 完善编辑器草稿保存机制,包含引用数据
---
src/components/editor/TiptapEditor.vue | 476 +++++++++++++++++--------
1 file changed, 319 insertions(+), 157 deletions(-)
diff --git a/src/components/editor/TiptapEditor.vue b/src/components/editor/TiptapEditor.vue
index e19da1e..77a8996 100644
--- a/src/components/editor/TiptapEditor.vue
+++ b/src/components/editor/TiptapEditor.vue
@@ -12,7 +12,7 @@ import { Plugin, PluginKey } from '@tiptap/pm/state'
// 引入Vue核心功能
import { reactive, watch, ref, markRaw, computed, onMounted, onUnmounted, shallowRef } from 'vue'
// 引入Naive UI的弹出框组件
-import { NPopover } from 'naive-ui'
+import { NPopover, NIcon } from 'naive-ui'
// 引入图标组件
import {
Voice as IconVoice, // 语音图标
@@ -22,7 +22,8 @@ import {
Pic, // 图片图标
FolderUpload, // 文件上传图标
Ranking, // 排名图标(用于投票)
- History // 历史记录图标
+ History, // 历史记录图标
+ Close // 关闭图标
} from '@icon-park/vue-next'
// 引入状态管理
@@ -70,6 +71,7 @@ const isShowEditorVote = ref(false)
const isShowEditorCode = ref(false)
// 控制是否显示录音界面
const isShowEditorRecorder = ref(false)
+const uploadingImages = ref(new Map())
// 图片文件上传DOM引用
const fileImageRef = ref()
// 文件上传DOM引用
@@ -78,6 +80,8 @@ const uploadFileRef = ref()
const emoticonRef = ref()
// 表情面板显示状态
const showEmoticon = ref(false)
+// 引用消息数据
+const quoteData = ref(null)
// 自定义Emoji扩展
const Emoji = Node.create({
@@ -120,76 +124,7 @@ const Emoji = Node.create({
},
})
-// 自定义Quote扩展
-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: '' }
- }
- },
-
- parseHTML() {
- return [{ tag: 'div.quote-card' }]
- },
-
- renderHTML({ HTMLAttributes }) {
- const { id, title, describe, image } = HTMLAttributes
-
- const titleEl = ['span', { class: 'quote-card-title' }, title || '']
- let contentChildren = [titleEl]
-
- 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 cardContent = ['span', { class: 'quote-card-content' }, ...contentChildren]
-
- return [
- 'div',
- {
- class: 'quote-card',
- 'data-id': id,
- 'data-title': title,
- 'data-describe': describe,
- 'data-image': image || '',
- contenteditable: 'false'
- },
- cardContent
- ]
- },
-
- addKeyboardShortcuts() {
- return {
- Backspace: () => {
- const { selection } = this.editor.state
- const { $from, empty } = selection
-
- if (!empty) {
- 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)
- }
- }
-
- return false
- }
- }
- }
-})
// 创建自定义键盘处理插件,处理Enter键发送消息
const EnterKeyPlugin = new Plugin({
@@ -222,7 +157,43 @@ const CustomKeyboard = Extension.create({
const editor = useEditor({
extensions: [
StarterKit,
- Image.configure({
+ Image.extend({
+ addNodeView() {
+ return ({ node, getPos, editor }) => {
+ const container = document.createElement('span')
+ container.style.position = 'relative'
+ container.style.display = 'inline-block'
+
+ const img = document.createElement('img')
+ img.setAttribute('src', node.attrs.src)
+ img.style.maxWidth = '100px'
+ img.style.borderRadius = '3px'
+ img.style.backgroundColor = '#48484d'
+ img.style.margin = '0px 2px'
+
+ container.appendChild(img)
+
+ if (uploadingImages.value.has(node.attrs.src)) {
+ container.classList.add('image-upload-loading')
+ }
+
+ const stopWatch = watch(uploadingImages, () => {
+ if (uploadingImages.value.has(node.attrs.src)) {
+ container.classList.add('image-upload-loading')
+ } else {
+ container.classList.remove('image-upload-loading')
+ }
+ }, { deep: true })
+
+ return {
+ dom: container,
+ destroy() {
+ stopWatch()
+ }
+ }
+ }
+ }
+ }).configure({
inline: true,
allowBase64: true,
}),
@@ -234,6 +205,10 @@ const editor = useEditor({
class: 'mention',
},
suggestion: {
+ allowedPrefixes: null,
+ hideOnClickOutside: true,
+ hideOnKeyDown: true,
+ emptyQueryClass: 'is-empty-query',
items: ({ query }) => {
if (!props.members.length) {
return []
@@ -245,13 +220,21 @@ const editor = useEditor({
list.unshift({ id: 0, nickname: '所有人', avatar: defAvatar, value: '所有人' })
}
- return list.filter(
+ const filteredItems = list.filter(
(item) => item.nickname.toLowerCase().includes(query.toLowerCase())
)
+
+ // 如果没有匹配项,返回空数组以关闭弹窗
+ if (filteredItems.length === 0) {
+ return []
+ }
+
+ return filteredItems
},
render: () => {
let component
let popup
+ let handleClickOutside
return {
onStart: (props) => {
@@ -260,6 +243,18 @@ const editor = useEditor({
popup.classList.add('ql-mention-list-container', 'me-scrollbar', 'me-scrollbar-thumb')
document.body.appendChild(popup)
+ // 添加全局点击事件监听器,点击弹窗外部时关闭弹窗
+ handleClickOutside = (event) => {
+ if (popup && !popup.contains(event.target)) {
+ popup.remove()
+ document.removeEventListener('click', handleClickOutside)
+ }
+ }
+ // 使用setTimeout确保事件不会立即触发
+ setTimeout(() => {
+ document.addEventListener('click', handleClickOutside)
+ }, 100)
+
// 渲染提及列表
props.items.forEach((item, index) => {
const mentionItem = document.createElement('div')
@@ -297,18 +292,28 @@ const editor = useEditor({
onKeyDown: (props) => {
// 处理键盘事件
+ // Escape键关闭弹窗
if (props.event.key === 'Escape') {
popup.remove()
return true
}
+ // 空格键、回车键或其他非导航键也关闭弹窗
+ const navigationKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Tab']
+ if (!navigationKeys.includes(props.event.key) && props.items.length === 0) {
+ popup.remove()
+ return false
+ }
+
return false
},
onExit: () => {
- // 清理
+ // 清理弹窗和事件监听器
if (popup) {
popup.remove()
+ // 移除所有可能的点击事件监听器
+ document.removeEventListener('click', handleClickOutside)
}
},
}
@@ -317,7 +322,6 @@ const editor = useEditor({
}),
Link,
Emoji,
- Quote,
CustomKeyboard,
],
content: '',
@@ -327,6 +331,57 @@ const editor = useEditor({
onUpdate: () => {
onEditorChange()
},
+ editorProps: {
+ handlePaste: (view, event) => {
+ const items = (event.clipboardData || event.originalEvent.clipboardData).items
+ for (const item of items) {
+ if (item.type.indexOf('image') === 0) {
+ event.preventDefault()
+ const file = item.getAsFile()
+ if (!file) continue
+
+ const tempUrl = URL.createObjectURL(file)
+ const { state, dispatch } = view
+ const { tr } = state
+ const node = state.schema.nodes.image.create({ src: tempUrl })
+ dispatch(tr.replaceSelectionWith(node))
+
+ const form = new FormData()
+ form.append('file', file)
+ form.append('source', 'fonchain-chat')
+
+ uploadingImages.value.set(tempUrl, true)
+
+ uploadImg(form)
+ .then(({ code, data, message }) => {
+ if (code === 0 && data.ori_url) {
+ const pos = findImagePos(tempUrl)
+ if (pos !== -1) {
+ const { tr } = view.state
+ view.dispatch(
+ tr.setNodeMarkup(pos, null, { src: data.ori_url })
+ )
+ }
+ } else {
+ window['$message'].error(message || '图片上传失败')
+ removeImage(tempUrl)
+ }
+ })
+ .catch(() => {
+ window['$message'].error('图片上传失败')
+ removeImage(tempUrl)
+ })
+ .finally(() => {
+ uploadingImages.value.delete(tempUrl)
+ URL.revokeObjectURL(tempUrl)
+ })
+
+ return true
+ }
+ }
+ return false
+ }
+ }
})
/**
@@ -334,6 +389,28 @@ const editor = useEditor({
* @param file 文件对象
* @returns Promise,成功时返回图片URL
*/
+function findImagePos(url) {
+ if (!editor.value) return -1
+ let pos = -1
+ editor.value.state.doc.descendants((node, p) => {
+ if (node.type.name === 'image' && node.attrs.src === url) {
+ pos = p
+ return false
+ }
+ return true
+ })
+ return pos
+}
+
+function removeImage(url) {
+ if (!editor.value) return
+ const pos = findImagePos(url)
+ if (pos !== -1) {
+ const { tr } = editor.value.state
+ editor.value.view.dispatch(tr.delete(pos, pos + 1))
+ }
+}
+
function onUploadImage(file) {
return new Promise((resolve) => {
let image = new Image()
@@ -461,7 +538,6 @@ function tiptapToMessage() {
const json = editor.value.getJSON()
const messages = []
- let quoteId = null
let currentTextBuffer = ''
let currentMentions = []
let currentMentionUids = new Set()
@@ -474,10 +550,6 @@ function tiptapToMessage() {
mentions: [...currentMentions],
mentionUids: Array.from(currentMentionUids)
}
- if (quoteId) {
- data.quoteId = quoteId
- quoteId = null
- }
messages.push({ type: 'text', data })
}
currentTextBuffer = ''
@@ -507,22 +579,12 @@ function tiptapToMessage() {
...getImageInfo(node.attrs.src),
url: node.attrs.src
}
- if (quoteId) {
- data.quoteId = quoteId
- quoteId = null
- }
messages.push({ type: 'image', data })
}
})
}
if (json.content) {
- const quoteIndex = json.content.findIndex(node => node.type === 'quote')
- if (quoteIndex > -1) {
- quoteId = json.content[quoteIndex].attrs.id
- json.content.splice(quoteIndex, 1)
- }
-
json.content.forEach(node => {
if (node.type === 'paragraph') {
if (node.content) {
@@ -535,10 +597,6 @@ function tiptapToMessage() {
...getImageInfo(node.attrs.src),
url: node.attrs.src
}
- if (quoteId) {
- data.quoteId = quoteId
- quoteId = null
- }
messages.push({ type: 'image', data })
}
})
@@ -585,11 +643,14 @@ function isEditorEmpty() {
* 根据编辑器内容类型发送不同类型的消息
*/
function onSendMessage() {
- if (!editor.value || isEditorEmpty()) return
+ if (uploadingImages.value.size > 0) {
+ return window['$message'].info('正在上传图片,请稍后再发')
+ }
+ if (!editor.value || (isEditorEmpty() && !quoteData.value)) return
const messages = tiptapToMessage()
- if (messages.length === 0) {
+ if (messages.length === 0 && !quoteData.value) {
return
}
@@ -601,6 +662,13 @@ function onSendMessage() {
canClear = false
return
}
+
+ // 添加引用消息参数
+ if (quoteData.value) {
+ msg.data.quoteId = quoteData.value.id
+ msg.data.quote = { ...quoteData.value }
+ }
+
emit('editor-event', emitCall('text_event', msg.data))
} else if (msg.type === 'image') {
const data = {
@@ -609,12 +677,33 @@ function onSendMessage() {
size: 10000,
url: msg.data.url,
}
+
+ // 添加引用消息参数
+ if (quoteData.value) {
+ data.quoteId = quoteData.value.id
+ data.quote = { ...quoteData.value }
+ }
+
emit('editor-event', emitCall('image_event', data))
}
})
+ // 如果只有引用消息但没有内容,也发送一条空文本消息带引用
+ if (messages.length === 0 && quoteData.value) {
+ const emptyData = {
+ items: [{ type: 1, content: '' }],
+ mentions: [],
+ mentionUids: [],
+ quoteId: quoteData.value.id,
+ quote: { ...quoteData.value }
+ }
+ emit('editor-event', emitCall('text_event', emptyData))
+ }
+
if (canClear) {
editor.value?.commands.clearContent(true)
+ // 清空引用数据
+ quoteData.value = null
}
}
@@ -627,11 +716,12 @@ function onEditorChange() {
const text = tiptapToString()
- if (!isEditorEmpty()) {
+ if (!isEditorEmpty() || quoteData.value) {
// 保存草稿到store
editorDraftStore.items[indexName.value || ''] = JSON.stringify({
text: text,
- content: editor.value.getJSON()
+ content: editor.value.getJSON(),
+ quoteData: quoteData.value
})
} else {
// 编辑器为空时删除对应草稿
@@ -649,6 +739,10 @@ function onEditorChange() {
function loadEditorDraftText() {
if (!editor.value) return
+ // 保存当前引用数据
+ const currentQuoteData = quoteData.value
+ quoteData.value = null
+
// 从缓存中加载编辑器草稿
let draft = editorDraftStore.items[indexName.value || '']
if (draft) {
@@ -658,10 +752,20 @@ function loadEditorDraftText() {
} else if (parsed.text) {
editor.value.commands.setContent(parsed.text)
}
+
+ // 如果草稿中有引用数据,恢复它
+ if (parsed.quoteData) {
+ quoteData.value = parsed.quoteData
+ }
} else {
editor.value.commands.clearContent(true) // 没有草稿则清空编辑器
}
+ // 如果有当前引用数据,优先使用它
+ if (currentQuoteData) {
+ quoteData.value = currentQuoteData
+ }
+
// 设置光标位置到末尾
editor.value.commands.focus('end')
}
@@ -689,27 +793,9 @@ function onSubscribeMention(data) {
*/
function onSubscribeQuote(data) {
if (!editor.value) return
-
- // 检查是否已有引用内容
- const json = editor.value.getJSON()
- if (json.content?.some(node => node.type === 'quote')) {
- return // 已有引用则不再添加
- }
-
- // 在编辑器开头插入引用
- editor.value
- .chain()
- .focus()
- .insertContentAt(0, [
- {
- type: 'quote',
- attrs: data
- },
- {
- type: 'paragraph'
- }
- ])
- .run()
+
+ // 保存引用数据
+ quoteData.value = data
}
/**
@@ -770,6 +856,8 @@ useEventBus([
+
+
-
+
+
+
+
+ {{ quoteData.title || ' ' }}
+
+
+
+
![引用图片]()
+
+
+ {{ quoteData.describe }}
+
+
+
@@ -838,10 +940,64 @@ useEventBus([
@@ -903,6 +1081,25 @@ html[theme-mode='dark'] {
overflow: auto;
padding: 8px;
outline: none;
+
+ .image-upload-loading {
+ position: relative;
+ display: inline-block;
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5) url('data:image/svg+xml;charset=UTF-8,');
+ background-size: 30px 30px;
+ background-position: center center;
+ background-repeat: no-repeat;
+ border-radius: 5px;
+ }
+ }
/* 滚动条样式 */
&::-webkit-scrollbar {
@@ -959,45 +1156,10 @@ html[theme-mode='dark'] {
}
/* 引用卡片样式 */
- .quote-card-wrapper {
+ .quote-card {
margin-bottom: 8px;
}
-
- .quote-card-content {
- display: flex;
- background-color: #f6f6f6;
- flex-direction: column;
- padding: 8px;
- margin-bottom: 5px;
- cursor: pointer;
- user-select: none;
- .quote-card-title {
- height: 22px;
- line-height: 22px;
- font-size: 12px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- display: flex;
- justify-content: space-between;
-
- .quote-card-remove {
- margin-right: 15px;
- font-size: 18px;
- }
- }
-
- .quote-card-meta {
- margin-top: 4px;
- font-size: 12px;
- line-height: 20px;
- color: #999;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
}
/* 提及列表样式 */
From a405a3bd906ea53ab410563229518bd290e99f5e Mon Sep 17 00:00:00 2001
From: Phoenix <64720302+Concur-max@users.noreply.github.com>
Date: Wed, 2 Jul 2025 16:04:41 +0800
Subject: [PATCH 6/9] =?UTF-8?q?fix(editor):=20=E5=88=87=E6=8D=A2=E4=BC=9A?=
=?UTF-8?q?=E8=AF=9D=E6=97=B6=E6=B8=85=E7=A9=BA=E5=BC=95=E7=94=A8=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E8=80=8C=E9=9D=9E=E4=BF=9D=E7=95=99?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修改编辑器行为,在切换会话时主动清空引用数据而不是保留之前的引用。这避免了不同会话间引用数据的混淆问题。
---
src/components/editor/TiptapEditor.vue | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/src/components/editor/TiptapEditor.vue b/src/components/editor/TiptapEditor.vue
index 77a8996..fa7b4a4 100644
--- a/src/components/editor/TiptapEditor.vue
+++ b/src/components/editor/TiptapEditor.vue
@@ -739,8 +739,7 @@ function onEditorChange() {
function loadEditorDraftText() {
if (!editor.value) return
- // 保存当前引用数据
- const currentQuoteData = quoteData.value
+ // 切换会话时清空引用数据,不保存当前引用数据
quoteData.value = null
// 从缓存中加载编辑器草稿
@@ -761,11 +760,6 @@ function loadEditorDraftText() {
editor.value.commands.clearContent(true) // 没有草稿则清空编辑器
}
- // 如果有当前引用数据,优先使用它
- if (currentQuoteData) {
- quoteData.value = currentQuoteData
- }
-
// 设置光标位置到末尾
editor.value.commands.focus('end')
}
From 0b634e8cdd291048e30c5c4919f165525a37860a Mon Sep 17 00:00:00 2001
From: Phoenix <64720302+Concur-max@users.noreply.github.com>
Date: Wed, 2 Jul 2025 16:22:42 +0800
Subject: [PATCH 7/9] =?UTF-8?q?fix(=E7=BC=96=E8=BE=91=E5=99=A8):=20?=
=?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B8=85=E7=A9=BA=E5=BC=95=E7=94=A8=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E6=97=B6=E6=9C=AA=E6=9B=B4=E6=96=B0=E8=8D=89=E7=A8=BF?=
=?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
在清空引用数据时调用clearQuoteData方法,确保同时更新草稿状态。修改了引用卡片关闭按钮的点击事件处理逻辑,使用新方法替代直接赋值null。
---
src/components/editor/TiptapEditor.vue | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/components/editor/TiptapEditor.vue b/src/components/editor/TiptapEditor.vue
index fa7b4a4..7ecedc9 100644
--- a/src/components/editor/TiptapEditor.vue
+++ b/src/components/editor/TiptapEditor.vue
@@ -704,6 +704,8 @@ function onSendMessage() {
editor.value?.commands.clearContent(true)
// 清空引用数据
quoteData.value = null
+ // 更新草稿
+ onEditorChange()
}
}
@@ -790,6 +792,17 @@ function onSubscribeQuote(data) {
// 保存引用数据
quoteData.value = data
+ // 更新草稿
+ onEditorChange()
+}
+
+/**
+ * 清空引用数据并更新草稿
+ */
+function clearQuoteData() {
+ quoteData.value = null
+ // 更新草稿
+ onEditorChange()
}
/**
@@ -894,7 +907,7 @@ useEventBus([
{{ quoteData.title || ' ' }}
-
+
![引用图片]()
From c64a562913a62c8793665032d4b08af6deedf350 Mon Sep 17 00:00:00 2001
From: Phoenix <64720302+Concur-max@users.noreply.github.com>
Date: Thu, 3 Jul 2025 10:33:29 +0800
Subject: [PATCH 8/9] =?UTF-8?q?refactor(db):=20=E9=87=8D=E6=9E=84=E4=BC=9A?=
=?UTF-8?q?=E8=AF=9D=E8=A1=A8=E4=B8=BB=E9=94=AE=E7=BB=93=E6=9E=84=E5=B9=B6?=
=?UTF-8?q?=E6=B8=85=E7=90=86=E6=97=A7=E6=95=B0=E6=8D=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将会话表主键从自增id改为index_name
- 添加数据库版本升级逻辑清理旧数据
- 更新所有相关操作方法使用新主键
- 添加详细的版本变更注释
---
src/store/modules/talk.ts | 2 +-
src/utils/auth.js | 2 +-
src/utils/db.js | 69 ++++++++++++++++++++++++++++++---------
3 files changed, 55 insertions(+), 18 deletions(-)
diff --git a/src/store/modules/talk.ts b/src/store/modules/talk.ts
index e79abd4..7f9350c 100644
--- a/src/store/modules/talk.ts
+++ b/src/store/modules/talk.ts
@@ -181,7 +181,7 @@ export const useTalkStore = defineStore('talk', {
// 更新状态和本地数据库
this.items = serverItems
-
+ console.log('serverItems',serverItems)
// 将最新的会话列表保存到本地数据库
for (const item of serverItems) {
await addOrUpdateConversation(item)
diff --git a/src/utils/auth.js b/src/utils/auth.js
index ea2b8c1..c218336 100644
--- a/src/utils/auth.js
+++ b/src/utils/auth.js
@@ -18,7 +18,7 @@ export function isLoggedIn() {
*/
export function getAccessToken() {
// return storage.get(AccessToken) || ''
- return JSON.parse(localStorage.getItem('token'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d22f227a3ad4383a2ddef0a2f43b855f869560968fd2dae0412498273e591b78554b2373a17017cdaae7c9ec427325b7d078d54cb00b001c9894c8e1f990747c8db3b62b17eb8ed39e2b2c2b6d63ce26756'
+ return JSON.parse(localStorage.getItem('token'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d22eec7a138bb20774ef183e109945229d43e1f63fb01cdee46f5f663037f4ed946a0c04441b1f642c945d218180e84e91d272dc621be157602785ef226dd21b9b6c92c292bc73be90fad0320bad0812e11'
}
/**
diff --git a/src/utils/db.js b/src/utils/db.js
index 8bec6a8..c4c56d1 100644
--- a/src/utils/db.js
+++ b/src/utils/db.js
@@ -1,11 +1,30 @@
import Dexie from 'dexie';
+/**
+ * 聊天历史数据库
+ * 版本5-6: 修复会话表主键问题
+ * - 版本5: 删除旧的会话表结构
+ * - 版本6: 使用index_name作为主键重新创建会话表
+ *
+ * 注意: Dexie不支持直接更改主键,必须通过删除并重建表的方式实现
+ */
export const db = new Dexie('chatHistory');
// 定义数据库表结构和索引
-// 版本3:优化了索引,提高了查询和排序性能
-db.version(4).stores({
+// 版本6:修复主键更改问题
+// 版本5:删除旧的会话表
+db.version(5).stores({
+ conversations: null
+}).upgrade(function(trans) {
+ // 确保物理删除表
+ if (trans.idbtrans.db.objectStoreNames.contains('conversations')) {
+ trans.idbtrans.db.deleteObjectStore('conversations');
+ }
+});
+
+// 版本6:使用新的主键结构重新创建会话表
+db.version(6).stores({
/**
* 聊天记录表
* - msg_id: 消息唯一ID (主键)
@@ -18,12 +37,20 @@ db.version(4).stores({
/**
* 会话表
- * - ++id: 自增主键
- * - &index_name: 唯一索引 (talk_type + '_' + receiver_id)
+ * - index_name: 主键 (talk_type + '_' + receiver_id)
* - updated_at: 索引,用于排序
* - is_top: 索引,用于置顶排序
*/
- conversations: 'id, &index_name, talk_type, receiver_id, updated_at, unread_num, is_top',
+ conversations: 'index_name, talk_type, receiver_id, updated_at, unread_num, is_top',
+});
+
+// 清理旧版本数据
+db.on('versionchange', function(event) {
+ if (event.oldVersion < 6 && event.newVersion >= 6) {
+ console.log('数据库版本升级到6,清理旧数据');
+ db.conversations.clear();
+ console.log('会话表数据已清理,主键结构已更新');
+ }
});
db.on('ready', () => {
@@ -219,14 +246,24 @@ export async function deleteMessage(msgId) {
/**
* 添加或更新会话
* @param {object} conversation - 会话对象
- * @returns {Promise
} 会话ID
+ * @returns {Promise} 会话索引名称
*/
export async function addOrUpdateConversation(conversation) {
try {
- // put 方法会根据唯一索引 index_name 自动判断是添加还是更新
+ // 确保 index_name 存在,这是会话表的主键
+ if (!conversation.index_name && conversation.talk_type && conversation.receiver_id) {
+ conversation.index_name = `${conversation.talk_type}_${conversation.receiver_id}`;
+ }
+
+ if (!conversation.index_name) {
+ throw new Error('无法添加会话:缺少必要的index_name或无法生成');
+ }
+
+ // 使用 put 方法,如果主键已存在则更新,否则添加
return await db.conversations.put(conversation);
} catch (error) {
console.error('添加或更新会话失败:', error);
+ console.error('错误详情:', error.message, error.stack);
throw error;
}
}
@@ -274,7 +311,7 @@ export async function getConversations(includeEmpty = false) {
export async function getConversation(talkType, receiverId) {
try {
const indexName = `${talkType}_${receiverId}`;
- return await db.conversations.get({ index_name: indexName });
+ return await db.conversations.get(indexName);
} catch (error) {
console.error('获取会话失败:', error);
throw error;
@@ -291,11 +328,11 @@ export async function getConversation(talkType, receiverId) {
export async function updateConversationUnreadNum(talkType, receiverId, unreadNum = null) {
try {
const indexName = `${talkType}_${receiverId}`;
- const conversation = await db.conversations.get({ index_name: indexName });
+ const conversation = await db.conversations.get(indexName);
if (conversation) {
const newUnreadNum = unreadNum === null ? (conversation.unread_num || 0) + 1 : unreadNum;
- return await db.conversations.update(conversation.id, { unread_num: newUnreadNum });
+ return await db.conversations.update(indexName, { unread_num: newUnreadNum });
}
return 0;
} catch (error) {
@@ -316,18 +353,18 @@ export function clearConversationUnreadNum(talkType, receiverId) {
/**
* 删除会话及其相关的消息
- * @param {number} conversationId - 会话ID
+ * @param {string} indexName - 会话索引名称
* @param {boolean} [deleteMessages=false] - 是否同时删除相关的消息记录
* @returns {Promise}
*/
-export async function deleteConversation(conversationId, deleteMessages = false) {
+export async function deleteConversation(indexName, deleteMessages = false) {
try {
await db.transaction('rw', db.conversations, db.messages, async () => {
- const conversation = await db.conversations.get(conversationId);
+ const conversation = await db.conversations.get(indexName);
if (!conversation) return;
// 删除会话
- await db.conversations.delete(conversationId);
+ await db.conversations.delete(indexName);
// 如果需要,删除关联的消息
if (deleteMessages) {
@@ -352,7 +389,7 @@ export async function updateConversationLastMessage(message) {
const targetReceiverId = talk_type === TalkType.PRIVATE ? (user_id === receiver_id ? user_id : receiver_id) : receiver_id;
const indexName = `${talk_type}_${targetReceiverId}`;
- const conversation = await db.conversations.get({ index_name: indexName });
+ const conversation = await db.conversations.get(indexName);
if (!conversation) return 0;
let msgText = '';
@@ -367,7 +404,7 @@ export async function updateConversationLastMessage(message) {
default: msgText = '[未知消息]';
}
- return await db.conversations.update(conversation.id, {
+ return await db.conversations.update(indexName, {
msg_text: msgText,
content: message.content || '',
updated_at: message.created_at,
From a0b28b19efd84f9e0c44c962fa1c27b343cbe82b Mon Sep 17 00:00:00 2001
From: Phoenix <64720302+Concur-max@users.noreply.github.com>
Date: Thu, 3 Jul 2025 10:47:10 +0800
Subject: [PATCH 9/9] =?UTF-8?q?fix(editor):=20=E4=BF=AE=E5=A4=8D=E7=BC=96?=
=?UTF-8?q?=E8=BE=91=E5=99=A8=E9=AB=98=E5=BA=A6=E9=97=AE=E9=A2=98=E5=92=8C?=
=?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
调整Tiptap编辑器高度为100%以正确填充容器
移除消息面板中已读回执的调试日志输出
---
src/components/editor/TiptapEditor.vue | 4 +++-
src/views/message/inner/panel/PanelContent.vue | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/components/editor/TiptapEditor.vue b/src/components/editor/TiptapEditor.vue
index 7ecedc9..12e0ffd 100644
--- a/src/components/editor/TiptapEditor.vue
+++ b/src/components/editor/TiptapEditor.vue
@@ -1088,7 +1088,9 @@ html[theme-mode='dark'] {
overflow: auto;
padding: 8px;
outline: none;
-
+ .tiptap.ProseMirror{
+ height: 100%;
+ }
.image-upload-loading {
position: relative;
display: inline-block;
diff --git a/src/views/message/inner/panel/PanelContent.vue b/src/views/message/inner/panel/PanelContent.vue
index 3fc8b63..e99c05d 100644
--- a/src/views/message/inner/panel/PanelContent.vue
+++ b/src/views/message/inner/panel/PanelContent.vue
@@ -511,7 +511,7 @@ const checkVisibleElements = () => {
prev.talk_type === doReadItem.talk_type && prev.receiver_id === doReadItem.receiver_id
)
if (!prevItem || !doReadItem.msg_ids.every((id) => prevItem.msg_ids.includes(id))) {
- console.error('====发送了新版已读回执=====', doReadItem)
+ // console.error('====发送了新版已读回执=====', doReadItem)
ws.emit('im.message.new.read', doReadItem)
}
})