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 1/4] =?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 @@ + + + + + + + + + + + + + + + 表情符号 + + + + + + + + + + {{ nav.title }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 2/4] =?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 3/4] =?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 4/4] =?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([ + + @@ -807,7 +895,21 @@ 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; - } - } } /* 提及列表样式 */
表情符号
{{ nav.title }}