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
This commit is contained in:
xfy 2026-06-05 10:20:47 +08:00
parent 5321b8cf97
commit a8704e199f
6 changed files with 729 additions and 177 deletions

View File

@ -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",

View File

@ -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"

View File

@ -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,

View File

@ -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<CommandItem>) {
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 = `
<div class="slash-command-item-icon">${item.icon}</div>
<div class="slash-command-item-text">
<div class="slash-command-item-title">${item.title}</div>
<div class="slash-command-item-desc">${item.description}</div>
</div>
`
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<CommandItem>({
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<typeof createPopup> | 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 })
},
}),
]
},
})

View File

@ -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;
}

File diff suppressed because one or more lines are too long