diff --git a/package.json b/package.json index cd568f7..cac12fd 100644 --- a/package.json +++ b/package.json @@ -37,16 +37,26 @@ "@replit/codemirror-lang-svelte": "^6.0.0", "@supabase/supabase-js": "^2.100.0", "@tanstack/react-query": "^5.95.2", + "@tiptap/extension-code-block-lowlight": "^2.27.2", + "@tiptap/extension-link": "^2.27.2", + "@tiptap/extension-placeholder": "^2.27.2", + "@tiptap/extension-task-item": "^2.27.2", + "@tiptap/extension-task-list": "^2.27.2", + "@tiptap/pm": "^2.27.2", + "@tiptap/react": "^2.27.2", + "@tiptap/starter-kit": "^2.27.2", "@uiw/codemirror-theme-vscode": "^4.25.9", "@uiw/react-codemirror": "^4.25.8", "clsx": "^2.1.1", "dexie": "^4.3.0", + "lowlight": "^3.3.0", "lucide-react": "^1.6.0", "next": "16.2.1", "prettier": "^3.8.1", "react": "19.2.4", "react-dom": "19.2.4", - "tailwind-merge": "^3.5.0" + "tailwind-merge": "^3.5.0", + "tiptap-markdown": "^0.9.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a05b01..b451e5c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,30 @@ importers: '@tanstack/react-query': specifier: ^5.95.2 version: 5.95.2(react@19.2.4) + '@tiptap/extension-code-block-lowlight': + specifier: ^2.27.2 + version: 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-code-block@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(highlight.js@11.11.1)(lowlight@3.3.0) + '@tiptap/extension-link': + specifier: ^2.27.2 + version: 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-placeholder': + specifier: ^2.27.2 + version: 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-task-item': + specifier: ^2.27.2 + version: 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-task-list': + specifier: ^2.27.2 + version: 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/pm': + specifier: ^2.27.2 + version: 2.27.2 + '@tiptap/react': + specifier: ^2.27.2 + version: 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tiptap/starter-kit': + specifier: ^2.27.2 + version: 2.27.2 '@uiw/codemirror-theme-vscode': specifier: ^4.25.9 version: 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0) @@ -80,6 +104,9 @@ importers: dexie: specifier: ^4.3.0 version: 4.3.0 + lowlight: + specifier: ^3.3.0 + version: 3.3.0 lucide-react: specifier: ^1.6.0 version: 1.6.0(react@19.2.4) @@ -98,6 +125,9 @@ importers: tailwind-merge: specifier: ^3.5.0 version: 3.5.0 + tiptap-markdown: + specifier: ^0.9.0 + version: 0.9.0(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) devDependencies: '@tailwindcss/postcss': specifier: ^4 @@ -1309,6 +1339,9 @@ packages: '@oxc-project/types@0.122.0': resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@poppinss/colors@4.1.6': resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} @@ -1318,6 +1351,9 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@replit/codemirror-lang-svelte@6.0.0': resolution: {integrity: sha512-U2OqqgMM6jKelL0GNWbAmqlu1S078zZNoBqlJBW+retTc5M4Mha6/Y2cf4SVg6ddgloJvmcSpt4hHrVoM4ePRA==} peerDependencies: @@ -1786,6 +1822,169 @@ packages: peerDependencies: react: ^18 || ^19 + '@tiptap/core@2.27.2': + resolution: {integrity: sha512-ABL1N6eoxzDzC1bYvkMbvyexHacszsKdVPYqhl5GwHLOvpZcv9VE9QaKwDILTyz5voCA0lGcAAXZp+qnXOk5lQ==} + peerDependencies: + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-blockquote@2.27.2': + resolution: {integrity: sha512-oIGZgiAeA4tG3YxbTDfrmENL4/CIwGuP3THtHsNhwRqwsl9SfMk58Ucopi2GXTQSdYXpRJ0ahE6nPqB5D6j/Zw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bold@2.27.2': + resolution: {integrity: sha512-bR7J5IwjCGQ0s3CIxyMvOCnMFMzIvsc5OVZKscTN5UkXzFsaY6muUAIqtKxayBUucjtUskm5qZowJITCeCb1/A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bubble-menu@2.27.2': + resolution: {integrity: sha512-VkwlCOcr0abTBGzjPXklJ92FCowG7InU8+Od9FyApdLNmn0utRYGRhw0Zno6VgE9EYr1JY4BRnuSa5f9wlR72w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-bullet-list@2.27.2': + resolution: {integrity: sha512-gmFuKi97u5f8uFc/GQs+zmezjiulZmFiDYTh3trVoLRoc2SAHOjGEB7qxdx7dsqmMN7gwiAWAEVurLKIi1lnnw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-code-block-lowlight@2.27.2': + resolution: {integrity: sha512-v6NKStBbQ/XCc1NnCi3ObsL1DsxadSIBtUQNA/B+urkPgn5LEy72HAGlf0xwjRaNkAGSaTASLKmc84L5q5zlGQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-code-block': ^2.7.0 + '@tiptap/pm': ^2.7.0 + highlight.js: ^11 + lowlight: ^2 || ^3 + + '@tiptap/extension-code-block@2.27.2': + resolution: {integrity: sha512-KgvdQHS4jXr79aU3wZOGBIZYYl9vCB7uDEuRFV4so2rYrfmiYMw3T8bTnlNEEGe4RUeAms1i4fdwwvQp9nR1Dw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-code@2.27.2': + resolution: {integrity: sha512-7X9AgwqiIGXoZX7uvdHQsGsjILnN/JaEVtqfXZnPECzKGaWHeK/Ao4sYvIIIffsyZJA8k5DC7ny2/0sAgr2TuA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-document@2.27.2': + resolution: {integrity: sha512-CFhAYsPnyYnosDC4639sCJnBUnYH4Cat9qH5NZWHVvdgtDwu8GZgZn2eSzaKSYXWH1vJ9DSlCK+7UyC3SNXIBA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-dropcursor@2.27.2': + resolution: {integrity: sha512-oEu/OrktNoQXq1x29NnH/GOIzQZm8ieTQl3FK27nxfBPA89cNoH4mFEUmBL5/OFIENIjiYG3qWpg6voIqzswNw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-floating-menu@2.27.2': + resolution: {integrity: sha512-GUN6gPIGXS7ngRJOwdSmtBRBDt9Kt9CM/9pSwKebhLJ+honFoNA+Y6IpVyDvvDMdVNgBchiJLs6qA5H97gAePQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-gapcursor@2.27.2': + resolution: {integrity: sha512-/c9VF1HBxj+AP54XGVgCmD9bEGYc5w5OofYCFQgM7l7PB1J00A4vOke0oPkHJnqnOOyPlFaxO/7N6l3XwFcnKA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-hard-break@2.27.2': + resolution: {integrity: sha512-kSRVGKlCYK6AGR0h8xRkk0WOFGXHIIndod3GKgWU49APuIGDiXd8sziXsSlniUsWmqgDmDXcNnSzPcV7AQ8YNg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-heading@2.27.2': + resolution: {integrity: sha512-iM3yeRWuuQR/IRQ1djwNooJGfn9Jts9zF43qZIUf+U2NY8IlvdNsk2wTOdBgh6E0CamrStPxYGuln3ZS4fuglw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-history@2.27.2': + resolution: {integrity: sha512-+hSyqERoFNTWPiZx4/FCyZ/0eFqB9fuMdTB4AC/q9iwu3RNWAQtlsJg5230bf/qmyO6bZxRUc0k8p4hrV6ybAw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-horizontal-rule@2.27.2': + resolution: {integrity: sha512-WGWUSgX+jCsbtf9Y9OCUUgRZYuwjVoieW5n6mAUohJ9/6gc6sGIOrUpBShf+HHo6WD+gtQjRd+PssmX3NPWMpg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-italic@2.27.2': + resolution: {integrity: sha512-1OFsw2SZqfaqx5Fa5v90iNlPRcqyt+lVSjBwTDzuPxTPFY4Q0mL89mKgkq2gVHYNCiaRkXvFLDxaSvBWbmthgg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-link@2.27.2': + resolution: {integrity: sha512-bnP61qkr0Kj9Cgnop1hxn2zbOCBzNtmawxr92bVTOE31fJv6FhtCnQiD6tuPQVGMYhcmAj7eihtvuEMFfqEPcQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-list-item@2.27.2': + resolution: {integrity: sha512-eJNee7IEGXMnmygM5SdMGDC8m/lMWmwNGf9fPCK6xk0NxuQRgmZHL6uApKcdH6gyNcRPHCqvTTkhEP7pbny/fg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-ordered-list@2.27.2': + resolution: {integrity: sha512-M7A4tLGJcLPYdLC4CI2Gwl8LOrENQW59u3cMVa+KkwG1hzSJyPsbDpa1DI6oXPC2WtYiTf22zrbq3gVvH+KA2w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-paragraph@2.27.2': + resolution: {integrity: sha512-elYVn2wHJJ+zB9LESENWOAfI4TNT0jqEN34sMA/hCtA4im1ZG2DdLHwkHIshj/c4H0dzQhmsS/YmNC5Vbqab/A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-placeholder@2.27.2': + resolution: {integrity: sha512-IjsgSVYJRjpAKmIoapU0E2R4E2FPY3kpvU7/1i7PUYisylqejSJxmtJPGYw0FOMQY9oxnEEvfZHMBA610tqKpg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-strike@2.27.2': + resolution: {integrity: sha512-HHIjhafLhS2lHgfAsCwC1okqMsQzR4/mkGDm4M583Yftyjri1TNA7lzhzXWRFWiiMfJxKtdjHjUAQaHuteRTZw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-task-item@2.27.2': + resolution: {integrity: sha512-ZBSqj/dygB/Rp5K9qOxRVwASTZCmKVoTq8C59KvMgD/aFjJxhq/w2dZaWkCUEXEep+NmvJqo0kfeAEMY5UDnGg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-task-list@2.27.2': + resolution: {integrity: sha512-5nupAewdzZ9F3599oAcaK0WkDH04wdACAVBPM4zG7InlIpkbho3txB7zWmm64OxfhCMIMGKiXY1q0bw9i0QBGQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text-style@2.27.2': + resolution: {integrity: sha512-Omk+uxjJLyEY69KStpCw5fA9asvV+MGcAX2HOxyISDFoLaL49TMrNjhGAuz09P1L1b0KGXo4ml7Q3v/Lfy4WPA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text@2.27.2': + resolution: {integrity: sha512-Xk7nYcigljAY0GO9hAQpZ65ZCxqOqaAlTPDFcKerXmlkQZP/8ndx95OgUb1Xf63kmPOh3xypurGS2is3v0MXSA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/pm@2.27.2': + resolution: {integrity: sha512-kaEg7BfiJPDQMKbjVIzEPO3wlcA+pZb2tlcK9gPrdDnEFaec2QTF1sXz2ak2IIb2curvnIrQ4yrfHgLlVA72wA==} + + '@tiptap/react@2.27.2': + resolution: {integrity: sha512-0EAs8Cpkfbvben1PZ34JN2Nd79Dhioynm2jML27DBbf1VWPk+FFWFGTMLUT0bu+Np5iVxio8fqV9t0mc4D6thA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.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@2.27.2': + resolution: {integrity: sha512-bb0gJvPoDuyRUQ/iuN52j1//EtWWttw+RXAv1uJxfR0uKf8X7uAqzaOOgwjknoCIDC97+1YHwpGdnRjpDkOBxw==} + '@tsconfig/node18@1.0.3': resolution: {integrity: sha512-RbwvSJQsuN9TB04AQbGULYfOGE/RnSFk/FLQ5b0NmDf5Kx2q/lABZbHQPKCO1vZ6Fiwkplu+yb9pGdLy1iGseQ==} @@ -1801,12 +2000,33 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/linkify-it@3.0.5': + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@13.0.9': + resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdurl@1.0.5': + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/node-fetch@2.6.13': resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} @@ -1824,6 +2044,12 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@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/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -2374,10 +2600,17 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dexie@4.3.0: resolution: {integrity: sha512-5EeoQpJvMKHe6zWt/FSIIuRa3CWlZeIl6zKXt+Lz7BU6RoRRLgX9dZEynRfXrkLcldKYCBiz7xekTEylnie1Ug==} @@ -2424,6 +2657,10 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} @@ -2847,6 +3084,10 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -3156,6 +3397,12 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + linkify-it@5.0.1: + resolution: {integrity: sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==} + + linkifyjs@4.3.3: + resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -3167,6 +3414,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3185,10 +3435,20 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-it-task-lists@2.1.1: + resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} + + markdown-it@14.2.0: + resolution: {integrity: sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + 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'} @@ -3386,6 +3646,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + 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'} @@ -3476,10 +3739,72 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + prosemirror-changeset@2.4.1: + resolution: {integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==} + + 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.2: + resolution: {integrity: sha512-6VgUJTYod0nMBlCaYJGhXGLu7Gt4AvcwcOq0YfJCY/6Uh+3S7UsWhpy6rJFCBFOmonq1hD8KyWOtZhkppd4YPg==} + + prosemirror-model@1.25.9: + resolution: {integrity: sha512-pRTklkDDMMRopyoAcrr9wV/8g/RYgrLHBuJAb5hlEuYZRdm5yqmPjWId83fpBwPpSFqEdja0H7Dfd7z1X/npcA==} + + 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.12.0: + resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==} + + prosemirror-view@1.41.9: + resolution: {integrity: sha512-clTunTX+eaLbr87L1V1QPheRlEQJyTlL3gXe9x3jQIk3rL0RVWxviDGz8tFaydwIVm+hKhYCyr+R/zBtWr9s6A==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + 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'} @@ -3545,6 +3870,9 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -3776,6 +4104,14 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tippy.js@6.3.7: + resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + + tiptap-markdown@0.9.0: + resolution: {integrity: sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ==} + peerDependencies: + '@tiptap/core': ^3.0.1 + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3848,6 +4184,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -3884,6 +4223,11 @@ packages: urlpattern-polyfill@10.1.0: resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -5668,6 +6012,8 @@ snapshots: '@oxc-project/types@0.122.0': {} + '@popperjs/core@2.11.8': {} + '@poppinss/colors@4.1.6': dependencies: kleur: 4.1.5 @@ -5680,6 +6026,8 @@ snapshots: '@poppinss/exception@1.2.3': {} + '@remirror/core-constants@3.0.0': {} + '@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.20.1)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8)': dependencies: '@codemirror/autocomplete': 6.20.1 @@ -6207,6 +6555,188 @@ snapshots: '@tanstack/query-core': 5.95.2 react: 19.2.4 + '@tiptap/core@2.27.2(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-blockquote@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-bold@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-bubble-menu@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + tippy.js: 6.3.7 + + '@tiptap/extension-bullet-list@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-code-block-lowlight@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-code-block@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(highlight.js@11.11.1)(lowlight@3.3.0)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/extension-code-block': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + highlight.js: 11.11.1 + lowlight: 3.3.0 + + '@tiptap/extension-code-block@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-code@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-document@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-dropcursor@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-floating-menu@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + tippy.js: 6.3.7 + + '@tiptap/extension-gapcursor@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-hard-break@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-heading@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-history@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-horizontal-rule@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-italic@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-link@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + linkifyjs: 4.3.3 + + '@tiptap/extension-list-item@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-ordered-list@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-paragraph@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-placeholder@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-strike@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-task-item@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-task-list@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-text-style@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-text@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/pm@2.27.2': + dependencies: + prosemirror-changeset: 2.4.1 + 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.2 + prosemirror-model: 1.25.9 + 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.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9) + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.9 + + '@tiptap/react@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/extension-bubble-menu': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-floating-menu': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + '@types/use-sync-external-store': 0.0.6 + fast-deep-equal: 3.1.3 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.4) + + '@tiptap/starter-kit@2.27.2': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/extension-blockquote': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-bold': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-bullet-list': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-code': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-code-block': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-document': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-dropcursor': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-gapcursor': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-hard-break': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-heading': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-history': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-horizontal-rule': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-italic': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-list-item': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-ordered-list': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-paragraph': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-strike': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-text': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-text-style': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/pm': 2.27.2 + '@tsconfig/node18@1.0.3': {} '@tybys/wasm-util@0.10.1': @@ -6223,10 +6753,32 @@ snapshots: '@types/estree@1.0.8': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} + '@types/linkify-it@3.0.5': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@13.0.9': + dependencies: + '@types/linkify-it': 3.0.5 + '@types/mdurl': 1.0.5 + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdurl@1.0.5': {} + + '@types/mdurl@2.0.0': {} + '@types/node-fetch@2.6.13': dependencies: '@types/node': 20.19.37 @@ -6248,6 +6800,10 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/unist@3.0.3': {} + + '@types/use-sync-external-store@0.0.6': {} + '@types/ws@8.18.1': dependencies: '@types/node': 20.19.37 @@ -6821,8 +7377,14 @@ snapshots: depd@2.0.0: {} + dequal@2.0.3: {} + detect-libc@2.1.2: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + dexie@4.3.0: {} doctrine@2.1.0: @@ -6866,6 +7428,8 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@4.5.0: {} + error-stack-parser-es@1.0.5: {} es-abstract@1.24.1: @@ -7517,6 +8081,8 @@ snapshots: dependencies: hermes-estree: 0.25.1 + highlight.js@11.11.1: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -7790,6 +8356,12 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + linkify-it@5.0.1: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.3: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -7800,6 +8372,12 @@ snapshots: dependencies: js-tokens: 4.0.0 + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.11.1 + lru-cache@10.4.3: {} lru-cache@11.3.3: {} @@ -7816,8 +8394,21 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-it-task-lists@2.1.1: {} + + markdown-it@14.2.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.1 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + math-intrinsics@1.1.0: {} + mdurl@2.0.0: {} + media-typer@1.1.0: {} merge-descriptors@2.0.0: {} @@ -8004,6 +8595,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + orderedmap@2.1.1: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -8080,11 +8673,116 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + prosemirror-changeset@2.4.1: + dependencies: + prosemirror-transform: 1.12.0 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.4 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.9 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.9 + + prosemirror-gapcursor@1.4.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.9 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.9 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.9 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.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.2.0 + prosemirror-model: 1.25.9 + + prosemirror-menu@1.3.2: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.5.0 + prosemirror-state: 1.4.4 + + prosemirror-model@1.25.9: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.9 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.9 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.9 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.9 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.9 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.9 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.9 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.9 + + prosemirror-transform@1.12.0: + dependencies: + prosemirror-model: 1.25.9 + + prosemirror-view@1.41.9: + dependencies: + prosemirror-model: 1.25.9 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 + punycode.js@2.3.1: {} + punycode@2.3.1: {} qs@6.15.1: @@ -8176,6 +8874,8 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' + rope-sequence@1.3.4: {} + router@2.2.0: dependencies: debug: 4.4.3 @@ -8472,6 +9172,18 @@ snapshots: tinyrainbow@3.1.0: {} + tippy.js@6.3.7: + dependencies: + '@popperjs/core': 2.11.8 + + tiptap-markdown@0.9.0(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)): + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@types/markdown-it': 13.0.9 + markdown-it: 14.2.0 + markdown-it-task-lists: 2.1.1 + prosemirror-markdown: 1.13.4 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -8555,6 +9267,8 @@ snapshots: typescript@5.9.3: {} + uc.micro@2.1.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -8610,6 +9324,10 @@ snapshots: urlpattern-polyfill@10.1.0: {} + use-sync-external-store@1.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + vary@1.1.2: {} vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@20.19.37)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.16.9)(yaml@2.8.3)): diff --git a/src/app/globals.css b/src/app/globals.css index c8ce028..8eb5dcd 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -320,3 +320,138 @@ body { .cm-fold-closed:hover { background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='%23cccccc'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.072 8.024L5.715 3.667l.618-.62L11 7.716v.618L6.333 13l-.618-.619 4.357-4.357z'/%3E%3C/svg%3E"); } + +/* ─── Markdown WYSIWYG editor (TipTap / ProseMirror) ───────────────────────── + Notion-like document styling for the rich Markdown view. Built from the theme + "ink" tokens so it flips with light/dark automatically. The editor root carries + the .klipcode-md class (see MarkdownEditorInner). */ +.klipcode-md { + min-height: 62vh; + font-family: var(--font-geist-sans), sans-serif; + font-size: 15px; + line-height: 1.75; + color: rgba(var(--ink-rgb), 0.85); + -webkit-font-smoothing: antialiased; +} +.klipcode-md:focus { outline: none; } +.klipcode-md > :first-child { margin-top: 0; } +.klipcode-md > :last-child { margin-bottom: 0; } + +.klipcode-md h1 { + font-size: 1.7rem; font-weight: 600; line-height: 1.2; letter-spacing: -0.01em; + margin: 1.6rem 0 0.6rem; color: var(--foreground); +} +.klipcode-md h2 { + font-size: 1.35rem; font-weight: 600; line-height: 1.25; margin: 1.5rem 0 0.6rem; + padding-bottom: 0.3rem; border-bottom: 1px solid rgba(var(--ink-rgb), 0.08); + color: var(--foreground); +} +.klipcode-md h3 { font-size: 1.15rem; font-weight: 600; margin: 1.3rem 0 0.4rem; color: var(--foreground); } +.klipcode-md h4 { font-size: 1rem; font-weight: 600; margin: 1.1rem 0 0.3rem; color: var(--foreground); } +.klipcode-md h5 { + font-size: 0.9rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; + margin: 1rem 0 0.3rem; color: rgba(var(--ink-rgb), 0.7); +} +.klipcode-md h6 { + font-size: 0.85rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; + margin: 1rem 0 0.3rem; color: rgba(var(--ink-rgb), 0.5); +} +.klipcode-md p { margin: 0.7rem 0; } +.klipcode-md a { + color: #4aa3ff; text-decoration: underline; + text-decoration-color: rgba(74, 163, 255, 0.35); text-underline-offset: 2px; cursor: pointer; +} +.klipcode-md a:hover { text-decoration-color: #4aa3ff; } +.klipcode-md strong { font-weight: 600; color: var(--foreground); } +.klipcode-md em { font-style: italic; } +.klipcode-md s, .klipcode-md del { color: rgba(var(--ink-rgb), 0.4); } +.klipcode-md ul, .klipcode-md ol { margin: 0.7rem 0; padding-left: 1.5rem; } +.klipcode-md ul { list-style: disc; } +.klipcode-md ol { list-style: decimal; } +.klipcode-md li { margin: 0.2rem 0; } +.klipcode-md li::marker { color: rgba(var(--ink-rgb), 0.4); } +.klipcode-md li > p { margin: 0.2rem 0; } +.klipcode-md blockquote { + margin: 1rem 0; padding-left: 1rem; border-left: 2px solid rgba(var(--ink-rgb), 0.2); + color: rgba(var(--ink-rgb), 0.6); font-style: italic; +} +.klipcode-md hr { margin: 1.5rem 0; border: 0; border-top: 1px solid rgba(var(--ink-rgb), 0.1); } +.klipcode-md code { + font-family: 'Cascadia Code', var(--font-geist-mono), monospace; font-size: 0.85em; + background: rgba(var(--ink-rgb), 0.08); padding: 0.1em 0.4em; border-radius: 4px; color: var(--foreground); +} +.klipcode-md pre { + margin: 1rem 0; padding: 1rem; border-radius: 0.5rem; + border: 1px solid rgba(var(--ink-rgb), 0.08); background: var(--code-surface); overflow-x: auto; +} +.klipcode-md pre code { + background: none; padding: 0; font-size: 13px; line-height: 1.6; color: rgba(var(--ink-rgb), 0.9); +} +.klipcode-md img { max-width: 100%; border-radius: 0.5rem; border: 1px solid rgba(var(--ink-rgb), 0.08); margin: 1rem 0; } +.klipcode-md table { border-collapse: collapse; width: 100%; margin: 1rem 0; font-size: 14px; } +.klipcode-md th, .klipcode-md td { border: 1px solid rgba(var(--ink-rgb), 0.1); padding: 0.4rem 0.6rem; text-align: left; } +.klipcode-md th { background: rgba(var(--ink-rgb), 0.04); font-weight: 600; color: var(--foreground); } + +/* GFM task lists */ +.klipcode-md ul[data-type="taskList"] { list-style: none; padding-left: 0.2rem; } +.klipcode-md ul[data-type="taskList"] li { display: flex; align-items: flex-start; gap: 0.5rem; } +.klipcode-md ul[data-type="taskList"] li > label { margin-top: 0.3rem; user-select: none; } +.klipcode-md ul[data-type="taskList"] li > div { flex: 1 1 auto; min-width: 0; } +.klipcode-md ul[data-type="taskList"] input[type="checkbox"] { accent-color: #4aa3ff; cursor: pointer; } + +/* Placeholder shown in the empty document */ +.klipcode-md p.is-editor-empty:first-child::before { + content: attr(data-placeholder); + color: rgba(var(--ink-rgb), 0.3); + float: left; + height: 0; + pointer-events: none; +} + +/* ─── Code-block syntax highlighting (highlight.js tokens, VS Code Dark+) ──── */ +.klipcode-md .hljs-comment, .klipcode-md .hljs-quote { color: #6a9955; font-style: italic; } +.klipcode-md .hljs-keyword, .klipcode-md .hljs-selector-tag, +.klipcode-md .hljs-literal, .klipcode-md .hljs-meta .hljs-keyword, +.klipcode-md .hljs-doctag, .klipcode-md .hljs-section { color: #569cd6; } +.klipcode-md .hljs-built_in, .klipcode-md .hljs-class .hljs-title, +.klipcode-md .hljs-title.class_, .klipcode-md .hljs-type { color: #4ec9b0; } +.klipcode-md .hljs-string, .klipcode-md .hljs-regexp, +.klipcode-md .hljs-addition, .klipcode-md .hljs-meta .hljs-string { color: #ce9178; } +.klipcode-md .hljs-number { color: #b5cea8; } +.klipcode-md .hljs-title, .klipcode-md .hljs-title.function_, .klipcode-md .hljs-function .hljs-title { color: #dcdcaa; } +.klipcode-md .hljs-attr, .klipcode-md .hljs-attribute, +.klipcode-md .hljs-variable, .klipcode-md .hljs-template-variable, +.klipcode-md .hljs-property, .klipcode-md .hljs-params, .klipcode-md .hljs-selector-attr { color: #9cdcfe; } +.klipcode-md .hljs-name, .klipcode-md .hljs-tag .hljs-name { color: #569cd6; } +.klipcode-md .hljs-symbol, .klipcode-md .hljs-bullet, .klipcode-md .hljs-link, +.klipcode-md .hljs-meta, .klipcode-md .hljs-selector-id, .klipcode-md .hljs-selector-class { color: #d7ba7d; } +.klipcode-md .hljs-deletion { color: #ce9178; } +.klipcode-md .hljs-emphasis { font-style: italic; } +.klipcode-md .hljs-strong { font-weight: 600; } + +/* Light-theme token overrides (VS Code Light+), for legibility on the white surface. */ +:root[data-theme="light"] .klipcode-md .hljs-comment, +:root[data-theme="light"] .klipcode-md .hljs-quote { color: #008000; } +:root[data-theme="light"] .klipcode-md .hljs-keyword, +:root[data-theme="light"] .klipcode-md .hljs-selector-tag, +:root[data-theme="light"] .klipcode-md .hljs-literal, +:root[data-theme="light"] .klipcode-md .hljs-meta .hljs-keyword, +:root[data-theme="light"] .klipcode-md .hljs-doctag, +:root[data-theme="light"] .klipcode-md .hljs-section, +:root[data-theme="light"] .klipcode-md .hljs-name, +:root[data-theme="light"] .klipcode-md .hljs-tag .hljs-name { color: #0000ff; } +:root[data-theme="light"] .klipcode-md .hljs-built_in, +:root[data-theme="light"] .klipcode-md .hljs-title.class_, +:root[data-theme="light"] .klipcode-md .hljs-type { color: #267f99; } +:root[data-theme="light"] .klipcode-md .hljs-string, +:root[data-theme="light"] .klipcode-md .hljs-regexp, +:root[data-theme="light"] .klipcode-md .hljs-addition { color: #a31515; } +:root[data-theme="light"] .klipcode-md .hljs-number { color: #098658; } +:root[data-theme="light"] .klipcode-md .hljs-title, +:root[data-theme="light"] .klipcode-md .hljs-title.function_ { color: #795e26; } +:root[data-theme="light"] .klipcode-md .hljs-attr, +:root[data-theme="light"] .klipcode-md .hljs-attribute, +:root[data-theme="light"] .klipcode-md .hljs-variable, +:root[data-theme="light"] .klipcode-md .hljs-template-variable, +:root[data-theme="light"] .klipcode-md .hljs-property, +:root[data-theme="light"] .klipcode-md .hljs-params { color: #001080; } diff --git a/src/components/KlipCodeApp.tsx b/src/components/KlipCodeApp.tsx index e735fe9..44816ad 100644 --- a/src/components/KlipCodeApp.tsx +++ b/src/components/KlipCodeApp.tsx @@ -373,6 +373,7 @@ export default function KlipCodeApp({ locale }: { locale: "en" | "es" }) { navigate(`${base}?folder=${selectedSnippetTrashed ? TRASH_ROOT_ID : SPACE_ROOT_ID}`) } onUpdate={mutations.handleUpdateSnippet} + markdownPreviewByDefault={preferences.markdownPreviewByDefault} menuButton={menuButton} readOnly={selectedSnippetTrashed} trashActions={ diff --git a/src/components/MarkdownPreview/MarkdownEditor.tsx b/src/components/MarkdownPreview/MarkdownEditor.tsx new file mode 100644 index 0000000..58083c8 --- /dev/null +++ b/src/components/MarkdownPreview/MarkdownEditor.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { lazy, Suspense } from "react"; + +// TipTap + ProseMirror + lowlight are sizeable; only load the editor when a +// Markdown snippet is actually opened in the WYSIWYG view. +const MarkdownEditorInner = lazy(() => import("./MarkdownEditorInner")); + +export interface MarkdownEditorCopy { + /** Shown in the empty editor before the user types. */ + placeholder: string; +} + +export interface MarkdownEditorProps { + /** The Markdown source. Consumed once on mount; the editor owns it afterwards. */ + value: string; + /** Called with the serialized Markdown on every edit. */ + onChange: (markdown: string) => void; + /** When false the document is read-only (e.g. a trashed snippet). */ + editable: boolean; + copy: MarkdownEditorCopy; +} + +/** + * Notion-like WYSIWYG editing of a Markdown snippet. Drops in where the code + * Editor would go; the breadcrumbs and aside stay untouched — only the editing + * surface is swapped. Edits serialize back to Markdown so the snippet stays a + * plain `.md` document. + */ +export function MarkdownEditor(props: MarkdownEditorProps) { + return ( + +
+
+ } + > + +
+ ); +} diff --git a/src/components/MarkdownPreview/MarkdownEditorInner.tsx b/src/components/MarkdownPreview/MarkdownEditorInner.tsx new file mode 100644 index 0000000..38a463b --- /dev/null +++ b/src/components/MarkdownPreview/MarkdownEditorInner.tsx @@ -0,0 +1,184 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import { useEditor, EditorContent, BubbleMenu, type Editor } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import Link from "@tiptap/extension-link"; +import Placeholder from "@tiptap/extension-placeholder"; +import TaskList from "@tiptap/extension-task-list"; +import TaskItem from "@tiptap/extension-task-item"; +import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; +import { common, createLowlight } from "lowlight"; +import { Markdown } from "tiptap-markdown"; +import { + Bold, + Italic, + Strikethrough, + Code, + Heading1, + Heading2, + List, + Quote, + Link as LinkIcon, +} from "lucide-react"; + +import type { MarkdownEditorCopy } from "./MarkdownEditor"; + +// Syntax highlighting inside fenced code blocks. `common` bundles ~35 popular +// grammars (js, ts, python, css, html, json, bash, …) — enough for snippets, +// without pulling in every highlight.js language. +const lowlight = createLowlight(common); + +// ────────────────────────────────────────────────────────────────────────────── +// Floating formatting toolbar (Notion-style) shown over a text selection. +// ────────────────────────────────────────────────────────────────────────────── + +function BubbleButton({ + onClick, + active, + label, + children, +}: { + onClick: () => void; + active?: boolean; + label: string; + children: React.ReactNode; +}) { + return ( + + ); +} + +function FormattingMenu({ editor }: { editor: Editor }) { + const promptLink = () => { + const prev = editor.getAttributes("link").href as string | undefined; + const url = window.prompt("URL", prev ?? "https://"); + if (url === null) return; + if (url === "") { + editor.chain().focus().extendMarkRange("link").unsetLink().run(); + return; + } + editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run(); + }; + + return ( + + editor.chain().focus().toggleBold().run()}> + + + editor.chain().focus().toggleItalic().run()}> + + + editor.chain().focus().toggleStrike().run()}> + + + editor.chain().focus().toggleCode().run()}> + + +
+ editor.chain().focus().toggleHeading({ level: 1 }).run()}> + + + editor.chain().focus().toggleHeading({ level: 2 }).run()}> + + + editor.chain().focus().toggleBulletList().run()}> + + + editor.chain().focus().toggleBlockquote().run()}> + + + + + + + ); +} + +// ────────────────────────────────────────────────────────────────────────────── +// Editor +// ────────────────────────────────────────────────────────────────────────────── + +export interface MarkdownEditorInnerProps { + value: string; + onChange: (markdown: string) => void; + editable: boolean; + copy: MarkdownEditorCopy; +} + +export default function MarkdownEditorInner({ + value, + onChange, + editable, + copy, +}: MarkdownEditorInnerProps) { + // Keep the latest onChange without re-creating the editor instance. + const onChangeRef = useRef(onChange); + useEffect(() => { + onChangeRef.current = onChange; + }, [onChange]); + + const editor = useEditor({ + // The editor renders only on the client (lazy boundary) — avoid SSR markup. + immediatelyRender: false, + editable, + extensions: [ + StarterKit.configure({ codeBlock: false }), + CodeBlockLowlight.configure({ lowlight, defaultLanguage: "plaintext" }), + Link.configure({ openOnClick: false, autolink: true, HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" } }), + TaskList, + TaskItem.configure({ nested: true }), + Placeholder.configure({ placeholder: copy.placeholder }), + Markdown.configure({ html: false, tightLists: true, transformPastedText: true, transformCopiedText: true }), + ], + content: value, + editorProps: { + attributes: { + class: "klipcode-md focus:outline-none", + spellcheck: "false", + }, + }, + onUpdate: ({ editor }) => { + onChangeRef.current(editor.storage.markdown.getMarkdown()); + }, + }); + + // Reflect read-only state (e.g. a trashed snippet) without rebuilding the doc. + useEffect(() => { + editor?.setEditable(editable); + }, [editor, editable]); + + return ( +
{ + if (e.target === e.currentTarget && editor) { + e.preventDefault(); + editor.chain().focus("end").run(); + } + }} + > +
+ {editor && editable && } + +
+
+ ); +} diff --git a/src/components/PreferencesDialog/PreferencesDialog.tsx b/src/components/PreferencesDialog/PreferencesDialog.tsx index d692b97..149ac88 100644 --- a/src/components/PreferencesDialog/PreferencesDialog.tsx +++ b/src/components/PreferencesDialog/PreferencesDialog.tsx @@ -203,6 +203,24 @@ export function PreferencesDialog({ /> } /> + + {/* Markdown preview by default */} + + value={preferences.markdownPreviewByDefault ? "on" : "off"} + onChange={(value) => + onChangePreferences({ markdownPreviewByDefault: value === "on" }) + } + options={[ + { value: "on", label: t.markdownPreview.on }, + { value: "off", label: t.markdownPreview.off }, + ]} + /> + } + />
, diff --git a/src/components/SnippetEditor/SnippetEditor.tsx b/src/components/SnippetEditor/SnippetEditor.tsx index a75c584..1d2ce3f 100644 --- a/src/components/SnippetEditor/SnippetEditor.tsx +++ b/src/components/SnippetEditor/SnippetEditor.tsx @@ -13,11 +13,14 @@ import { FolderOpen, Layers, Zap, + Eye, + Code2, RotateCcw, Trash2, } from "lucide-react"; import { Editor } from "@/components/Editor/Editor"; +import { MarkdownEditor } from "@/components/MarkdownPreview/MarkdownEditor"; import { Breadcrumbs, type BreadcrumbItem } from "@/components/Breadcrumbs/Breadcrumbs"; import { LanguageSelect } from "@/ui/LanguageSelect"; import { LanguageIcon } from "@/ui/LanguageIcon"; @@ -100,6 +103,8 @@ export interface SnippetEditorProps { onNavigateFolder?: (folderId: string) => void; onNavigateHome?: () => void; onUpdate: (snippetId: string, changes: { title?: string; code?: string; language?: LanguageId }) => void; + /** Whether Markdown snippets open in the Notion-like preview by default. */ + markdownPreviewByDefault?: boolean; menuButton?: React.ReactNode; /** When true the snippet is in the trash: it's shown read-only with a notice * and restore / delete-permanently actions instead of the edit controls. */ @@ -120,16 +125,22 @@ export function SnippetEditor({ onNavigateFolder, onNavigateHome, onUpdate, + markdownPreviewByDefault = true, menuButton, readOnly = false, trashActions, }: SnippetEditorProps) { const editorCopy = copy.snippetEditor; + const isMarkdown = snippet.language === "markdown"; + // Local state — initialised from snippet once (key={snippet.id} resets on swap) const [code, setCode] = useState(snippet.code); const [copied, setCopied] = useState(false); const [formatting, setFormatting] = useState(false); + // Markdown snippets can swap the code editor for a Notion-like rendered preview; + // the initial side honours the user's preference (key={snippet.id} re-seeds it). + const [showPreview, setShowPreview] = useState(isMarkdown && markdownPreviewByDefault); // Per-field debounce timers const codeTimerRef = useRef | null>(null); @@ -239,8 +250,27 @@ export function SnippetEditor({ const isFormattable = snippet.language in PRETTIER_PARSERS; + // Markdown-only toggle between the rendered preview and the raw source editor. + const previewToggle = isMarkdown ? ( + + + + ) : null; + const breadcrumbActions = readOnly ? ( <> + {previewToggle} + {previewToggle}