From f093ff4662b4d48c31fd7049751f6c7effc8e3e0 Mon Sep 17 00:00:00 2001 From: zhuzhengjian Date: Fri, 21 Nov 2025 17:17:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=86=E9=A1=B5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8C=81=E7=BB=AD=E5=88=86=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + pnpm-lock.yaml | 51 +++++ src/components/Editor.vue | 62 +++++ src/components/SlashCommandList.vue | 29 ++- src/components/TableControls.vue | 342 ++++++++++++++++++++++++++++ 5 files changed, 484 insertions(+), 2 deletions(-) create mode 100644 src/components/TableControls.vue diff --git a/package.json b/package.json index d726474..cdd0421 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@tiptap/extension-table-cell": "^3.11.0", "@tiptap/extension-table-header": "^3.11.0", "@tiptap/extension-table-row": "^3.11.0", + "@tiptap/extension-text-align": "^3.11.0", "@tiptap/pm": "^3.11.0", "@tiptap/starter-kit": "^3.11.0", "@tiptap/suggestion": "^3.11.0", @@ -30,6 +31,7 @@ "lucide-vue-next": "^0.554.0", "monaco-editor": "^0.55.1", "shiki": "^3.15.0", + "tiptap-markdown": "^0.9.0", "vue": "^3.5.24" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f064845..bec4eaa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: '@tiptap/extension-table-row': specifier: ^3.11.0 version: 3.11.0(@tiptap/extension-table@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)) + '@tiptap/extension-text-align': + specifier: ^3.11.0 + version: 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0)) '@tiptap/pm': specifier: ^3.11.0 version: 3.11.0 @@ -74,6 +77,9 @@ importers: shiki: specifier: ^3.15.0 version: 3.15.0 + tiptap-markdown: + specifier: ^0.9.0 + version: 0.9.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0)) vue: specifier: ^3.5.24 version: 3.5.24(typescript@5.9.3) @@ -443,6 +449,11 @@ packages: '@tiptap/core': ^3.11.0 '@tiptap/pm': ^3.11.0 + '@tiptap/extension-text-align@3.11.0': + resolution: {integrity: sha512-Hmcnc10vP2TecVYEuIKpx9HPWXQ263Vaqq8BoplXIt7XQ+pCZFS/TF6F8zcClb8gMIhICI89GzF4TEvxnHlxFw==} + peerDependencies: + '@tiptap/core': ^3.11.0 + '@tiptap/extension-text@3.11.0': resolution: {integrity: sha512-ELAYm2BuChzZOqDG9B0k3W6zqM4pwNvXkam28KgHGiT2y7Ni68Rb+NXp16uVR+5zR6hkqnQ/BmJSKzAW59MXpA==} peerDependencies: @@ -495,15 +506,24 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/linkify-it@3.0.5': + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + '@types/linkify-it@5.0.0': resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + '@types/markdown-it@13.0.9': + resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} + '@types/markdown-it@14.1.2': resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@1.0.5': + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} @@ -754,6 +774,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-it-task-lists@2.1.1: + resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} + markdown-it@14.1.0: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true @@ -957,6 +980,11 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tiptap-markdown@0.9.0: + resolution: {integrity: sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ==} + peerDependencies: + '@tiptap/core': ^3.0.1 + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -1324,6 +1352,10 @@ snapshots: '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0) '@tiptap/pm': 3.11.0 + '@tiptap/extension-text-align@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))': + dependencies: + '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0) + '@tiptap/extension-text@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))': dependencies: '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0) @@ -1418,8 +1450,15 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/linkify-it@3.0.5': {} + '@types/linkify-it@5.0.0': {} + '@types/markdown-it@13.0.9': + dependencies: + '@types/linkify-it': 3.0.5 + '@types/mdurl': 1.0.5 + '@types/markdown-it@14.1.2': dependencies: '@types/linkify-it': 5.0.0 @@ -1429,6 +1468,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/mdurl@1.0.5': {} + '@types/mdurl@2.0.0': {} '@types/node@24.10.1': @@ -1669,6 +1710,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-it-task-lists@2.1.1: {} + markdown-it@14.1.0: dependencies: argparse: 2.0.1 @@ -1919,6 +1962,14 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tiptap-markdown@0.9.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0)): + dependencies: + '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0) + '@types/markdown-it': 13.0.9 + markdown-it: 14.1.0 + markdown-it-task-lists: 2.1.1 + prosemirror-markdown: 1.13.2 + trim-lines@3.0.1: {} tslib@2.8.1: diff --git a/src/components/Editor.vue b/src/components/Editor.vue index d6c32a1..dfa1d15 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -1,5 +1,9 @@ @@ -54,11 +60,13 @@ import { BubbleMenu, FloatingMenu } from '@tiptap/vue-3/menus' import StarterKit from '@tiptap/starter-kit' import Placeholder from '@tiptap/extension-placeholder' import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight' +import TextAlign from '@tiptap/extension-text-align' import { Table } from '@tiptap/extension-table' import { TableCell } from '@tiptap/extension-table-cell' import { TableHeader } from '@tiptap/extension-table-header' import { TableRow } from '@tiptap/extension-table-row' import { DragHandle } from '@tiptap/extension-drag-handle-vue-3' +import { Markdown } from 'tiptap-markdown' import SlashCommand from '../extensions/SlashCommand' import { computePosition, flip, shift, offset, autoUpdate } from '@floating-ui/dom' import { @@ -78,6 +86,7 @@ import { } from 'lucide-vue-next' import SlashCommandList from './SlashCommandList.vue' import CodeBlockComponent from './CodeBlockComponent.vue' +import TableControls from './TableControls.vue' import { all, createLowlight } from 'lowlight' const props = defineProps({ @@ -262,11 +271,64 @@ const editor = useEditor({ render: renderSuggestion, }, }), + TextAlign.configure({ + types: ['heading', 'paragraph', 'tableCell', 'tableHeader'], + }), + Markdown, ], content: '

Hello World!

', }) + +const exportHTML = () => { + const html = editor.value?.getHTML() + if (html) { + const blob = new Blob([html], { type: 'text/html' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = 'document.html' + a.click() + URL.revokeObjectURL(url) + } +} + +const exportMarkdown = () => { + const markdown = (editor.value?.storage as any).markdown.getMarkdown() + if (markdown) { + const blob = new Blob([markdown], { type: 'text/markdown' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = 'document.md' + a.click() + URL.revokeObjectURL(url) + } +} diff --git a/src/components/SlashCommandList.vue b/src/components/SlashCommandList.vue index 022b9cd..b288cd1 100644 --- a/src/components/SlashCommandList.vue +++ b/src/components/SlashCommandList.vue @@ -1,5 +1,5 @@