From a8704e199f88e089c46b1605b9e1b0f53ab4b75e Mon Sep 17 00:00:00 2001 From: xfy Date: Fri, 5 Jun 2026 10:20:47 +0800 Subject: [PATCH] feat(editor): add slash command, table, task list, image and link extensions - Add @tiptap/suggestion-based slash command menu with 12 commands - Add TableKit for table support (3x3 with header) - Add Image extension with base64 support - Add Link extension with autolink and paste detection - Add TaskList/TaskItem extensions for checklists - Upgrade all tiptap packages from 3.23.6 to 3.25.0 - Add CSS styles for all new features including dark theme --- libs/tiptap-editor/package-lock.json | 341 ++++++++++++++---------- libs/tiptap-editor/package.json | 13 +- libs/tiptap-editor/src/index.ts | 16 ++ libs/tiptap-editor/src/slash-command.ts | 272 +++++++++++++++++++ libs/tiptap-editor/src/style.css | 262 +++++++++++++++--- public/tiptap/editor.css | 2 +- 6 files changed, 729 insertions(+), 177 deletions(-) create mode 100644 libs/tiptap-editor/src/slash-command.ts diff --git a/libs/tiptap-editor/package-lock.json b/libs/tiptap-editor/package-lock.json index 9e9820b..fb85efc 100644 --- a/libs/tiptap-editor/package-lock.json +++ b/libs/tiptap-editor/package-lock.json @@ -8,10 +8,15 @@ "name": "@yggdrasil/tiptap-editor", "version": "1.0.0", "dependencies": { - "@tiptap/core": "^3.0.0", - "@tiptap/markdown": "^3.0.0", - "@tiptap/pm": "^3.0.0", - "@tiptap/starter-kit": "^3.0.0" + "@tiptap/core": "^3.25.0", + "@tiptap/extension-image": "^3.25.0", + "@tiptap/extension-link": "^3.25.0", + "@tiptap/extension-list": "^3.25.0", + "@tiptap/extension-table": "^3.25.0", + "@tiptap/markdown": "^3.25.0", + "@tiptap/pm": "^3.25.0", + "@tiptap/starter-kit": "^3.25.0", + "@tiptap/suggestion": "^3.25.0" }, "devDependencies": { "vite": "^5.4.20" @@ -759,180 +764,193 @@ ] }, "node_modules/@tiptap/core": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/core/-/core-3.23.6.tgz", - "integrity": "sha512-MRB3pHz4Oxqmcawh0cQ5iOGdY5xtNYp/1CoK7hdTLzw5K0C6/gTC2VvanB1R4INaB6EpBkxG/GiWkVirDRnuXw==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/core/-/core-3.25.0.tgz", + "integrity": "sha512-I9edH6vUXgbjUl5GPICYYYQeql8hC77VZnHLvWg8wc7FwaOw242Uy4Y89c/eX7LGmKwVxz28JFvAsZ8tIdDVvg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "3.23.6" + "@tiptap/pm": "3.25.0" } }, "node_modules/@tiptap/extension-blockquote": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-blockquote/-/extension-blockquote-3.23.6.tgz", - "integrity": "sha512-2RmnqNqTltZ2k1F7IfjoDNs935Uq4rRDR7d98mqkg3OlDktcQIyBpv0t9dTay6H5bkQeZUuS8ogK2S1E8Edjug==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-blockquote/-/extension-blockquote-3.25.0.tgz", + "integrity": "sha512-nSWhYtAKVFAZluRTew+BZUMHo5+87uQqTBOnbyy9ZFBp3gjHjCgGqhboJg5ksMHLCEz1XVoHnS5iXcu9d6Bm6Q==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extension-bold": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-bold/-/extension-bold-3.23.6.tgz", - "integrity": "sha512-1LMhjnytdbbhWHSoOwnLxZAOQZWPkKyXVCNmaIk0Mhi4tLPUXptG4qKS5sVYTCveE5H6IBPFrbgBFi5dMI6krA==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-bold/-/extension-bold-3.25.0.tgz", + "integrity": "sha512-owygVm6XMtk8VVclm2CCCz3Q1HfNpkjeoRTIbeM5r/R1cDrPQAVOuAd3w+mdXlC3iDsvCkfYzSTSphZcDpwThQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extension-bullet-list": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.23.6.tgz", - "integrity": "sha512-RMRgfXZykr/13X8UBOwvpgysVOo9KchwqMoEbvqQSj4YFfU56iIn59C8sbxiQ1sKfeltUf0wH4fPc0I4iwKqAA==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.25.0.tgz", + "integrity": "sha512-v0+0kvg0CddW4bz05YVssnMhfe+4x32Tg9qNzYMYK4jGtSm5GDLYG7JaOqAUwiXj5jhKmoOTfXzV6cB5Tk4OEA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "3.23.6" + "@tiptap/extension-list": "3.25.0" } }, "node_modules/@tiptap/extension-code": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-code/-/extension-code-3.23.6.tgz", - "integrity": "sha512-KG8KXFYyLrtYvT7AZ1WGV61ofx8pDe5g9pH658MERxqQGii+Pyfc6xkz04l7XeBts/7+571UQp/0O7i/z560TA==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-code/-/extension-code-3.25.0.tgz", + "integrity": "sha512-1Lcwwny7JwQ6m2wEqytKWmSfQzV0ONhZqUmMaAAAFvDCCG7dRPOVKT+3s0UqFlGePP1xbYl0Yy0YOVv3M6sedg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extension-code-block": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-code-block/-/extension-code-block-3.23.6.tgz", - "integrity": "sha512-4kccgcn5yHThxrzsIhJny3EwfEZYIk+BjUCL4uIuzOyWvExtGhZ6JMHVCZeMhI8D1/bX1LNkkAKN5DXPzH4lXQ==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-code-block/-/extension-code-block-3.25.0.tgz", + "integrity": "sha512-bMKhg+Qcve1O3L5k6dzNCbCI/QsWPK1ez+1k9CQEd5rO0mwCpqLGb5tyFztI6umdFr5dulI3FZVt8IOtUptuxQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6", - "@tiptap/pm": "3.23.6" + "@tiptap/core": "3.25.0", + "@tiptap/pm": "3.25.0" } }, "node_modules/@tiptap/extension-document": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-document/-/extension-document-3.23.6.tgz", - "integrity": "sha512-XDAIgG9KcKumFM9KJWUEUhXPbFIhhl47bfy5GknareWTRKke85rcoj/oxKKO9ihLZr8JfpbXjqnS4SCm5yhYPw==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-document/-/extension-document-3.25.0.tgz", + "integrity": "sha512-YEENTItTHdOiIAemTDej2HsbMvq4IlrgQ7obR89Kyaxs2oE4gYw0GPA3gjHfuJnv2VHMQqFn7K37nlyuiABhHw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extension-dropcursor": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.23.6.tgz", - "integrity": "sha512-+XWEoRKf3lXxi7Le1aOM2xU1XHwxICGpXjT3m4QaYqUgIpsq8gQEuso6kVg8DnTD7biKQs6+oIQ0o2b/gTW9WA==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.25.0.tgz", + "integrity": "sha512-4SyWreaR82Gx1vMp5fYTM+acijNNWXQyrx7yKQPFSjh1I9cPNz3wvQEY6gEpBQ6WDwS/WdUIZq9nw99JQx7XRQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extensions": "3.23.6" + "@tiptap/extensions": "3.25.0" } }, "node_modules/@tiptap/extension-gapcursor": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.23.6.tgz", - "integrity": "sha512-wbKmxXsszxWacEkrHucRpSQbiKjz4fmOebD6OVyL9AcrmlbxNk8vcM3iyh/8cVeRy09XY+morM165t/u7/z4IQ==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.25.0.tgz", + "integrity": "sha512-XLXfYLtP744b88qLEWcUUAMB0yD3TFGUtfiFh5eYw3ybOW/BA0f6SIJQWk6l0Uk8TZ1x/YQURWNo07/csJcwew==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extensions": "3.23.6" + "@tiptap/extensions": "3.25.0" } }, "node_modules/@tiptap/extension-hard-break": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-hard-break/-/extension-hard-break-3.23.6.tgz", - "integrity": "sha512-KeUm+tkUfIVSX9QM9XOIhaay0Fn36sLKUo5NVYjN3uJaxFvaZXZmTlxdO85OTdgF2P5sqh9LomrIgliaFRGk4w==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-hard-break/-/extension-hard-break-3.25.0.tgz", + "integrity": "sha512-86JdgqwBUSPhLH5l5TaOA1JbdaE1nCEv/INdPykVCC0Tlf1sdoF356rmFNLo8cLxmDLp9bTVo85EZx7HWl+d+w==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extension-heading": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-heading/-/extension-heading-3.23.6.tgz", - "integrity": "sha512-A/0jPhxnUh9THSZymlu0OGPZe1wdFdwHAXnRCmqvYUCwJjrG7LCC/ahzmcj1tcNzI9hgHyuYPSfev8RXYrNu/w==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-heading/-/extension-heading-3.25.0.tgz", + "integrity": "sha512-3SJGZgV3cNQiUi98dWQQ3SFQAKaZg+O8PTdQmA4XC4JJn3NgDpBHiRz+bSE4NYjaRXk8DOq3+zxgGGiaGsC1Ww==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extension-horizontal-rule": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.23.6.tgz", - "integrity": "sha512-hEUlz4H+I64r+TH6LCuNCRgO7JTHncXGmx9+WbU69EOfY8O0ZurcgeJc8HeiAKL+r9YuC1e5YHfFxgCaaC0jlg==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.25.0.tgz", + "integrity": "sha512-Ku1PQxiBoprEwf7O2uzJSYvfpkQ26UhZ4tptXqCUdsG9IXYn/Gg9qAtJrm8UFnPwsxm0CrkMsAlAG3JmBrtXKQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6", - "@tiptap/pm": "3.23.6" + "@tiptap/core": "3.25.0", + "@tiptap/pm": "3.25.0" + } + }, + "node_modules/@tiptap/extension-image": { + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-image/-/extension-image-3.25.0.tgz", + "integrity": "sha512-LO1W36UovZzs7CqqHo1Fo3/nQDz9UJaV26D+YolqnbMBC2UDxozBPuxxvwkfl7965E3Q/gM8HjdDVM2DcH0VoQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extension-italic": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-italic/-/extension-italic-3.23.6.tgz", - "integrity": "sha512-wol5KdwCPAvpiYhH9PLlvO8ZnJHwZtIboVevrfOGgBcKlXRA3dedR4OAMXHnUtkkzu9KtliLg1+TYzEx4JZG9Q==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-italic/-/extension-italic-3.25.0.tgz", + "integrity": "sha512-eZw+q8mtap8n0B5LtvPSgpyqkSIL7FzT7syD5ut++29FoXNl3fhJO6ct0hspWKFB4ihbvo3NG2gIwHi2ESXQow==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extension-link": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-link/-/extension-link-3.23.6.tgz", - "integrity": "sha512-KNZz7z7P2/qbQsx5bPAbSPjrKDg1VHsedGlLHJCr8U2VRD5VgmDLkMpkouP1CsDg15qgyUKv/nDib5KgPpLNWA==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-link/-/extension-link-3.25.0.tgz", + "integrity": "sha512-DauqQS55xZACzPb0+KxXDiDw1GVDszltMUikHSLZSCp1+EjPSVt86X8CxJNc83rC/ZrqJMM/iUK74DHRUg2XSA==", "license": "MIT", "dependencies": { "linkifyjs": "^4.3.3" @@ -942,133 +960,147 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6", - "@tiptap/pm": "3.23.6" + "@tiptap/core": "3.25.0", + "@tiptap/pm": "3.25.0" } }, "node_modules/@tiptap/extension-list": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-list/-/extension-list-3.23.6.tgz", - "integrity": "sha512-z6vj9+Qht2sjdQkyyHcUpsC/yCIZqTrQiyHDhs/HGKrfvoANyAZGpqdNeKf1wSyjIso+27tQuIH5NDfk8ygyNw==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-list/-/extension-list-3.25.0.tgz", + "integrity": "sha512-bYw4o2YiTdj/tdgktgbMRUfqAJgsnRkwUQTTKElycPdIwlNNs6EQiXku+E2ACftLaFxd3Ek+P50H0AQ5fA/hPw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6", - "@tiptap/pm": "3.23.6" + "@tiptap/core": "3.25.0", + "@tiptap/pm": "3.25.0" } }, "node_modules/@tiptap/extension-list-item": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-list-item/-/extension-list-item-3.23.6.tgz", - "integrity": "sha512-3zzyhdkUWcHVpXuvy6KiIwjh29rbH6gEDEqPQqHLrl1XGnO9pnShC7pSHctlCDjmcx3O4n9cd4QMtVBlUerbiA==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-list-item/-/extension-list-item-3.25.0.tgz", + "integrity": "sha512-RfxDdLXUggC4tKB9V8Vhfxqjn4ZFbL2suFpl3ct0RY7ynrv9tE66ukYQ2SPg6rAYZK+WxVND0VSeLFB5QclO2A==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "3.23.6" + "@tiptap/extension-list": "3.25.0" } }, "node_modules/@tiptap/extension-list-keymap": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.23.6.tgz", - "integrity": "sha512-x8bPcLViGzg/RAmQM/XtmfqIwQ/Pv9Q8mkd+OgfUiTqjeJqKwVQmiqbLFNa7zw81+H61M+HDU+qGAaQ3vRIMjw==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.25.0.tgz", + "integrity": "sha512-8JOWSQc4mpXNmQWn52THIEpcGdVgBz51J/pz/KcbJBMDZIPvB7nDwFsLkkURrcWDX0DO7G9uepjvAEb8LfBFXg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "3.23.6" + "@tiptap/extension-list": "3.25.0" } }, "node_modules/@tiptap/extension-ordered-list": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.23.6.tgz", - "integrity": "sha512-1m/wWB/ZtXcmG2vNdiUkCqsOgqv5vBjCv/mVaHhF9OvV+zQS8YDjoWE7zEuT/GgELdT77Xq8lHrn4nCDudB3/A==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.25.0.tgz", + "integrity": "sha512-3qs1Q7HgJWlgI0VDXGMiTKTOQdNKN6omAgaq5i+jITCbKn+OKC95E9tbkTq9fPWPgH0svJRUfvvACRem4rhJew==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "3.23.6" + "@tiptap/extension-list": "3.25.0" } }, "node_modules/@tiptap/extension-paragraph": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-paragraph/-/extension-paragraph-3.23.6.tgz", - "integrity": "sha512-+7m58LUSncodjrIyXks4RZ3tLNYrvgT77wRR4l3HnM5OABY3GDsDTqi7c1t1yI29NVOSk/DUacqy6UwYAj1DGg==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-paragraph/-/extension-paragraph-3.25.0.tgz", + "integrity": "sha512-yETzkQFjcRA7JeaAw927qaT5xTweAMr1rznN5fRxJdHdURPjvm+8gz76W/8DuloN4EF/fzAjpVBXZwwcJ+61yg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extension-strike": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-strike/-/extension-strike-3.23.6.tgz", - "integrity": "sha512-oF7FEZ37f15aCe5kPgzGDYf/m+hr7VdQ/Ko/Hds/UM9pX7AG1fdtmRrl6wqkRqDM/incZaC/AQR2/Dpo2VCNGQ==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-strike/-/extension-strike-3.25.0.tgz", + "integrity": "sha512-rtM9tkqH8XWay7TplUcXPjlBiNg/dbEOuaCvZGvNxTw8xbH+cmEGPxojWVW6oVMsQodBlUoNveATE2yzhiUB1A==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" + } + }, + "node_modules/@tiptap/extension-table": { + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-table/-/extension-table-3.25.0.tgz", + "integrity": "sha512-at1XYnO0b3OyZjx7ZaIKYRvkjTjHtI/xb+LstN51TrHInf+cc8b10EGWHbQztBPaEmg9t3JC/tNYjzCYy+874g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.25.0", + "@tiptap/pm": "3.25.0" } }, "node_modules/@tiptap/extension-text": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-text/-/extension-text-3.23.6.tgz", - "integrity": "sha512-ipoC2TkIAIOTiF5ByiGgvQB1DqDyfP90wrUB3mohBcgvp7lQnwHszCDGv8dNnmcUek8uXV/uoLu2VXeVQlxjPA==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-text/-/extension-text-3.25.0.tgz", + "integrity": "sha512-qXAYiIIOX7F6wVftN7FeHTAg9lDLzgqrscrT4BJxTL3Vk38EP1R3w1sDDfSCTQ53ui8SzoaKe0iyzkTa6V/1LQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extension-underline": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extension-underline/-/extension-underline-3.23.6.tgz", - "integrity": "sha512-P55wGIZGYTVH92Fq0cgI4/O9AhLCaJC3hhxg15RSERP5/YegM9eJHDK/GQ1EE/DvYA+xpYGOV6agKwAUqfA/Iw==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-underline/-/extension-underline-3.25.0.tgz", + "integrity": "sha512-GTCjXnOhjQ8ipiOrdskMdBqQ8nUnczFWWNJ5IoCkMcEDWviOS14Mr2n6zewjlKjtPoRTzwOpFDQUevSK1SHpJg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6" + "@tiptap/core": "3.25.0" } }, "node_modules/@tiptap/extensions": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/extensions/-/extensions-3.23.6.tgz", - "integrity": "sha512-X09/Db1teB+ifXzDGVVFmOeQRx7wTAayE9/280spxpsHkHZvJ5bHRvWIzUzviMIjbBz+NPDIKYPK7gMfh9iaig==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/extensions/-/extensions-3.25.0.tgz", + "integrity": "sha512-aRXZwOPLdIRey28uctNT/Nbh3EaiNYnKt5qBhBbxs5aTtwoExzYAEtR7D8KjpUVBJAZNeLwFxvD2Ub+F94uAAw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6", - "@tiptap/pm": "3.23.6" + "@tiptap/core": "3.25.0", + "@tiptap/pm": "3.25.0" } }, "node_modules/@tiptap/markdown": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/markdown/-/markdown-3.23.6.tgz", - "integrity": "sha512-11RS+WswVAtAJMn1CaXN1YblMdLn69I6TeFVoHwJXbprV8M45tjyID9uV0WoGYPnpjhfepPhaG7kivgAu5XukQ==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/markdown/-/markdown-3.25.0.tgz", + "integrity": "sha512-7LG/R3bW+RJZOWWE/t80aZ4rQ4HYR9rA9anEBUSqoar4A+KFRt0mlbCqN3DVxQXai0SS2ke59NKZP1E3vCV6NA==", "license": "MIT", "dependencies": { "marked": "^17.0.1" @@ -1078,14 +1110,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.23.6", - "@tiptap/pm": "3.23.6" + "@tiptap/core": "3.25.0", + "@tiptap/pm": "3.25.0" } }, "node_modules/@tiptap/pm": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/pm/-/pm-3.23.6.tgz", - "integrity": "sha512-in5CaMaWlJcH2A1q6GJKFtrodE8WLS3M9tIi/f89jPmIVHJShpodC0KZDNyJkrVBQomYk0DEh86Utm6ASXzQww==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/pm/-/pm-3.25.0.tgz", + "integrity": "sha512-JeaVgyLj0gQZ1gVxDI73QkP+/Ozcjyp37HyL1pXLCRVjY8nnsDrdMzuKsP1SWN2fOhC+JBGW8/88g0rPmwZQFg==", "license": "MIT", "dependencies": { "prosemirror-changeset": "^2.3.0", @@ -1093,13 +1125,14 @@ "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", - "prosemirror-keymap": "^1.2.2", - "prosemirror-model": "^1.24.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.7", "prosemirror-schema-list": "^1.5.0", - "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.6.4", - "prosemirror-transform": "^1.10.2", - "prosemirror-view": "^1.38.1" + "prosemirror-state": "^1.4.4", + "prosemirror-tables": "^1.8.0", + "prosemirror-transform": "^1.12.0", + "prosemirror-view": "^1.41.8" }, "funding": { "type": "github", @@ -1107,41 +1140,55 @@ } }, "node_modules/@tiptap/starter-kit": { - "version": "3.23.6", - "resolved": "https://registry.npmmirror.com/@tiptap/starter-kit/-/starter-kit-3.23.6.tgz", - "integrity": "sha512-gykwtGWrnWCmtql1hid3opac/KV8zQvOAnu3bTqIqcHrn1FusbUwKmNzavSbfGvcktHM3hFjb35W48JyVLyu/A==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/starter-kit/-/starter-kit-3.25.0.tgz", + "integrity": "sha512-bKe1BhA8YXX7DHC6dsvkkedeQM7r2Iif36i9meTY4szNd9limlnP0ZlFBrBcktl7D/XFy1rkDfD+diWfYeG9BQ==", "license": "MIT", "dependencies": { - "@tiptap/core": "^3.23.6", - "@tiptap/extension-blockquote": "^3.23.6", - "@tiptap/extension-bold": "^3.23.6", - "@tiptap/extension-bullet-list": "^3.23.6", - "@tiptap/extension-code": "^3.23.6", - "@tiptap/extension-code-block": "^3.23.6", - "@tiptap/extension-document": "^3.23.6", - "@tiptap/extension-dropcursor": "^3.23.6", - "@tiptap/extension-gapcursor": "^3.23.6", - "@tiptap/extension-hard-break": "^3.23.6", - "@tiptap/extension-heading": "^3.23.6", - "@tiptap/extension-horizontal-rule": "^3.23.6", - "@tiptap/extension-italic": "^3.23.6", - "@tiptap/extension-link": "^3.23.6", - "@tiptap/extension-list": "^3.23.6", - "@tiptap/extension-list-item": "^3.23.6", - "@tiptap/extension-list-keymap": "^3.23.6", - "@tiptap/extension-ordered-list": "^3.23.6", - "@tiptap/extension-paragraph": "^3.23.6", - "@tiptap/extension-strike": "^3.23.6", - "@tiptap/extension-text": "^3.23.6", - "@tiptap/extension-underline": "^3.23.6", - "@tiptap/extensions": "^3.23.6", - "@tiptap/pm": "^3.23.6" + "@tiptap/core": "^3.25.0", + "@tiptap/extension-blockquote": "^3.25.0", + "@tiptap/extension-bold": "^3.25.0", + "@tiptap/extension-bullet-list": "^3.25.0", + "@tiptap/extension-code": "^3.25.0", + "@tiptap/extension-code-block": "^3.25.0", + "@tiptap/extension-document": "^3.25.0", + "@tiptap/extension-dropcursor": "^3.25.0", + "@tiptap/extension-gapcursor": "^3.25.0", + "@tiptap/extension-hard-break": "^3.25.0", + "@tiptap/extension-heading": "^3.25.0", + "@tiptap/extension-horizontal-rule": "^3.25.0", + "@tiptap/extension-italic": "^3.25.0", + "@tiptap/extension-link": "^3.25.0", + "@tiptap/extension-list": "^3.25.0", + "@tiptap/extension-list-item": "^3.25.0", + "@tiptap/extension-list-keymap": "^3.25.0", + "@tiptap/extension-ordered-list": "^3.25.0", + "@tiptap/extension-paragraph": "^3.25.0", + "@tiptap/extension-strike": "^3.25.0", + "@tiptap/extension-text": "^3.25.0", + "@tiptap/extension-underline": "^3.25.0", + "@tiptap/extensions": "^3.25.0", + "@tiptap/pm": "^3.25.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" } }, + "node_modules/@tiptap/suggestion": { + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/@tiptap/suggestion/-/suggestion-3.25.0.tgz", + "integrity": "sha512-1Idw1WM4Oz9v+3fQ00nh0sajNjIjUtcAREni1Ivky/r1JV6IGjp0q8/v86YQGvp7B+R6OsRks62H8cKO7J+TJg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.25.0", + "@tiptap/pm": "3.25.0" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", @@ -1337,6 +1384,16 @@ "rope-sequence": "^1.3.0" } }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, "node_modules/prosemirror-keymap": { "version": "1.2.3", "resolved": "https://registry.npmmirror.com/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", diff --git a/libs/tiptap-editor/package.json b/libs/tiptap-editor/package.json index 5d3a060..e42f958 100644 --- a/libs/tiptap-editor/package.json +++ b/libs/tiptap-editor/package.json @@ -8,10 +8,15 @@ "dev": "vite" }, "dependencies": { - "@tiptap/core": "^3.0.0", - "@tiptap/markdown": "^3.0.0", - "@tiptap/pm": "^3.0.0", - "@tiptap/starter-kit": "^3.0.0" + "@tiptap/core": "^3.25.0", + "@tiptap/extension-image": "^3.25.0", + "@tiptap/extension-link": "^3.25.0", + "@tiptap/extension-list": "^3.25.0", + "@tiptap/extension-table": "^3.25.0", + "@tiptap/markdown": "^3.25.0", + "@tiptap/pm": "^3.25.0", + "@tiptap/starter-kit": "^3.25.0", + "@tiptap/suggestion": "^3.25.0" }, "devDependencies": { "vite": "^5.4.20" diff --git a/libs/tiptap-editor/src/index.ts b/libs/tiptap-editor/src/index.ts index 4dfa81e..e42bdbe 100644 --- a/libs/tiptap-editor/src/index.ts +++ b/libs/tiptap-editor/src/index.ts @@ -1,6 +1,11 @@ import { Editor } from '@tiptap/core' import StarterKit from '@tiptap/starter-kit' import { Markdown } from '@tiptap/markdown' +import { TableKit } from '@tiptap/extension-table' +import { Image } from '@tiptap/extension-image' +import { Link } from '@tiptap/extension-link' +import { TaskList, TaskItem } from '@tiptap/extension-list' +import { SlashCommand } from './slash-command' import './style.css' export interface EditorOptions { @@ -39,6 +44,17 @@ class TiptapEditorInstance { Markdown.configure({ html: false, }), + TableKit, + Image.configure({ allowBase64: true }), + Link.configure({ + openOnClick: false, + autolink: true, + linkOnPaste: true, + HTMLAttributes: { rel: 'noopener noreferrer', target: '_blank' }, + }), + TaskList, + TaskItem.configure({ nested: true }), + SlashCommand, ], content: this.options.content || '', editable: this.options.editable !== false, diff --git a/libs/tiptap-editor/src/slash-command.ts b/libs/tiptap-editor/src/slash-command.ts new file mode 100644 index 0000000..5dbabe6 --- /dev/null +++ b/libs/tiptap-editor/src/slash-command.ts @@ -0,0 +1,272 @@ +import { Extension, type Range } from '@tiptap/core' +import { Suggestion, type SuggestionProps, type SuggestionKeyDownProps } from '@tiptap/suggestion' +import { PluginKey } from '@tiptap/pm/state' + +interface CommandItem { + title: string + description: string + icon: string + command: (props: { editor: any; range: Range }) => void +} + +const COMMANDS: CommandItem[] = [ + { + title: '标题 1', + description: '大标题', + icon: 'H1', + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).setHeading({ level: 1 }).run() + }, + }, + { + title: '标题 2', + description: '中标题', + icon: 'H2', + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).setHeading({ level: 2 }).run() + }, + }, + { + title: '标题 3', + description: '小标题', + icon: 'H3', + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).setHeading({ level: 3 }).run() + }, + }, + { + title: '无序列表', + description: '创建无序列表', + icon: '•', + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleBulletList().run() + }, + }, + { + title: '有序列表', + description: '创建有序列表', + icon: '1.', + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleOrderedList().run() + }, + }, + { + title: '任务列表', + description: '创建任务列表', + icon: '☑', + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleTaskList().run() + }, + }, + { + title: '引用', + description: '插入引用块', + icon: '❝', + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleBlockquote().run() + }, + }, + { + title: '代码块', + description: '插入代码块', + icon: '<>', + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleCodeBlock().run() + }, + }, + { + title: '分割线', + description: '插入水平分割线', + icon: '—', + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).setHorizontalRule().run() + }, + }, + { + title: '表格', + description: '插入 3×3 表格', + icon: '▦', + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run() + }, + }, + { + title: '图片', + description: '插入图片', + icon: '🖼', + command: ({ editor, range }) => { + const url = window.prompt('输入图片 URL') + if (url) { + editor.chain().focus().deleteRange(range).setImage({ src: url }).run() + } + }, + }, + { + title: '链接', + description: '插入链接', + icon: '🔗', + command: ({ editor, range }) => { + const url = window.prompt('输入链接 URL') + if (url) { + editor.chain().focus().deleteRange(range).setLink({ href: url }).insertContent(url).run() + } + }, + }, +] + +const SlashCommandPluginKey = new PluginKey('slashCommand') + +function createPopup(props: SuggestionProps) { + const component = document.createElement('div') + component.classList.add('slash-command') + + const list = document.createElement('div') + list.classList.add('slash-command-list') + component.appendChild(list) + + let selectedIndex = 0 + let currentItems: CommandItem[] = [] + + function renderItems(items: CommandItem[]) { + currentItems = items + list.innerHTML = '' + selectedIndex = 0 + + items.forEach((item, index) => { + const el = document.createElement('div') + el.classList.add('slash-command-item') + if (index === 0) el.classList.add('is-selected') + + el.innerHTML = ` +
${item.icon}
+
+
${item.title}
+
${item.description}
+
+ ` + + el.addEventListener('click', () => { + props.command(item) + }) + + el.addEventListener('mouseenter', () => { + selectedIndex = index + updateSelection() + }) + + list.appendChild(el) + }) + } + + function updateSelection() { + const children = list.children + for (let i = 0; i < children.length; i++) { + if (i === selectedIndex) { + children[i].classList.add('is-selected') + } else { + children[i].classList.remove('is-selected') + } + } + children[selectedIndex]?.scrollIntoView({ block: 'nearest' }) + } + + function selectItem() { + if (currentItems[selectedIndex]) { + props.command(currentItems[selectedIndex]) + } + } + + function updatePosition() { + const rect = props.clientRect?.() + if (!rect) return + component.style.left = `${rect.left}px` + component.style.top = `${rect.bottom + 4}px` + } + + renderItems(props.items) + document.body.appendChild(component) + updatePosition() + + return { + component, + updateItems(items: CommandItem[]) { + renderItems(items) + }, + updatePosition, + onKeyDown({ event }: SuggestionKeyDownProps): boolean { + if (event.key === 'ArrowUp') { + event.preventDefault() + selectedIndex = (selectedIndex - 1 + currentItems.length) % currentItems.length + updateSelection() + return true + } + if (event.key === 'ArrowDown') { + event.preventDefault() + selectedIndex = (selectedIndex + 1) % currentItems.length + updateSelection() + return true + } + if (event.key === 'Enter') { + event.preventDefault() + selectItem() + return true + } + if (event.key === 'Escape') { + event.preventDefault() + return true + } + return false + }, + destroy() { + component.remove() + }, + } +} + +export const SlashCommand = Extension.create({ + name: 'slashCommand', + + addProseMirrorPlugins() { + return [ + Suggestion({ + pluginKey: SlashCommandPluginKey, + editor: this.editor, + char: '/', + items: ({ query }) => { + return COMMANDS.filter( + (item) => + item.title.toLowerCase().includes(query.toLowerCase()) || + item.description.toLowerCase().includes(query.toLowerCase()) + ) + }, + render() { + let popup: ReturnType | null = null + + return { + onStart(props) { + popup = createPopup(props) + }, + onUpdate(props) { + if (!popup) return + popup.updateItems(props.items) + popup.updatePosition() + }, + onKeyDown(props) { + if (!popup) return false + return popup.onKeyDown(props) + }, + onExit() { + if (popup) { + popup.destroy() + popup = null + } + }, + } + }, + command: ({ editor, range, props: item }) => { + item.command({ editor, range }) + }, + }), + ] + }, +}) diff --git a/libs/tiptap-editor/src/style.css b/libs/tiptap-editor/src/style.css index 954f2f7..3b74901 100644 --- a/libs/tiptap-editor/src/style.css +++ b/libs/tiptap-editor/src/style.css @@ -137,36 +137,6 @@ margin: 1.5em 0; } -.tiptap-editor a { - color: #0366d6; - text-decoration: none; -} - -.tiptap-editor a:hover { - text-decoration: underline; -} - -/* Task list */ -.tiptap-editor ul[data-type="taskList"] { - list-style: none; - padding-left: 0; -} - -.tiptap-editor ul[data-type="taskList"] li { - display: flex; - align-items: flex-start; - gap: 0.5em; -} - -.tiptap-editor ul[data-type="taskList"] li > label { - flex-shrink: 0; - margin-top: 0.3em; -} - -.tiptap-editor ul[data-type="taskList"] li > div { - flex: 1; -} - /* Selection */ .tiptap-editor .ProseMirror ::selection { background: #b4d7ff; @@ -223,3 +193,235 @@ .dark .tiptap-editor .ProseMirror ::selection { background: #1c4e80; } + +/* ========== Link ========== */ +.tiptap-editor a { + color: #0366d6; + text-decoration: none; +} + +.tiptap-editor a:hover { + text-decoration: underline; +} + +/* ========== Table ========== */ +.tiptap-editor table { + border-collapse: collapse; + table-layout: fixed; + width: 100%; + margin: 1em 0; + overflow: hidden; + border: 1px solid #e1e4e8; + border-radius: 6px; +} + +.tiptap-editor table td, +.tiptap-editor table th { + min-width: 80px; + border: 1px solid #e1e4e8; + padding: 8px 12px; + position: relative; + vertical-align: top; + box-sizing: border-box; +} + +.tiptap-editor table th { + background: #f6f8fa; + font-weight: 600; + text-align: left; +} + +.tiptap-editor table .selectedCell { + background: #b4d7ff; +} + +.tiptap-editor table .column-resize-handle { + position: absolute; + right: -2px; + top: 0; + bottom: -2px; + width: 4px; + background-color: #adf; + pointer-events: none; +} + +.tiptap-editor .tableWrapper { + padding: 1rem 0; + overflow-x: auto; +} + +.tiptap-editor .resize-cursor { + cursor: ew-resize; + cursor: col-resize; +} + +/* ========== Slash Command ========== */ +.slash-command { + position: fixed; + z-index: 9999; + background: #fff; + border: 1px solid #e1e4e8; + border-radius: 8px; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12); + padding: 4px; + min-width: 260px; + max-height: 320px; + overflow-y: auto; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +} + +.slash-command-list { + display: flex; + flex-direction: column; +} + +.slash-command-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + border-radius: 6px; + cursor: pointer; + transition: background 0.15s; +} + +.slash-command-item:hover, +.slash-command-item.is-selected { + background: #f0f4ff; +} + +.slash-command-item-icon { + flex-shrink: 0; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: #f3f3f3; + border-radius: 6px; + font-size: 13px; + font-weight: 600; + color: #555; +} + +.slash-command-item-text { + flex: 1; + min-width: 0; +} + +.slash-command-item-title { + font-size: 14px; + font-weight: 500; + color: #1a1a1a; + line-height: 1.3; +} + +.slash-command-item-desc { + font-size: 12px; + color: #999; + line-height: 1.3; +} + +/* ========== Task List (Enhanced) ========== */ +.tiptap-editor ul[data-type="taskList"] { + list-style: none; + padding-left: 0; +} + +.tiptap-editor ul[data-type="taskList"] li { + display: flex; + align-items: flex-start; + gap: 0.5em; + margin: 0.3em 0; +} + +.tiptap-editor ul[data-type="taskList"] li > label { + flex-shrink: 0; + margin-top: 0.3em; + cursor: pointer; + user-select: none; +} + +.tiptap-editor ul[data-type="taskList"] li > label input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: #0366d6; + cursor: pointer; +} + +.tiptap-editor ul[data-type="taskList"] li > div { + flex: 1; +} + +.tiptap-editor ul[data-type="taskList"] li[data-checked="true"] > div { + text-decoration: line-through; + opacity: 0.6; +} + +/* ========== Image ========== */ +.tiptap-editor img { + max-width: 100%; + height: auto; + border-radius: 6px; + margin: 1em 0; + display: block; +} + +.tiptap-editor img.ProseMirror-selectednode { + outline: 2px solid #0366d6; + border-radius: 6px; +} + +/* ========== Dark Theme: Table ========== */ +.dark .tiptap-editor table { + border-color: #333; +} + +.dark .tiptap-editor table td, +.dark .tiptap-editor table th { + border-color: #333; + color: #dadadb; +} + +.dark .tiptap-editor table th { + background: #2e2e33; +} + +.dark .tiptap-editor table .selectedCell { + background: #1c4e80; +} + +/* ========== Dark Theme: Slash Command ========== */ +.dark .slash-command { + background: #2e2e33; + border-color: #444; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4); +} + +.dark .slash-command-item:hover, +.dark .slash-command-item.is-selected { + background: #3a3a44; +} + +.dark .slash-command-item-icon { + background: #3a3a44; + color: #dadadb; +} + +.dark .slash-command-item-title { + color: #f0f0f0; +} + +.dark .slash-command-item-desc { + color: #888; +} + +/* ========== Dark Theme: Image ========== */ +.dark .tiptap-editor img.ProseMirror-selectednode { + outline-color: #58a6ff; +} + +/* ========== Dark Theme: Task List ========== */ +.dark .tiptap-editor ul[data-type="taskList"] li > label input[type="checkbox"] { + accent-color: #58a6ff; +} diff --git a/public/tiptap/editor.css b/public/tiptap/editor.css index 6ee9565..f4269ec 100644 --- a/public/tiptap/editor.css +++ b/public/tiptap/editor.css @@ -1 +1 @@ -.tiptap-editor{position:relative;width:100%;height:100%;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:16px;line-height:1.75;color:#2c2c2c;outline:none}.tiptap-editor .ProseMirror{width:100%;height:100%;padding:24px 32px;outline:none;overflow-y:auto}.tiptap-editor .ProseMirror-focused{outline:none}.tiptap-editor .ProseMirror p:first-child:last-child:has(br.ProseMirror-trailingBreak:only-child):before{content:"在此输入内容...";float:left;color:#999;pointer-events:none;height:0}.tiptap-editor h1{font-size:2em;font-weight:700;margin:1.2em 0 .6em;line-height:1.3;color:#1a1a1a}.tiptap-editor h2{font-size:1.5em;font-weight:600;margin:1em 0 .5em;line-height:1.35;color:#1a1a1a}.tiptap-editor h3{font-size:1.25em;font-weight:600;margin:.8em 0 .4em;color:#1a1a1a}.tiptap-editor p{margin:.6em 0}.tiptap-editor p:first-child{margin-top:0}.tiptap-editor strong{font-weight:700;color:#1a1a1a}.tiptap-editor em{font-style:italic}.tiptap-editor s{text-decoration:line-through;opacity:.7}.tiptap-editor code{font-family:SF Mono,Monaco,Cascadia Code,Roboto Mono,Consolas,monospace;font-size:.875em;background:#f3f3f3;padding:.15em .4em;border-radius:3px;color:#d73a49}.tiptap-editor pre{background:#f6f8fa;border-radius:6px;padding:16px;overflow-x:auto;margin:1em 0}.tiptap-editor pre code{background:none;padding:0;color:#24292e;font-size:.85em;line-height:1.6}.tiptap-editor blockquote{border-left:4px solid #dfe2e5;padding-left:16px;margin:1em 0;color:#6a737d;font-style:italic}.tiptap-editor ul{list-style-type:disc;margin:.6em 0;padding-left:2em}.tiptap-editor ol{list-style-type:decimal;margin:.6em 0;padding-left:2em}.tiptap-editor li{margin:.2em 0}.tiptap-editor li>p{margin:.2em 0}.tiptap-editor hr{border:none;border-top:1px solid #e1e4e8;margin:1.5em 0}.tiptap-editor a{color:#0366d6;text-decoration:none}.tiptap-editor a:hover{text-decoration:underline}.tiptap-editor ul[data-type=taskList]{list-style:none;padding-left:0}.tiptap-editor ul[data-type=taskList] li{display:flex;align-items:flex-start;gap:.5em}.tiptap-editor ul[data-type=taskList] li>label{flex-shrink:0;margin-top:.3em}.tiptap-editor ul[data-type=taskList] li>div{flex:1}.tiptap-editor .ProseMirror ::selection{background:#b4d7ff}.tiptap-editor .ProseMirror-focused .ProseMirror-gapcursor{display:none}.dark .tiptap-editor{color:#dadadb}.dark .tiptap-editor h1,.dark .tiptap-editor h2,.dark .tiptap-editor h3,.dark .tiptap-editor strong{color:#f0f0f0}.dark .tiptap-editor .ProseMirror p.is-editor-empty:first-child:before{color:#666}.dark .tiptap-editor code{background:#2e2e33;color:#ff7b72}.dark .tiptap-editor pre{background:#2e2e33}.dark .tiptap-editor pre code{color:#dadadb}.dark .tiptap-editor blockquote{border-left-color:#444;color:#9b9c9d}.dark .tiptap-editor hr{border-top-color:#333}.dark .tiptap-editor a{color:#58a6ff}.dark .tiptap-editor .ProseMirror ::selection{background:#1c4e80} +.tiptap-editor{position:relative;width:100%;height:100%;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:16px;line-height:1.75;color:#2c2c2c;outline:none}.tiptap-editor .ProseMirror{width:100%;height:100%;padding:24px 32px;outline:none;overflow-y:auto}.tiptap-editor .ProseMirror-focused{outline:none}.tiptap-editor .ProseMirror p:first-child:last-child:has(br.ProseMirror-trailingBreak:only-child):before{content:"在此输入内容...";float:left;color:#999;pointer-events:none;height:0}.tiptap-editor h1{font-size:2em;font-weight:700;margin:1.2em 0 .6em;line-height:1.3;color:#1a1a1a}.tiptap-editor h2{font-size:1.5em;font-weight:600;margin:1em 0 .5em;line-height:1.35;color:#1a1a1a}.tiptap-editor h3{font-size:1.25em;font-weight:600;margin:.8em 0 .4em;color:#1a1a1a}.tiptap-editor p{margin:.6em 0}.tiptap-editor p:first-child{margin-top:0}.tiptap-editor strong{font-weight:700;color:#1a1a1a}.tiptap-editor em{font-style:italic}.tiptap-editor s{text-decoration:line-through;opacity:.7}.tiptap-editor code{font-family:SF Mono,Monaco,Cascadia Code,Roboto Mono,Consolas,monospace;font-size:.875em;background:#f3f3f3;padding:.15em .4em;border-radius:3px;color:#d73a49}.tiptap-editor pre{background:#f6f8fa;border-radius:6px;padding:16px;overflow-x:auto;margin:1em 0}.tiptap-editor pre code{background:none;padding:0;color:#24292e;font-size:.85em;line-height:1.6}.tiptap-editor blockquote{border-left:4px solid #dfe2e5;padding-left:16px;margin:1em 0;color:#6a737d;font-style:italic}.tiptap-editor ul{list-style-type:disc;margin:.6em 0;padding-left:2em}.tiptap-editor ol{list-style-type:decimal;margin:.6em 0;padding-left:2em}.tiptap-editor li{margin:.2em 0}.tiptap-editor li>p{margin:.2em 0}.tiptap-editor hr{border:none;border-top:1px solid #e1e4e8;margin:1.5em 0}.tiptap-editor .ProseMirror ::selection{background:#b4d7ff}.tiptap-editor .ProseMirror-focused .ProseMirror-gapcursor{display:none}.dark .tiptap-editor{color:#dadadb}.dark .tiptap-editor h1,.dark .tiptap-editor h2,.dark .tiptap-editor h3,.dark .tiptap-editor strong{color:#f0f0f0}.dark .tiptap-editor .ProseMirror p.is-editor-empty:first-child:before{color:#666}.dark .tiptap-editor code{background:#2e2e33;color:#ff7b72}.dark .tiptap-editor pre{background:#2e2e33}.dark .tiptap-editor pre code{color:#dadadb}.dark .tiptap-editor blockquote{border-left-color:#444;color:#9b9c9d}.dark .tiptap-editor hr{border-top-color:#333}.dark .tiptap-editor a{color:#58a6ff}.dark .tiptap-editor .ProseMirror ::selection{background:#1c4e80}.tiptap-editor a{color:#0366d6;text-decoration:none}.tiptap-editor a:hover{text-decoration:underline}.tiptap-editor table{border-collapse:collapse;table-layout:fixed;width:100%;margin:1em 0;overflow:hidden;border:1px solid #e1e4e8;border-radius:6px}.tiptap-editor table td,.tiptap-editor table th{min-width:80px;border:1px solid #e1e4e8;padding:8px 12px;position:relative;vertical-align:top;box-sizing:border-box}.tiptap-editor table th{background:#f6f8fa;font-weight:600;text-align:left}.tiptap-editor table .selectedCell{background:#b4d7ff}.tiptap-editor table .column-resize-handle{position:absolute;right:-2px;top:0;bottom:-2px;width:4px;background-color:#adf;pointer-events:none}.tiptap-editor .tableWrapper{padding:1rem 0;overflow-x:auto}.tiptap-editor .resize-cursor{cursor:ew-resize;cursor:col-resize}.slash-command{position:fixed;z-index:9999;background:#fff;border:1px solid #e1e4e8;border-radius:8px;box-shadow:0 4px 24px #0000001f;padding:4px;min-width:260px;max-height:320px;overflow-y:auto;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.slash-command-list{display:flex;flex-direction:column}.slash-command-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:6px;cursor:pointer;transition:background .15s}.slash-command-item:hover,.slash-command-item.is-selected{background:#f0f4ff}.slash-command-item-icon{flex-shrink:0;width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:#f3f3f3;border-radius:6px;font-size:13px;font-weight:600;color:#555}.slash-command-item-text{flex:1;min-width:0}.slash-command-item-title{font-size:14px;font-weight:500;color:#1a1a1a;line-height:1.3}.slash-command-item-desc{font-size:12px;color:#999;line-height:1.3}.tiptap-editor ul[data-type=taskList]{list-style:none;padding-left:0}.tiptap-editor ul[data-type=taskList] li{display:flex;align-items:flex-start;gap:.5em;margin:.3em 0}.tiptap-editor ul[data-type=taskList] li>label{flex-shrink:0;margin-top:.3em;cursor:pointer;-webkit-user-select:none;user-select:none}.tiptap-editor ul[data-type=taskList] li>label input[type=checkbox]{width:16px;height:16px;accent-color:#0366d6;cursor:pointer}.tiptap-editor ul[data-type=taskList] li>div{flex:1}.tiptap-editor ul[data-type=taskList] li[data-checked=true]>div{text-decoration:line-through;opacity:.6}.tiptap-editor img{max-width:100%;height:auto;border-radius:6px;margin:1em 0;display:block}.tiptap-editor img.ProseMirror-selectednode{outline:2px solid #0366d6;border-radius:6px}.dark .tiptap-editor table{border-color:#333}.dark .tiptap-editor table td,.dark .tiptap-editor table th{border-color:#333;color:#dadadb}.dark .tiptap-editor table th{background:#2e2e33}.dark .tiptap-editor table .selectedCell{background:#1c4e80}.dark .slash-command{background:#2e2e33;border-color:#444;box-shadow:0 4px 24px #0006}.dark .slash-command-item:hover,.dark .slash-command-item.is-selected{background:#3a3a44}.dark .slash-command-item-icon{background:#3a3a44;color:#dadadb}.dark .slash-command-item-title{color:#f0f0f0}.dark .slash-command-item-desc{color:#888}.dark .tiptap-editor img.ProseMirror-selectednode{outline-color:#58a6ff}.dark .tiptap-editor ul[data-type=taskList] li>label input[type=checkbox]{accent-color:#58a6ff}