feat: add Tiptap WYSIWYG editor component for task descriptions
Replace Textarea + Edit/Preview tabs with a reusable WYSIWYG editor based on Tiptap v3. Integrated into TaskDetailDialog for editing task descriptions with native markdown I/O. Components: - TiptapEditor: main component with EditorContext.Provider pattern - TiptapToolbar: configurable toolbar with useEditorState for v3 reactivity - TiptapBubbleMenu: floating formatting menu on text selection - useTiptapEditor: core hook with markdown I/O, content sync, stale closure prevention - presets: full/compact/minimal editor configurations - tiptapStyles.css: ProseMirror styles matching MarkdownViewer values Key details: - Data format stays markdown string (not HTML) - contentType: 'markdown' for both init and setContent - GFM enabled via markedOptions - ProseMirror li>p margin fix, nested list styles, gapcursor
This commit is contained in:
parent
a29d8403d6
commit
37a4c458bb
11 changed files with 1486 additions and 61 deletions
|
|
@ -96,6 +96,7 @@
|
|||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fastify/cors": "^11.2.0",
|
||||
"@fastify/static": "^9.0.0",
|
||||
"@floating-ui/dom": "^1.7.6",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
|
|
@ -109,6 +110,11 @@
|
|||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tanstack/react-virtual": "^3.10.8",
|
||||
"@tiptap/extension-placeholder": "^3.20.1",
|
||||
"@tiptap/markdown": "^3.20.1",
|
||||
"@tiptap/pm": "^3.20.1",
|
||||
"@tiptap/react": "^3.20.1",
|
||||
"@tiptap/starter-kit": "^3.20.1",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/addon-web-links": "^0.12.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
|
|
|
|||
650
pnpm-lock.yaml
650
pnpm-lock.yaml
|
|
@ -101,6 +101,9 @@ importers:
|
|||
'@fastify/static':
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0
|
||||
'@floating-ui/dom':
|
||||
specifier: ^1.7.6
|
||||
version: 1.7.6
|
||||
'@radix-ui/react-alert-dialog':
|
||||
specifier: ^1.1.15
|
||||
version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
|
@ -140,6 +143,21 @@ importers:
|
|||
'@tanstack/react-virtual':
|
||||
specifier: ^3.10.8
|
||||
version: 3.13.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tiptap/extension-placeholder':
|
||||
specifier: ^3.20.1
|
||||
version: 3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))
|
||||
'@tiptap/markdown':
|
||||
specifier: ^3.20.1
|
||||
version: 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm':
|
||||
specifier: ^3.20.1
|
||||
version: 3.20.1
|
||||
'@tiptap/react':
|
||||
specifier: ^3.20.1
|
||||
version: 3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tiptap/starter-kit':
|
||||
specifier: ^3.20.1
|
||||
version: 3.20.1
|
||||
'@xterm/addon-fit':
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.0
|
||||
|
|
@ -1105,11 +1123,11 @@ packages:
|
|||
'@fastify/static@9.0.0':
|
||||
resolution: {integrity: sha512-r64H8Woe/vfilg5RTy7lwWlE8ZZcTrc3kebYFMEUBrMqlydhQyoiExQXdYAy2REVpST/G35+stAM8WYp1WGmMA==}
|
||||
|
||||
'@floating-ui/core@1.7.4':
|
||||
resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==}
|
||||
'@floating-ui/core@1.7.5':
|
||||
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
|
||||
|
||||
'@floating-ui/dom@1.7.5':
|
||||
resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==}
|
||||
'@floating-ui/dom@1.7.6':
|
||||
resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
|
||||
|
||||
'@floating-ui/react-dom@2.1.7':
|
||||
resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==}
|
||||
|
|
@ -1117,8 +1135,8 @@ packages:
|
|||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
'@floating-ui/utils@0.2.11':
|
||||
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
|
||||
|
||||
'@gar/promisify@1.1.3':
|
||||
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
|
||||
|
|
@ -1864,6 +1882,9 @@ packages:
|
|||
'@radix-ui/rect@1.1.1':
|
||||
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||
|
||||
'@remirror/core-constants@3.0.0':
|
||||
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27':
|
||||
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
|
||||
|
||||
|
|
@ -2027,6 +2048,166 @@ packages:
|
|||
'@tanstack/virtual-core@3.13.18':
|
||||
resolution: {integrity: sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==}
|
||||
|
||||
'@tiptap/core@3.20.1':
|
||||
resolution: {integrity: sha512-SwkPEWIfaDEZjC8SEIi4kZjqIYUbRgLUHUuQezo5GbphUNC8kM1pi3C3EtoOPtxXrEbY6e4pWEzW54Pcrd+rVA==}
|
||||
peerDependencies:
|
||||
'@tiptap/pm': ^3.20.1
|
||||
|
||||
'@tiptap/extension-blockquote@3.20.1':
|
||||
resolution: {integrity: sha512-WzNXk/63PQI2fav4Ta6P0GmYRyu8Gap1pV3VUqaVK829iJ6Zt1T21xayATHEHWMK27VT1GLPJkx9Ycr2jfDyQw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extension-bold@3.20.1':
|
||||
resolution: {integrity: sha512-fz++Qv6Rk/Hov0IYG/r7TJ1Y4zWkuGONe0UN5g0KY32NIMg3HeOHicbi4xsNWTm9uAOl3eawWDkezEMrleObMw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extension-bubble-menu@3.20.1':
|
||||
resolution: {integrity: sha512-XaPvO6aCoWdFnCBus0s88lnj17NR/OopV79i8Qhgz3WMR0vrsL5zsd45l0lZuu9pSvm5VW47SoxakkJiZC1suw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
'@tiptap/pm': ^3.20.1
|
||||
|
||||
'@tiptap/extension-bullet-list@3.20.1':
|
||||
resolution: {integrity: sha512-mbrlvOZo5OF3vLhp+3fk9KuL/6J/wsN0QxF6ZFRAHzQ9NkJdtdfARcBeBnkWXGN8inB6YxbTGY1/E4lmBkOpOw==}
|
||||
peerDependencies:
|
||||
'@tiptap/extension-list': ^3.20.1
|
||||
|
||||
'@tiptap/extension-code-block@3.20.1':
|
||||
resolution: {integrity: sha512-vKejwBq+Nlj4Ybd3qOyDxIQKzYymdNH+8eXkKwGShk2nfLJIxq69DCyGvmuHgipIO1qcYPJ149UNpGN+YGcdmA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
'@tiptap/pm': ^3.20.1
|
||||
|
||||
'@tiptap/extension-code@3.20.1':
|
||||
resolution: {integrity: sha512-509DHINIA/Gg+eTG7TEkfsS8RUiPLH5xZNyLRT0A1oaoaJmECKfrV6aAm05IdfTyqDqz6LW5pbnX6DdUC4keug==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extension-document@3.20.1':
|
||||
resolution: {integrity: sha512-9vrqdGmRV7bQCSY3NLgu7UhIwgOCDp4sKqMNsoNRX0aZ021QQMTvBQDPkiRkCf7MNsnWrNNnr52PVnULEn3vFQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extension-dropcursor@3.20.1':
|
||||
resolution: {integrity: sha512-K18L9FX4znn+ViPSIbTLOGcIaXMx/gLNwAPE8wPLwswbHhQqdiY1zzdBw6drgOc1Hicvebo2dIoUlSXOZsOEcw==}
|
||||
peerDependencies:
|
||||
'@tiptap/extensions': ^3.20.1
|
||||
|
||||
'@tiptap/extension-floating-menu@3.20.1':
|
||||
resolution: {integrity: sha512-BeDC6nfOesIMn5pFuUnkEjOxGv80sOJ8uk1mdt9/3Fkvra8cB9NIYYCVtd6PU8oQFmJ8vFqPrRkUWrG5tbqnOg==}
|
||||
peerDependencies:
|
||||
'@floating-ui/dom': ^1.0.0
|
||||
'@tiptap/core': ^3.20.1
|
||||
'@tiptap/pm': ^3.20.1
|
||||
|
||||
'@tiptap/extension-gapcursor@3.20.1':
|
||||
resolution: {integrity: sha512-kZOtttV6Ai8VUAgEng3h4WKFbtdSNJ6ps7r0cRPY+FctWhVmgNb/JJwwyC+vSilR7nRENAhrA/Cv/RxVlvLw+g==}
|
||||
peerDependencies:
|
||||
'@tiptap/extensions': ^3.20.1
|
||||
|
||||
'@tiptap/extension-hard-break@3.20.1':
|
||||
resolution: {integrity: sha512-9sKpmg/IIdlLXimYWUZ3PplIRcehv4Oc7V1miTqlnAthMzjMqigDkjjgte4JZV67RdnDJTQkRw8TklCAU28Emg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extension-heading@3.20.1':
|
||||
resolution: {integrity: sha512-unudyfQP6FxnyWinxvPqe/51DG91J6AaJm666RnAubgYMCgym+33kBftx4j4A6qf+ddWYbD00thMNKOnVLjAEQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extension-horizontal-rule@3.20.1':
|
||||
resolution: {integrity: sha512-rjFKFXNntdl0jay8oIGFvvykHlpyQTLmrH3Ag2fj3i8yh6MVvqhtaDomYQbw5sxECd5hBkL+T4n2d2DRuVw/QQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
'@tiptap/pm': ^3.20.1
|
||||
|
||||
'@tiptap/extension-italic@3.20.1':
|
||||
resolution: {integrity: sha512-ZYRX13Kt8tR8JOzSXirH3pRpi8x30o7LHxZY58uXBdUvr3tFzOkh03qbN523+diidSVeHP/aMd/+IrplHRkQug==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extension-link@3.20.1':
|
||||
resolution: {integrity: sha512-oYTTIgsQMqpkSnJAuAc+UtIKMuI4lv9e1y4LfI1iYm6NkEUHhONppU59smhxHLzb3Ww7YpDffbp5IgDTAiJztA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
'@tiptap/pm': ^3.20.1
|
||||
|
||||
'@tiptap/extension-list-item@3.20.1':
|
||||
resolution: {integrity: sha512-tzgnyTW82lYJkUnadYbatwkI9dLz/OWRSWuFpQPRje/ItmFMWuQ9c9NDD8qLbXPdEYnvrgSAA+ipCD/1G0qA0Q==}
|
||||
peerDependencies:
|
||||
'@tiptap/extension-list': ^3.20.1
|
||||
|
||||
'@tiptap/extension-list-keymap@3.20.1':
|
||||
resolution: {integrity: sha512-Dr0xsQKx0XPOgDg7xqoWwfv7FFwZ3WeF3eOjqh3rDXlNHMj1v+UW5cj1HLphrsAZHTrVTn2C+VWPJkMZrSbpvQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/extension-list': ^3.20.1
|
||||
|
||||
'@tiptap/extension-list@3.20.1':
|
||||
resolution: {integrity: sha512-euBRAn0mkV7R2VEE+AuOt3R0j9RHEMFXamPFmtvTo8IInxDClusrm6mJoDjS8gCGAXsQCRiAe1SCQBPgGbOOwg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
'@tiptap/pm': ^3.20.1
|
||||
|
||||
'@tiptap/extension-ordered-list@3.20.1':
|
||||
resolution: {integrity: sha512-Y+3Ad7OwAdagqdYwCnbqf7/to5ypD4NnUNHA0TXRCs7cAHRA8AdgPoIcGFpaaSpV86oosNU3yfeJouYeroffog==}
|
||||
peerDependencies:
|
||||
'@tiptap/extension-list': ^3.20.1
|
||||
|
||||
'@tiptap/extension-paragraph@3.20.1':
|
||||
resolution: {integrity: sha512-QFrAtXNyv7JSnomMQc1nx5AnG9mMznfbYJAbdOQYVdbLtAzTfiTuNPNbQrufy5ZGtGaHxDCoaygu2QEfzaKG+Q==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extension-placeholder@3.20.1':
|
||||
resolution: {integrity: sha512-k+jfbCugYGuIFBdojukgEopGazIMOgHrw46FnyN2X/6ICOIjQP2rh2ObslrsUOsJYoEevxCsNF9hZl1HvWX66g==}
|
||||
peerDependencies:
|
||||
'@tiptap/extensions': ^3.20.1
|
||||
|
||||
'@tiptap/extension-strike@3.20.1':
|
||||
resolution: {integrity: sha512-EYgyma10lpsY+rwbVQL9u+gA7hBlKLSMFH7Zgd37FSxukOjr+HE8iKPQQ+SwbGejyDsPlLT8Z5Jnuxo5Ng90Pg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extension-text@3.20.1':
|
||||
resolution: {integrity: sha512-7PlIbYW8UenV6NPOXHmv8IcmPGlGx6HFq66RmkJAOJRPXPkTLAiX0N8rQtzUJ6jDEHqoJpaHFEHJw0xzW1yF+A==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extension-underline@3.20.1':
|
||||
resolution: {integrity: sha512-fmHvDKzwCgnZUwRreq8tYkb1YyEwgzZ6QQkAQ0CsCRtvRMqzerr3Duz0Als4i8voZTuGDEL3VR6nAJbLAb/wPg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
|
||||
'@tiptap/extensions@3.20.1':
|
||||
resolution: {integrity: sha512-JRc/v+OBH0qLTdvQ7HvHWTxGJH73QOf1MC0R8NhOX2QnAbg2mPFv1h+FjGa2gfLGuCXBdWQomjekWkUKbC4e5A==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
'@tiptap/pm': ^3.20.1
|
||||
|
||||
'@tiptap/markdown@3.20.1':
|
||||
resolution: {integrity: sha512-dNrtP7kmabDomgjv9G/6+JSFL6WraPaFbmKh1eHSYKdDGvIwBfJnVPTV2VS3bP1OuYJEDJN/2ydtiCHyOTrQsQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
'@tiptap/pm': ^3.20.1
|
||||
|
||||
'@tiptap/pm@3.20.1':
|
||||
resolution: {integrity: sha512-6kCiGLvpES4AxcEuOhb7HR7/xIeJWMjZlb6J7e8zpiIh5BoQc7NoRdctsnmFEjZvC19bIasccshHQ7H2zchWqw==}
|
||||
|
||||
'@tiptap/react@3.20.1':
|
||||
resolution: {integrity: sha512-UH1NpVpCaZBGB3Yr5N6aTS+rsCMDl9wHfrt/w+6+Gz4KHFZ2OILA82hELxZzhNc1Lmjz8vgCArKcsYql9gbzJA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.20.1
|
||||
'@tiptap/pm': ^3.20.1
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
'@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@tiptap/starter-kit@3.20.1':
|
||||
resolution: {integrity: sha512-opqWxL/4OTEiqmVC0wsU4o3JhAf6LycJ2G/gRIZVAIFLljI9uHfpPMTFGxZ5w9IVVJaP5PJysfwW/635kKqkrw==}
|
||||
|
||||
'@tokenizer/inflate@0.4.1':
|
||||
resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -2185,9 +2366,18 @@ packages:
|
|||
'@types/keyv@3.1.4':
|
||||
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
||||
|
||||
'@types/linkify-it@5.0.0':
|
||||
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
|
||||
|
||||
'@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@2.0.0':
|
||||
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
|
||||
|
||||
'@types/ms@2.1.0':
|
||||
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
||||
|
||||
|
|
@ -2235,6 +2425,9 @@ packages:
|
|||
'@types/unist@3.0.3':
|
||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||
|
||||
'@types/use-sync-external-store@0.0.6':
|
||||
resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
|
||||
|
||||
'@types/verror@1.10.11':
|
||||
resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==}
|
||||
|
||||
|
|
@ -3504,6 +3697,10 @@ packages:
|
|||
end-of-stream@1.4.5:
|
||||
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@6.0.1:
|
||||
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
|
@ -3798,6 +3995,10 @@ packages:
|
|||
fast-equals@4.0.3:
|
||||
resolution: {integrity: sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==}
|
||||
|
||||
fast-equals@5.4.0:
|
||||
resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
|
|
@ -4665,6 +4866,12 @@ packages:
|
|||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
linkify-it@5.0.0:
|
||||
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
|
||||
|
||||
linkifyjs@4.3.2:
|
||||
resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
|
||||
|
||||
lint-staged@16.2.7:
|
||||
resolution: {integrity: sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==}
|
||||
engines: {node: '>=20.17'}
|
||||
|
|
@ -4779,6 +4986,10 @@ packages:
|
|||
resolution: {integrity: sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
markdown-it@14.1.1:
|
||||
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
|
||||
hasBin: true
|
||||
|
||||
markdown-table@3.0.4:
|
||||
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
|
||||
|
||||
|
|
@ -4787,6 +4998,11 @@ packages:
|
|||
engines: {node: '>= 20'}
|
||||
hasBin: true
|
||||
|
||||
marked@17.0.4:
|
||||
resolution: {integrity: sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==}
|
||||
engines: {node: '>= 20'}
|
||||
hasBin: true
|
||||
|
||||
matcher@3.0.0:
|
||||
resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -4844,6 +5060,9 @@ packages:
|
|||
mdast-util-to-string@4.0.0:
|
||||
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
|
||||
|
||||
mdurl@2.0.0:
|
||||
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
||||
|
||||
media-typer@1.1.0:
|
||||
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
|
@ -5235,6 +5454,9 @@ packages:
|
|||
resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
orderedmap@2.1.1:
|
||||
resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
|
||||
|
||||
own-keys@1.0.1:
|
||||
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -5547,6 +5769,64 @@ packages:
|
|||
property-information@7.1.0:
|
||||
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
||||
|
||||
prosemirror-changeset@2.4.0:
|
||||
resolution: {integrity: sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==}
|
||||
|
||||
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.4.1:
|
||||
resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==}
|
||||
|
||||
prosemirror-history@1.5.0:
|
||||
resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==}
|
||||
|
||||
prosemirror-inputrules@1.5.1:
|
||||
resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==}
|
||||
|
||||
prosemirror-keymap@1.2.3:
|
||||
resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==}
|
||||
|
||||
prosemirror-markdown@1.13.4:
|
||||
resolution: {integrity: sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==}
|
||||
|
||||
prosemirror-menu@1.3.0:
|
||||
resolution: {integrity: sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==}
|
||||
|
||||
prosemirror-model@1.25.4:
|
||||
resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==}
|
||||
|
||||
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.4:
|
||||
resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==}
|
||||
|
||||
prosemirror-tables@1.8.5:
|
||||
resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==}
|
||||
|
||||
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.11.0:
|
||||
resolution: {integrity: sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==}
|
||||
|
||||
prosemirror-view@1.41.6:
|
||||
resolution: {integrity: sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
|
@ -5557,6 +5837,10 @@ packages:
|
|||
pump@3.0.3:
|
||||
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
|
||||
|
||||
punycode.js@2.3.1:
|
||||
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -5805,6 +6089,9 @@ packages:
|
|||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
rope-sequence@1.3.4:
|
||||
resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
|
||||
|
||||
roughjs@4.6.6:
|
||||
resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
|
||||
|
||||
|
|
@ -6380,6 +6667,9 @@ packages:
|
|||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
uc.micro@2.1.0:
|
||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||
|
||||
ufo@1.6.3:
|
||||
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
|
||||
|
||||
|
|
@ -7654,22 +7944,22 @@ snapshots:
|
|||
fastq: 1.20.1
|
||||
glob: 13.0.2
|
||||
|
||||
'@floating-ui/core@1.7.4':
|
||||
'@floating-ui/core@1.7.5':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.10
|
||||
'@floating-ui/utils': 0.2.11
|
||||
|
||||
'@floating-ui/dom@1.7.5':
|
||||
'@floating-ui/dom@1.7.6':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.4
|
||||
'@floating-ui/utils': 0.2.10
|
||||
'@floating-ui/core': 1.7.5
|
||||
'@floating-ui/utils': 0.2.11
|
||||
|
||||
'@floating-ui/react-dom@2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.5
|
||||
'@floating-ui/dom': 1.7.6
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
'@floating-ui/utils@0.2.11': {}
|
||||
|
||||
'@gar/promisify@1.1.3': {}
|
||||
|
||||
|
|
@ -8461,6 +8751,8 @@ snapshots:
|
|||
|
||||
'@radix-ui/rect@1.1.1': {}
|
||||
|
||||
'@remirror/core-constants@3.0.0': {}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.55.1':
|
||||
|
|
@ -8565,6 +8857,193 @@ snapshots:
|
|||
|
||||
'@tanstack/virtual-core@3.13.18': {}
|
||||
|
||||
'@tiptap/core@3.20.1(@tiptap/pm@3.20.1)':
|
||||
dependencies:
|
||||
'@tiptap/pm': 3.20.1
|
||||
|
||||
'@tiptap/extension-blockquote@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-bold@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-bubble-menu@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.6
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm': 3.20.1
|
||||
optional: true
|
||||
|
||||
'@tiptap/extension-bullet-list@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm': 3.20.1
|
||||
|
||||
'@tiptap/extension-code@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-document@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-dropcursor@3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-floating-menu@3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.6
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm': 3.20.1
|
||||
optional: true
|
||||
|
||||
'@tiptap/extension-gapcursor@3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-hard-break@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-heading@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-horizontal-rule@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm': 3.20.1
|
||||
|
||||
'@tiptap/extension-italic@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-link@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm': 3.20.1
|
||||
linkifyjs: 4.3.2
|
||||
|
||||
'@tiptap/extension-list-item@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-list-keymap@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm': 3.20.1
|
||||
|
||||
'@tiptap/extension-ordered-list@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-paragraph@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-placeholder@3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-strike@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-text@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extension-underline@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
|
||||
'@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm': 3.20.1
|
||||
|
||||
'@tiptap/markdown@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm': 3.20.1
|
||||
marked: 17.0.4
|
||||
|
||||
'@tiptap/pm@3.20.1':
|
||||
dependencies:
|
||||
prosemirror-changeset: 2.4.0
|
||||
prosemirror-collab: 1.3.1
|
||||
prosemirror-commands: 1.7.1
|
||||
prosemirror-dropcursor: 1.8.2
|
||||
prosemirror-gapcursor: 1.4.1
|
||||
prosemirror-history: 1.5.0
|
||||
prosemirror-inputrules: 1.5.1
|
||||
prosemirror-keymap: 1.2.3
|
||||
prosemirror-markdown: 1.13.4
|
||||
prosemirror-menu: 1.3.0
|
||||
prosemirror-model: 1.25.4
|
||||
prosemirror-schema-basic: 1.2.4
|
||||
prosemirror-schema-list: 1.5.1
|
||||
prosemirror-state: 1.4.4
|
||||
prosemirror-tables: 1.8.5
|
||||
prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6)
|
||||
prosemirror-transform: 1.11.0
|
||||
prosemirror-view: 1.41.6
|
||||
|
||||
'@tiptap/react@3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm': 3.20.1
|
||||
'@types/react': 18.3.27
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.27)
|
||||
'@types/use-sync-external-store': 0.0.6
|
||||
fast-equals: 5.4.0
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
use-sync-external-store: 1.6.0(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@tiptap/extension-bubble-menu': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
'@tiptap/extension-floating-menu': 3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
transitivePeerDependencies:
|
||||
- '@floating-ui/dom'
|
||||
|
||||
'@tiptap/starter-kit@3.20.1':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.1(@tiptap/pm@3.20.1)
|
||||
'@tiptap/extension-blockquote': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-bold': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-bullet-list': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-code': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-code-block': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
'@tiptap/extension-document': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-dropcursor': 3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-gapcursor': 3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-hard-break': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-heading': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-horizontal-rule': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
'@tiptap/extension-italic': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-link': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
'@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
'@tiptap/extension-list-item': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-list-keymap': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-ordered-list': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-paragraph': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-strike': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-text': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extension-underline': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))
|
||||
'@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)
|
||||
'@tiptap/pm': 3.20.1
|
||||
|
||||
'@tokenizer/inflate@0.4.1':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
|
|
@ -8763,10 +9242,19 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/node': 25.0.7
|
||||
|
||||
'@types/linkify-it@5.0.0': {}
|
||||
|
||||
'@types/markdown-it@14.1.2':
|
||||
dependencies:
|
||||
'@types/linkify-it': 5.0.0
|
||||
'@types/mdurl': 2.0.0
|
||||
|
||||
'@types/mdast@4.0.4':
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/mdurl@2.0.0': {}
|
||||
|
||||
'@types/ms@2.1.0': {}
|
||||
|
||||
'@types/node@18.19.130':
|
||||
|
|
@ -8821,6 +9309,8 @@ snapshots:
|
|||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
||||
'@types/use-sync-external-store@0.0.6': {}
|
||||
|
||||
'@types/verror@1.10.11':
|
||||
optional: true
|
||||
|
||||
|
|
@ -10339,6 +10829,8 @@ snapshots:
|
|||
dependencies:
|
||||
once: 1.4.0
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
entities@6.0.1: {}
|
||||
|
||||
env-paths@2.2.1: {}
|
||||
|
|
@ -10850,6 +11342,8 @@ snapshots:
|
|||
|
||||
fast-equals@4.0.3: {}
|
||||
|
||||
fast-equals@5.4.0: {}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
|
|
@ -11865,6 +12359,12 @@ snapshots:
|
|||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
linkify-it@5.0.0:
|
||||
dependencies:
|
||||
uc.micro: 2.1.0
|
||||
|
||||
linkifyjs@4.3.2: {}
|
||||
|
||||
lint-staged@16.2.7:
|
||||
dependencies:
|
||||
commander: 14.0.3
|
||||
|
|
@ -12009,10 +12509,21 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
markdown-it@14.1.1:
|
||||
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-table@3.0.4: {}
|
||||
|
||||
marked@16.4.2: {}
|
||||
|
||||
marked@17.0.4: {}
|
||||
|
||||
matcher@3.0.0:
|
||||
dependencies:
|
||||
escape-string-regexp: 4.0.0
|
||||
|
|
@ -12179,6 +12690,8 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
|
||||
mdurl@2.0.0: {}
|
||||
|
||||
media-typer@1.1.0: {}
|
||||
|
||||
merge-descriptors@2.0.0: {}
|
||||
|
|
@ -12703,6 +13216,8 @@ snapshots:
|
|||
strip-ansi: 6.0.1
|
||||
wcwidth: 1.0.1
|
||||
|
||||
orderedmap@2.1.1: {}
|
||||
|
||||
own-keys@1.0.1:
|
||||
dependencies:
|
||||
get-intrinsic: 1.3.0
|
||||
|
|
@ -12964,6 +13479,109 @@ snapshots:
|
|||
|
||||
property-information@7.1.0: {}
|
||||
|
||||
prosemirror-changeset@2.4.0:
|
||||
dependencies:
|
||||
prosemirror-transform: 1.11.0
|
||||
|
||||
prosemirror-collab@1.3.1:
|
||||
dependencies:
|
||||
prosemirror-state: 1.4.4
|
||||
|
||||
prosemirror-commands@1.7.1:
|
||||
dependencies:
|
||||
prosemirror-model: 1.25.4
|
||||
prosemirror-state: 1.4.4
|
||||
prosemirror-transform: 1.11.0
|
||||
|
||||
prosemirror-dropcursor@1.8.2:
|
||||
dependencies:
|
||||
prosemirror-state: 1.4.4
|
||||
prosemirror-transform: 1.11.0
|
||||
prosemirror-view: 1.41.6
|
||||
|
||||
prosemirror-gapcursor@1.4.1:
|
||||
dependencies:
|
||||
prosemirror-keymap: 1.2.3
|
||||
prosemirror-model: 1.25.4
|
||||
prosemirror-state: 1.4.4
|
||||
prosemirror-view: 1.41.6
|
||||
|
||||
prosemirror-history@1.5.0:
|
||||
dependencies:
|
||||
prosemirror-state: 1.4.4
|
||||
prosemirror-transform: 1.11.0
|
||||
prosemirror-view: 1.41.6
|
||||
rope-sequence: 1.3.4
|
||||
|
||||
prosemirror-inputrules@1.5.1:
|
||||
dependencies:
|
||||
prosemirror-state: 1.4.4
|
||||
prosemirror-transform: 1.11.0
|
||||
|
||||
prosemirror-keymap@1.2.3:
|
||||
dependencies:
|
||||
prosemirror-state: 1.4.4
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
prosemirror-markdown@1.13.4:
|
||||
dependencies:
|
||||
'@types/markdown-it': 14.1.2
|
||||
markdown-it: 14.1.1
|
||||
prosemirror-model: 1.25.4
|
||||
|
||||
prosemirror-menu@1.3.0:
|
||||
dependencies:
|
||||
crelt: 1.0.6
|
||||
prosemirror-commands: 1.7.1
|
||||
prosemirror-history: 1.5.0
|
||||
prosemirror-state: 1.4.4
|
||||
|
||||
prosemirror-model@1.25.4:
|
||||
dependencies:
|
||||
orderedmap: 2.1.1
|
||||
|
||||
prosemirror-schema-basic@1.2.4:
|
||||
dependencies:
|
||||
prosemirror-model: 1.25.4
|
||||
|
||||
prosemirror-schema-list@1.5.1:
|
||||
dependencies:
|
||||
prosemirror-model: 1.25.4
|
||||
prosemirror-state: 1.4.4
|
||||
prosemirror-transform: 1.11.0
|
||||
|
||||
prosemirror-state@1.4.4:
|
||||
dependencies:
|
||||
prosemirror-model: 1.25.4
|
||||
prosemirror-transform: 1.11.0
|
||||
prosemirror-view: 1.41.6
|
||||
|
||||
prosemirror-tables@1.8.5:
|
||||
dependencies:
|
||||
prosemirror-keymap: 1.2.3
|
||||
prosemirror-model: 1.25.4
|
||||
prosemirror-state: 1.4.4
|
||||
prosemirror-transform: 1.11.0
|
||||
prosemirror-view: 1.41.6
|
||||
|
||||
prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6):
|
||||
dependencies:
|
||||
'@remirror/core-constants': 3.0.0
|
||||
escape-string-regexp: 4.0.0
|
||||
prosemirror-model: 1.25.4
|
||||
prosemirror-state: 1.4.4
|
||||
prosemirror-view: 1.41.6
|
||||
|
||||
prosemirror-transform@1.11.0:
|
||||
dependencies:
|
||||
prosemirror-model: 1.25.4
|
||||
|
||||
prosemirror-view@1.41.6:
|
||||
dependencies:
|
||||
prosemirror-model: 1.25.4
|
||||
prosemirror-state: 1.4.4
|
||||
prosemirror-transform: 1.11.0
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
|
|
@ -12976,6 +13594,8 @@ snapshots:
|
|||
end-of-stream: 1.4.5
|
||||
once: 1.4.0
|
||||
|
||||
punycode.js@2.3.1: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qs@6.15.0:
|
||||
|
|
@ -13306,6 +13926,8 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc': 4.55.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
rope-sequence@1.3.4: {}
|
||||
|
||||
roughjs@4.6.6:
|
||||
dependencies:
|
||||
hachure-fill: 0.5.2
|
||||
|
|
@ -14005,6 +14627,8 @@ snapshots:
|
|||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
uc.micro@2.1.0: {}
|
||||
|
||||
ufo@1.6.3: {}
|
||||
|
||||
uglify-js@3.19.3:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {
|
|||
import { ExpandableContent } from '@renderer/components/ui/ExpandableContent';
|
||||
import { Input } from '@renderer/components/ui/input';
|
||||
import { MemberSelect } from '@renderer/components/ui/MemberSelect';
|
||||
import { Textarea } from '@renderer/components/ui/textarea';
|
||||
import { TiptapEditor } from '@renderer/components/ui/tiptap';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
||||
import { getLastReadTimestamp } from '@renderer/services/commentReadStorage';
|
||||
import { useStore } from '@renderer/store';
|
||||
|
|
@ -132,7 +132,6 @@ export const TaskDetailDialog = ({
|
|||
// Inline editing: description
|
||||
const [editingDescription, setEditingDescription] = useState(false);
|
||||
const [descriptionDraft, setDescriptionDraft] = useState('');
|
||||
const [descriptionPreview, setDescriptionPreview] = useState(false);
|
||||
const [savingDescription, setSavingDescription] = useState(false);
|
||||
|
||||
const startEditSubject = useCallback(() => {
|
||||
|
|
@ -160,7 +159,6 @@ export const TaskDetailDialog = ({
|
|||
const startEditDescription = useCallback(() => {
|
||||
if (!currentTask) return;
|
||||
setDescriptionDraft(currentTask.description ?? '');
|
||||
setDescriptionPreview(false);
|
||||
setEditingDescription(true);
|
||||
}, [currentTask]);
|
||||
|
||||
|
|
@ -715,51 +713,16 @@ export const TaskDetailDialog = ({
|
|||
>
|
||||
{editingDescription ? (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className={`flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors ${
|
||||
!descriptionPreview
|
||||
? 'bg-[var(--color-surface-raised)] text-[var(--color-text)]'
|
||||
: 'text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
|
||||
}`}
|
||||
onClick={() => setDescriptionPreview(false)}
|
||||
>
|
||||
<Pencil size={12} />
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors ${
|
||||
descriptionPreview
|
||||
? 'bg-[var(--color-surface-raised)] text-[var(--color-text)]'
|
||||
: 'text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
|
||||
}`}
|
||||
onClick={() => setDescriptionPreview(true)}
|
||||
>
|
||||
<Eye size={12} />
|
||||
Preview
|
||||
</button>
|
||||
</div>
|
||||
{descriptionPreview ? (
|
||||
<div className="max-h-[200px] overflow-y-auto rounded border border-[var(--color-border)] p-2">
|
||||
{descriptionDraft.trim() ? (
|
||||
<MarkdownViewer content={descriptionDraft} maxHeight="max-h-[180px]" />
|
||||
) : (
|
||||
<p className="text-xs text-[var(--color-text-muted)]">Nothing to preview</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Textarea
|
||||
autoFocus
|
||||
value={descriptionDraft}
|
||||
onChange={(e) => setDescriptionDraft(e.target.value)}
|
||||
disabled={savingDescription}
|
||||
rows={6}
|
||||
className="text-xs"
|
||||
placeholder="Task description (supports markdown)"
|
||||
/>
|
||||
)}
|
||||
<TiptapEditor
|
||||
content={descriptionDraft}
|
||||
onChange={setDescriptionDraft}
|
||||
placeholder="Task description (supports markdown)"
|
||||
autoFocus
|
||||
minHeight="120px"
|
||||
maxHeight="200px"
|
||||
toolbar
|
||||
disabled={savingDescription}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
|
|
|
|||
76
src/renderer/components/ui/tiptap/TiptapBubbleMenu.tsx
Normal file
76
src/renderer/components/ui/tiptap/TiptapBubbleMenu.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { useCurrentEditor, useEditorState } from '@tiptap/react';
|
||||
import { BubbleMenu } from '@tiptap/react/menus';
|
||||
import { Bold, Code, Italic, Strikethrough } from 'lucide-react';
|
||||
|
||||
import { cn } from '@renderer/lib/utils';
|
||||
|
||||
export function TiptapBubbleMenu() {
|
||||
const { editor } = useCurrentEditor();
|
||||
|
||||
const state = useEditorState({
|
||||
editor,
|
||||
selector: ({ editor: e }) => {
|
||||
if (!e) return null;
|
||||
return {
|
||||
isBold: e.isActive('bold'),
|
||||
isItalic: e.isActive('italic'),
|
||||
isStrike: e.isActive('strike'),
|
||||
isCode: e.isActive('code'),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
if (!editor || !state) return null;
|
||||
|
||||
const btnClass = (active: boolean) =>
|
||||
cn(
|
||||
'rounded p-1 transition-colors text-[var(--color-text-muted)]',
|
||||
'hover:bg-[var(--color-surface-raised)] hover:text-[var(--color-text-secondary)]',
|
||||
active && 'bg-[var(--color-surface-raised)] text-[var(--color-text)]'
|
||||
);
|
||||
|
||||
return (
|
||||
<BubbleMenu
|
||||
editor={editor}
|
||||
options={{ placement: 'top', offset: 8 }}
|
||||
className={cn(
|
||||
'flex items-center gap-0.5 rounded-lg p-1 shadow-lg',
|
||||
'border border-[var(--color-border-emphasis)]',
|
||||
'bg-[var(--color-surface-overlay)]'
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={btnClass(state.isBold)}
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
aria-label="Bold"
|
||||
>
|
||||
<Bold size={12} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={btnClass(state.isItalic)}
|
||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||
aria-label="Italic"
|
||||
>
|
||||
<Italic size={12} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={btnClass(state.isStrike)}
|
||||
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||
aria-label="Strike"
|
||||
>
|
||||
<Strikethrough size={12} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={btnClass(state.isCode)}
|
||||
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||
aria-label="Code"
|
||||
>
|
||||
<Code size={12} />
|
||||
</button>
|
||||
</BubbleMenu>
|
||||
);
|
||||
}
|
||||
72
src/renderer/components/ui/tiptap/TiptapEditor.tsx
Normal file
72
src/renderer/components/ui/tiptap/TiptapEditor.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { EditorContent, EditorContext } from '@tiptap/react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { cn } from '@renderer/lib/utils';
|
||||
|
||||
import { TiptapBubbleMenu } from './TiptapBubbleMenu';
|
||||
import { TiptapToolbar } from './TiptapToolbar';
|
||||
import type { TiptapEditorProps } from './types';
|
||||
import { useTiptapEditor } from './useTiptapEditor';
|
||||
|
||||
import './tiptapStyles.css';
|
||||
|
||||
export function TiptapEditor({
|
||||
content,
|
||||
onChange,
|
||||
placeholder,
|
||||
editable = true,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
autoFocus = false,
|
||||
toolbar = true,
|
||||
bubbleMenu = true,
|
||||
extensions,
|
||||
className,
|
||||
disabled = false,
|
||||
}: TiptapEditorProps) {
|
||||
const isEditable = editable && !disabled;
|
||||
const { editor } = useTiptapEditor({
|
||||
content,
|
||||
onChange,
|
||||
editable: isEditable,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
extensions,
|
||||
});
|
||||
|
||||
// EditorContext.Provider — v3 паттерн для sharing editor instance
|
||||
// TiptapToolbar и TiptapBubbleMenu получают editor через useCurrentEditor()
|
||||
const providerValue = useMemo(() => ({ editor }), [editor]);
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
const showToolbar = toolbar !== false && isEditable;
|
||||
const showBubble = bubbleMenu && isEditable;
|
||||
const toolbarConfig = typeof toolbar === 'object' ? toolbar : undefined;
|
||||
|
||||
return (
|
||||
<EditorContext.Provider value={providerValue}>
|
||||
<div
|
||||
className={cn(
|
||||
'tiptap-editor-wrapper rounded-md border border-[var(--color-border)] bg-transparent',
|
||||
disabled && 'cursor-not-allowed opacity-50',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{showToolbar && <TiptapToolbar config={toolbarConfig} />}
|
||||
|
||||
<div
|
||||
className="overflow-y-auto px-3 py-2"
|
||||
style={{
|
||||
minHeight: minHeight ?? '60px',
|
||||
maxHeight: maxHeight ?? 'none',
|
||||
}}
|
||||
>
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
|
||||
{showBubble && <TiptapBubbleMenu />}
|
||||
</div>
|
||||
</EditorContext.Provider>
|
||||
);
|
||||
}
|
||||
271
src/renderer/components/ui/tiptap/TiptapToolbar.tsx
Normal file
271
src/renderer/components/ui/tiptap/TiptapToolbar.tsx
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
import { useCurrentEditor, useEditorState } from '@tiptap/react';
|
||||
import {
|
||||
Bold,
|
||||
Code,
|
||||
FileCode2,
|
||||
Heading1,
|
||||
Heading2,
|
||||
Heading3,
|
||||
Italic,
|
||||
List,
|
||||
ListOrdered,
|
||||
Minus,
|
||||
Quote,
|
||||
Redo2,
|
||||
Strikethrough,
|
||||
Undo2,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { cn } from '@renderer/lib/utils';
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
|
||||
|
||||
import type { ToolbarConfig } from './types';
|
||||
|
||||
interface TiptapToolbarProps {
|
||||
config?: ToolbarConfig;
|
||||
}
|
||||
|
||||
function ToolbarButton({
|
||||
icon,
|
||||
active,
|
||||
disabled,
|
||||
onClick,
|
||||
label,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
label: string;
|
||||
}) {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
aria-label={label}
|
||||
aria-pressed={active}
|
||||
className={cn(
|
||||
'rounded p-1.5 transition-colors',
|
||||
'text-[var(--color-text-muted)]',
|
||||
'hover:bg-[var(--color-surface-raised)] hover:text-[var(--color-text-secondary)]',
|
||||
active && 'bg-[var(--color-surface-raised)] text-[var(--color-text)]',
|
||||
disabled &&
|
||||
'cursor-not-allowed opacity-30 hover:bg-transparent hover:text-[var(--color-text-muted)]'
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="text-xs">
|
||||
{label}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
function Divider() {
|
||||
return <div className="mx-0.5 h-4 w-px bg-[var(--color-border)]" />;
|
||||
}
|
||||
|
||||
export function TiptapToolbar({ config }: TiptapToolbarProps) {
|
||||
const { editor } = useCurrentEditor();
|
||||
|
||||
// useEditorState — КРИТИЧНО для v3!
|
||||
// Без этого active state НЕ обновляется (shouldRerenderOnTransaction: false)
|
||||
const state = useEditorState({
|
||||
editor,
|
||||
selector: ({ editor: e }) => {
|
||||
if (!e) return null;
|
||||
return {
|
||||
isBold: e.isActive('bold'),
|
||||
isItalic: e.isActive('italic'),
|
||||
isStrike: e.isActive('strike'),
|
||||
isCode: e.isActive('code'),
|
||||
isCodeBlock: e.isActive('codeBlock'),
|
||||
isBulletList: e.isActive('bulletList'),
|
||||
isOrderedList: e.isActive('orderedList'),
|
||||
isBlockquote: e.isActive('blockquote'),
|
||||
headingLevel:
|
||||
([1, 2, 3] as const).find((l) => e.isActive('heading', { level: l })) ?? 0,
|
||||
canUndo: e.can().undo(),
|
||||
canRedo: e.can().redo(),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
if (!editor || !state) return null;
|
||||
|
||||
const c = {
|
||||
bold: true,
|
||||
italic: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
codeBlock: true,
|
||||
heading: { levels: [1, 2, 3] as (1 | 2 | 3)[] },
|
||||
bulletList: true,
|
||||
orderedList: true,
|
||||
blockquote: true,
|
||||
horizontalRule: true,
|
||||
undoRedo: true,
|
||||
...config,
|
||||
};
|
||||
const headingLevels = c.heading === false ? [] : (c.heading?.levels ?? [1, 2, 3]);
|
||||
|
||||
const groups: React.ReactNode[][] = [];
|
||||
|
||||
// Group 1: Text formatting
|
||||
const textGroup: React.ReactNode[] = [];
|
||||
if (c.bold)
|
||||
textGroup.push(
|
||||
<ToolbarButton
|
||||
key="bold"
|
||||
icon={<Bold size={14} />}
|
||||
active={state.isBold}
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
label="Bold (⌘B)"
|
||||
/>
|
||||
);
|
||||
if (c.italic)
|
||||
textGroup.push(
|
||||
<ToolbarButton
|
||||
key="italic"
|
||||
icon={<Italic size={14} />}
|
||||
active={state.isItalic}
|
||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||
label="Italic (⌘I)"
|
||||
/>
|
||||
);
|
||||
if (c.strike)
|
||||
textGroup.push(
|
||||
<ToolbarButton
|
||||
key="strike"
|
||||
icon={<Strikethrough size={14} />}
|
||||
active={state.isStrike}
|
||||
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||
label="Strikethrough (⌘⇧S)"
|
||||
/>
|
||||
);
|
||||
if (textGroup.length) groups.push(textGroup);
|
||||
|
||||
// Group 2: Code
|
||||
const codeGroup: React.ReactNode[] = [];
|
||||
if (c.code)
|
||||
codeGroup.push(
|
||||
<ToolbarButton
|
||||
key="code"
|
||||
icon={<Code size={14} />}
|
||||
active={state.isCode}
|
||||
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||
label="Code (⌘E)"
|
||||
/>
|
||||
);
|
||||
if (c.codeBlock)
|
||||
codeGroup.push(
|
||||
<ToolbarButton
|
||||
key="codeBlock"
|
||||
icon={<FileCode2 size={14} />}
|
||||
active={state.isCodeBlock}
|
||||
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||
label="Code Block (⌘⌥C)"
|
||||
/>
|
||||
);
|
||||
if (codeGroup.length) groups.push(codeGroup);
|
||||
|
||||
// Group 3: Headings
|
||||
const headingIcons = { 1: Heading1, 2: Heading2, 3: Heading3 } as const;
|
||||
const headingGroup: React.ReactNode[] = headingLevels.map((level) => {
|
||||
const Icon = headingIcons[level];
|
||||
return (
|
||||
<ToolbarButton
|
||||
key={`h${level}`}
|
||||
icon={<Icon size={14} />}
|
||||
active={state.headingLevel === level}
|
||||
onClick={() => editor.chain().focus().toggleHeading({ level }).run()}
|
||||
label={`Heading ${level}`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
if (headingGroup.length) groups.push(headingGroup);
|
||||
|
||||
// Group 4: Lists
|
||||
const listGroup: React.ReactNode[] = [];
|
||||
if (c.bulletList)
|
||||
listGroup.push(
|
||||
<ToolbarButton
|
||||
key="bullet"
|
||||
icon={<List size={14} />}
|
||||
active={state.isBulletList}
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
label="Bullet List (⌘⇧8)"
|
||||
/>
|
||||
);
|
||||
if (c.orderedList)
|
||||
listGroup.push(
|
||||
<ToolbarButton
|
||||
key="ordered"
|
||||
icon={<ListOrdered size={14} />}
|
||||
active={state.isOrderedList}
|
||||
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
label="Ordered List (⌘⇧7)"
|
||||
/>
|
||||
);
|
||||
if (listGroup.length) groups.push(listGroup);
|
||||
|
||||
// Group 5: Blocks
|
||||
const blockGroup: React.ReactNode[] = [];
|
||||
if (c.blockquote)
|
||||
blockGroup.push(
|
||||
<ToolbarButton
|
||||
key="quote"
|
||||
icon={<Quote size={14} />}
|
||||
active={state.isBlockquote}
|
||||
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
||||
label="Blockquote (⌘⇧B)"
|
||||
/>
|
||||
);
|
||||
if (c.horizontalRule)
|
||||
blockGroup.push(
|
||||
<ToolbarButton
|
||||
key="hr"
|
||||
icon={<Minus size={14} />}
|
||||
onClick={() => editor.chain().focus().setHorizontalRule().run()}
|
||||
label="Horizontal Rule"
|
||||
/>
|
||||
);
|
||||
if (blockGroup.length) groups.push(blockGroup);
|
||||
|
||||
// Group 6: Undo/Redo
|
||||
if (c.undoRedo) {
|
||||
groups.push([
|
||||
<ToolbarButton
|
||||
key="undo"
|
||||
icon={<Undo2 size={14} />}
|
||||
disabled={!state.canUndo}
|
||||
onClick={() => editor.chain().focus().undo().run()}
|
||||
label="Undo (⌘Z)"
|
||||
/>,
|
||||
<ToolbarButton
|
||||
key="redo"
|
||||
icon={<Redo2 size={14} />}
|
||||
disabled={!state.canRedo}
|
||||
onClick={() => editor.chain().focus().redo().run()}
|
||||
label="Redo (⌘⇧Z)"
|
||||
/>,
|
||||
]);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-0.5 border-b border-[var(--color-border)] px-1.5 py-1">
|
||||
{groups.map((group, i) => (
|
||||
<div key={i} className="contents">
|
||||
{i > 0 && <Divider />}
|
||||
{group}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
src/renderer/components/ui/tiptap/index.ts
Normal file
3
src/renderer/components/ui/tiptap/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { TiptapEditor } from './TiptapEditor';
|
||||
export type { EditorPreset, TiptapEditorProps, ToolbarConfig } from './types';
|
||||
export { EDITOR_PRESETS } from './presets';
|
||||
46
src/renderer/components/ui/tiptap/presets.ts
Normal file
46
src/renderer/components/ui/tiptap/presets.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import type { TiptapEditorProps } from './types';
|
||||
|
||||
export const EDITOR_PRESETS = {
|
||||
full: {
|
||||
toolbar: true,
|
||||
bubbleMenu: true,
|
||||
minHeight: '120px',
|
||||
maxHeight: '400px',
|
||||
},
|
||||
compact: {
|
||||
toolbar: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
bulletList: true,
|
||||
orderedList: true,
|
||||
undoRedo: true,
|
||||
codeBlock: false,
|
||||
heading: false,
|
||||
blockquote: false,
|
||||
horizontalRule: false,
|
||||
},
|
||||
bubbleMenu: true,
|
||||
minHeight: '60px',
|
||||
maxHeight: '200px',
|
||||
},
|
||||
minimal: {
|
||||
toolbar: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
code: true,
|
||||
strike: false,
|
||||
codeBlock: false,
|
||||
heading: false,
|
||||
bulletList: false,
|
||||
orderedList: false,
|
||||
blockquote: false,
|
||||
horizontalRule: false,
|
||||
undoRedo: false,
|
||||
},
|
||||
bubbleMenu: false,
|
||||
minHeight: '40px',
|
||||
maxHeight: '120px',
|
||||
},
|
||||
} as const satisfies Record<string, Partial<TiptapEditorProps>>;
|
||||
235
src/renderer/components/ui/tiptap/tiptapStyles.css
Normal file
235
src/renderer/components/ui/tiptap/tiptapStyles.css
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/* =============================================================================
|
||||
TiptapEditor — ProseMirror DOM styles
|
||||
Значения извлечены из MarkdownViewer.tsx для pixel-perfect совпадения
|
||||
============================================================================= */
|
||||
|
||||
/* === Placeholder === */
|
||||
.tiptap-editor-wrapper .tiptap p.is-editor-empty:first-child::before {
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem; /* text-sm */
|
||||
}
|
||||
|
||||
/* === Base text === */
|
||||
.tiptap-editor-wrapper .tiptap {
|
||||
outline: none;
|
||||
font-size: 0.875rem; /* text-sm */
|
||||
line-height: 1.625; /* leading-relaxed */
|
||||
color: var(--prose-body);
|
||||
}
|
||||
|
||||
/* === Headings === */
|
||||
.tiptap-editor-wrapper .tiptap h1 {
|
||||
font-size: 1.25rem; /* text-xl */
|
||||
font-weight: 600; /* font-semibold */
|
||||
color: var(--prose-heading);
|
||||
margin-top: 1rem; /* mt-4 */
|
||||
margin-bottom: 0.5rem; /* mb-2 */
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap h2 {
|
||||
font-size: 1.125rem; /* text-lg */
|
||||
font-weight: 600;
|
||||
color: var(--prose-heading);
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap h3 {
|
||||
font-size: 1rem; /* text-base */
|
||||
font-weight: 600;
|
||||
color: var(--prose-heading);
|
||||
margin-top: 0.75rem; /* mt-3 */
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap :is(h1, h2, h3):first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* === Paragraphs === */
|
||||
.tiptap-editor-wrapper .tiptap p {
|
||||
margin-top: 0.5rem; /* my-2 */
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--prose-body);
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* === Strong/Em/Strike === */
|
||||
.tiptap-editor-wrapper .tiptap strong {
|
||||
font-weight: 600;
|
||||
color: var(--prose-heading);
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap s {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* === Inline code === */
|
||||
.tiptap-editor-wrapper .tiptap code {
|
||||
border-radius: 0.25rem; /* rounded */
|
||||
padding: 0.125rem 0.375rem; /* px-1.5 py-0.5 */
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 0.75rem; /* text-xs */
|
||||
background-color: var(--prose-code-bg);
|
||||
color: var(--prose-code-text);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* === Code blocks (pre > code) === */
|
||||
.tiptap-editor-wrapper .tiptap pre {
|
||||
margin-top: 0.75rem; /* my-3 */
|
||||
margin-bottom: 0.75rem;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
border-radius: 0.5rem; /* rounded-lg */
|
||||
padding: 0.75rem; /* p-3 */
|
||||
font-size: 0.75rem; /* text-xs */
|
||||
line-height: 1.625;
|
||||
background-color: var(--prose-pre-bg);
|
||||
border: 1px solid var(--prose-pre-border);
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
font-size: inherit;
|
||||
color: var(--color-text);
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
/* === Blockquote === */
|
||||
.tiptap-editor-wrapper .tiptap blockquote {
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
border-left: 4px solid var(--prose-blockquote-border);
|
||||
padding-left: 1rem; /* pl-4 */
|
||||
font-style: italic;
|
||||
color: var(--prose-muted);
|
||||
}
|
||||
|
||||
/* Reset inner margins at blockquote boundaries */
|
||||
.tiptap-editor-wrapper .tiptap blockquote > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap blockquote > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* === Lists === */
|
||||
.tiptap-editor-wrapper .tiptap ul {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
list-style-type: disc;
|
||||
padding-left: 1.25rem; /* pl-5 */
|
||||
color: var(--prose-body);
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap ol {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
list-style-type: decimal;
|
||||
padding-left: 1.25rem;
|
||||
color: var(--prose-body);
|
||||
}
|
||||
|
||||
/* Nested list bullet styles: disc → circle → square */
|
||||
.tiptap-editor-wrapper .tiptap ul ul {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
/* Nested lists inside li — reduce top margin */
|
||||
.tiptap-editor-wrapper .tiptap li > ul,
|
||||
.tiptap-editor-wrapper .tiptap li > ol {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap li {
|
||||
font-size: 0.875rem;
|
||||
color: var(--prose-body);
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap li + li {
|
||||
margin-top: 0.25rem; /* space-y-1 */
|
||||
}
|
||||
|
||||
/* ProseMirror wraps li content in <p> — reset paragraph margins inside list items */
|
||||
.tiptap-editor-wrapper .tiptap li > p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap li > p + p {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* === Horizontal rule === */
|
||||
.tiptap-editor-wrapper .tiptap hr {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-color: var(--prose-table-border);
|
||||
}
|
||||
|
||||
/* === Links === */
|
||||
.tiptap-editor-wrapper .tiptap a {
|
||||
color: var(--prose-link);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* === Gap cursor — ProseMirror invisible without styles === */
|
||||
.tiptap-editor-wrapper .tiptap .ProseMirror-gapcursor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tiptap-editor-wrapper .tiptap .ProseMirror-gapcursor::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
width: 20px;
|
||||
border-top: 1px solid var(--color-text);
|
||||
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
|
||||
}
|
||||
|
||||
@keyframes ProseMirror-cursor-blink {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* === Selection styles — для readonly mode === */
|
||||
.tiptap-editor-wrapper .tiptap ::selection {
|
||||
background-color: rgba(99, 102, 241, 0.3); /* indigo with opacity */
|
||||
}
|
||||
|
||||
/* === Focus ring для wrapper при фокусе внутри === */
|
||||
.tiptap-editor-wrapper:focus-within {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1px var(--color-border-emphasis);
|
||||
}
|
||||
32
src/renderer/components/ui/tiptap/types.ts
Normal file
32
src/renderer/components/ui/tiptap/types.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import type { Extension } from '@tiptap/react';
|
||||
|
||||
export interface ToolbarConfig {
|
||||
bold?: boolean;
|
||||
italic?: boolean;
|
||||
strike?: boolean;
|
||||
code?: boolean;
|
||||
codeBlock?: boolean;
|
||||
heading?: false | { levels: (1 | 2 | 3)[] };
|
||||
bulletList?: boolean;
|
||||
orderedList?: boolean;
|
||||
blockquote?: boolean;
|
||||
horizontalRule?: boolean;
|
||||
undoRedo?: boolean;
|
||||
}
|
||||
|
||||
export interface TiptapEditorProps {
|
||||
content: string;
|
||||
onChange: (markdown: string) => void;
|
||||
placeholder?: string;
|
||||
editable?: boolean;
|
||||
minHeight?: string;
|
||||
maxHeight?: string;
|
||||
autoFocus?: boolean;
|
||||
toolbar?: boolean | ToolbarConfig;
|
||||
bubbleMenu?: boolean;
|
||||
extensions?: Extension[];
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export type EditorPreset = 'full' | 'compact' | 'minimal';
|
||||
97
src/renderer/components/ui/tiptap/useTiptapEditor.ts
Normal file
97
src/renderer/components/ui/tiptap/useTiptapEditor.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import Placeholder from '@tiptap/extension-placeholder';
|
||||
import { Markdown } from '@tiptap/markdown';
|
||||
import { type Extension, useEditor } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
interface UseTiptapEditorOptions {
|
||||
content: string;
|
||||
onChange?: (markdown: string) => void;
|
||||
editable?: boolean;
|
||||
autoFocus?: boolean;
|
||||
placeholder?: string;
|
||||
extensions?: Extension[];
|
||||
}
|
||||
|
||||
export function useTiptapEditor({
|
||||
content,
|
||||
onChange,
|
||||
editable = true,
|
||||
autoFocus = false,
|
||||
placeholder = '',
|
||||
extensions: extraExtensions = [],
|
||||
}: UseTiptapEditorOptions) {
|
||||
// Ref для стабильной ссылки — избегаем stale closure в onUpdate
|
||||
const onChangeRef = useRef(onChange);
|
||||
useEffect(() => {
|
||||
onChangeRef.current = onChange;
|
||||
}, [onChange]);
|
||||
|
||||
// Double safety: ref guard для programmatic setContent (emitUpdate: false — основной механизм)
|
||||
const isProgrammaticUpdate = useRef(false);
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({}),
|
||||
Markdown.configure({
|
||||
markedOptions: { gfm: true },
|
||||
}),
|
||||
Placeholder.configure({
|
||||
placeholder,
|
||||
showOnlyWhenEditable: true,
|
||||
}),
|
||||
...extraExtensions,
|
||||
],
|
||||
content,
|
||||
contentType: 'markdown',
|
||||
editable,
|
||||
shouldRerenderOnTransaction: false, // v3 performance — toolbar использует useEditorState
|
||||
autofocus: autoFocus ? 'end' : false,
|
||||
enableContentCheck: true,
|
||||
onContentError: ({ error }) => {
|
||||
console.error('[TiptapEditor] Content error:', error);
|
||||
},
|
||||
onUpdate: ({ editor: e }) => {
|
||||
if (isProgrammaticUpdate.current) return;
|
||||
try {
|
||||
const md = e.getMarkdown();
|
||||
onChangeRef.current?.(md);
|
||||
} catch {
|
||||
console.error('[TiptapEditor] getMarkdown() failed, skipping onChange');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// === Content sync ===
|
||||
// Когда внешний content меняется, обновляем editor БЕЗ триггера onUpdate
|
||||
useEffect(() => {
|
||||
if (!editor || editor.isDestroyed) return;
|
||||
|
||||
let currentMd: string;
|
||||
try {
|
||||
currentMd = editor.getMarkdown();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentMd.trim() === content.trim()) return;
|
||||
|
||||
isProgrammaticUpdate.current = true;
|
||||
try {
|
||||
editor.commands.setContent(content, { contentType: 'markdown', emitUpdate: false });
|
||||
} catch {
|
||||
console.error('[TiptapEditor] setContent() failed');
|
||||
} finally {
|
||||
isProgrammaticUpdate.current = false;
|
||||
}
|
||||
}, [content, editor]);
|
||||
|
||||
// === Editable toggle ===
|
||||
useEffect(() => {
|
||||
if (editor && !editor.isDestroyed) {
|
||||
editor.setEditable(editable);
|
||||
}
|
||||
}, [editor, editable]);
|
||||
|
||||
return { editor };
|
||||
}
|
||||
Loading…
Reference in a new issue