diff --git a/package.json b/package.json index 77d73a7..2c6fe92 100644 --- a/package.json +++ b/package.json @@ -40,14 +40,17 @@ "mermaid": "^11.12.0", "next": "15.5.3", "next-auth": "^4.24.11", + "prism-react-renderer": "^2.4.1", "prisma": "^6.16.1", "react": "19.1.0", "react-dom": "19.1.0", "react-markdown": "^10.1.0", "recharts": "^3.2.1", - "rehype-highlight": "^7.0.2", + "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", + "rehype-slug": "^6.0.0", "remark-gfm": "^4.0.1", + "remark-toc": "^9.0.0", "tailwind-merge": "^3.3.1", "twemoji": "^14.0.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8ef695..d18b245 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: next-auth: specifier: ^4.24.11 version: 4.24.11(next@15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + prism-react-renderer: + specifier: ^2.4.1 + version: 2.4.1(react@19.1.0) prisma: specifier: ^6.16.1 version: 6.17.1(typescript@5.9.3) @@ -68,15 +71,21 @@ importers: recharts: specifier: ^3.2.1 version: 3.2.1(@types/react@19.2.2)(react-dom@19.1.0(react@19.1.0))(react-is@16.13.1)(react@19.1.0)(redux@5.0.1) - rehype-highlight: - specifier: ^7.0.2 - version: 7.0.2 + rehype-raw: + specifier: ^7.0.0 + version: 7.0.0 rehype-sanitize: specifier: ^6.0.0 version: 6.0.0 + rehype-slug: + specifier: ^6.0.0 + version: 6.0.0 remark-gfm: specifier: ^4.0.1 version: 4.0.1 + remark-toc: + specifier: ^9.0.0 + version: 9.0.0 tailwind-merge: specifier: ^3.3.1 version: 3.3.1 @@ -1016,6 +1025,9 @@ packages: '@types/node@20.19.21': resolution: {integrity: sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==} + '@types/prismjs@1.26.5': + resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + '@types/react-dom@19.2.2': resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} peerDependencies: @@ -1027,6 +1039,9 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/ungap__structured-clone@1.2.0': + resolution: {integrity: sha512-ZoaihZNLeZSxESbk9PUAPZOlSpcKx81I1+4emtULDVmBLkYutTcMlCj2K9VNlf9EWODxdO6gkAqEaLorXwZQVA==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1709,6 +1724,10 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -2007,6 +2026,9 @@ packages: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2067,8 +2089,17 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hast-util-is-element@3.0.0: - resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-heading-rank@3.0.0: + resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} hast-util-sanitize@5.0.2: resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} @@ -2076,19 +2107,24 @@ packages: hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} - hast-util-to-text@4.0.2: - resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + hast-util-to-parse5@8.0.0: + resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - highlight.js@11.11.1: - resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} - engines: {node: '>=12.0.0'} + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -2457,9 +2493,6 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - lowlight@3.3.0: - resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} - lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -2529,6 +2562,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdast-util-toc@7.1.0: + resolution: {integrity: sha512-2TVKotOQzqdY7THOdn2gGzS9d1Sdd66bvxUyw3aNpWfcPXCLYSJCCgfPy30sEtuzkDraJgqF35dzgmz6xlvH/w==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -2805,6 +2841,9 @@ packages: parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-data-parser@0.1.0: resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} @@ -2889,6 +2928,11 @@ packages: pretty-format@3.8.0: resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + prism-react-renderer@2.4.1: + resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==} + peerDependencies: + react: '>=16.0.0' + prisma@6.17.1: resolution: {integrity: sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==} engines: {node: '>=18.18'} @@ -2902,6 +2946,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} @@ -2979,12 +3026,15 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} - rehype-highlight@7.0.2: - resolution: {integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==} + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} rehype-sanitize@6.0.0: resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} + rehype-slug@6.0.0: + resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} + remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} @@ -2997,6 +3047,9 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + remark-toc@9.0.0: + resolution: {integrity: sha512-KJ9txbo33GjDAV1baHFze7ij4G8c7SGYoY8Kzsm2gzFpbhL/bSoVpMMzGa3vrNDSWASNd/3ppAqL7cP2zD6JIA==} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -3319,9 +3372,6 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - unist-util-find-after@5.0.0: - resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} - unist-util-is@6.0.0: resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} @@ -3360,6 +3410,9 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -3393,6 +3446,9 @@ packages: resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} engines: {node: 20 || >=22} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -4194,6 +4250,8 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/prismjs@1.26.5': {} + '@types/react-dom@19.2.2(@types/react@19.2.2)': dependencies: '@types/react': 19.2.2 @@ -4205,6 +4263,8 @@ snapshots: '@types/trusted-types@2.0.7': optional: true + '@types/ungap__structured-clone@1.2.0': {} + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -4912,6 +4972,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + entities@6.0.1: {} + environment@1.1.0: {} es-abstract@1.24.0: @@ -5398,6 +5460,8 @@ snapshots: nypm: 0.6.2 pathe: 2.0.3 + github-slugger@2.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -5445,9 +5509,40 @@ snapshots: dependencies: function-bind: 1.1.2 - hast-util-is-element@3.0.0: + hast-util-from-parse5@8.0.3: dependencies: '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-heading-rank@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 hast-util-sanitize@5.0.2: dependencies: @@ -5475,21 +5570,36 @@ snapshots: transitivePeerDependencies: - supports-color - hast-util-to-text@4.0.2: + hast-util-to-parse5@8.0.0: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-string@3.0.1: dependencies: '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - hast-util-is-element: 3.0.0 - unist-util-find-after: 5.0.0 hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.4 - highlight.js@11.11.1: {} + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 html-url-attributes@3.0.1: {} + html-void-elements@3.0.0: {} + human-signals@5.0.0: {} husky@9.1.7: {} @@ -5857,12 +5967,6 @@ 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@6.0.0: dependencies: yallist: 4.0.0 @@ -6034,6 +6138,16 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdast-util-toc@7.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/ungap__structured-clone': 1.2.0 + '@ungap/structured-clone': 1.3.0 + github-slugger: 2.0.0 + mdast-util-to-string: 4.0.0 + unist-util-is: 6.0.0 + unist-util-visit: 5.0.0 + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -6472,6 +6586,10 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-data-parser@0.1.0: {} path-exists@4.0.0: {} @@ -6540,6 +6658,12 @@ snapshots: pretty-format@3.8.0: {} + prism-react-renderer@2.4.1(react@19.1.0): + dependencies: + '@types/prismjs': 1.26.5 + clsx: 2.1.1 + react: 19.1.0 + prisma@6.17.1(typescript@5.9.3): dependencies: '@prisma/config': 6.17.1 @@ -6555,6 +6679,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-information@6.5.0: {} + property-information@7.1.0: {} punycode@2.3.1: {} @@ -6654,12 +6780,10 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 - rehype-highlight@7.0.2: + rehype-raw@7.0.0: dependencies: '@types/hast': 3.0.4 - hast-util-to-text: 4.0.2 - lowlight: 3.3.0 - unist-util-visit: 5.0.0 + hast-util-raw: 9.1.0 vfile: 6.0.3 rehype-sanitize@6.0.0: @@ -6667,6 +6791,14 @@ snapshots: '@types/hast': 3.0.4 hast-util-sanitize: 5.0.2 + rehype-slug@6.0.0: + dependencies: + '@types/hast': 3.0.4 + github-slugger: 2.0.0 + hast-util-heading-rank: 3.0.0 + hast-util-to-string: 3.0.1 + unist-util-visit: 5.0.0 + remark-gfm@4.0.1: dependencies: '@types/mdast': 4.0.4 @@ -6701,6 +6833,11 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + remark-toc@9.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-toc: 7.1.0 + reselect@5.1.1: {} resolve-from@4.0.0: {} @@ -7099,11 +7236,6 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 - unist-util-find-after@5.0.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - unist-util-is@6.0.0: dependencies: '@types/unist': 3.0.3 @@ -7165,6 +7297,11 @@ snapshots: uuid@8.3.2: {} + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -7211,6 +7348,8 @@ snapshots: walk-up-path@4.0.0: {} + web-namespaces@2.0.1: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 diff --git a/src/components/notes/MarkdownEditor.tsx b/src/components/notes/MarkdownEditor.tsx index a51a7cf..9c51d67 100644 --- a/src/components/notes/MarkdownEditor.tsx +++ b/src/components/notes/MarkdownEditor.tsx @@ -4,14 +4,274 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import React from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; -import rehypeHighlight from 'rehype-highlight'; +import remarkToc from 'remark-toc'; +import rehypeRaw from 'rehype-raw'; +import rehypeSlug from 'rehype-slug'; import rehypeSanitize from 'rehype-sanitize'; +import { Highlight, themes } from 'prism-react-renderer'; import { Eye, EyeOff, Edit3, X, CheckSquare2 } from 'lucide-react'; import { TagInput } from '@/components/ui/TagInput'; import { TagDisplay } from '@/components/ui/TagDisplay'; import { TaskSelectorWithData } from '@/components/shared/TaskSelectorWithData'; import { MermaidRenderer } from '@/components/ui/MermaidRenderer'; import { Tag, Task } from '@/lib/types'; +import type { Components } from 'react-markdown'; + +// Fonction pour générer les composants Markdown réutilisables +const createMarkdownComponents = ( + isPreviewMode: boolean +): Partial => ({ + // Titres avec tailles différentes selon le mode + h1: ({ children }: { children?: React.ReactNode }) => ( +

+ {children} +

+ ), + h2: ({ children }: { children?: React.ReactNode }) => ( +

+ {children} +

+ ), + h3: ({ children }: { children?: React.ReactNode }) => ( +

+ {children} +

+ ), + h4: ({ children }: { children?: React.ReactNode }) => ( +

+ {children} +

+ ), + h5: ({ children }: { children?: React.ReactNode }) => ( +
+ {children} +
+ ), + h6: ({ children }: { children?: React.ReactNode }) => ( +
+ {children} +
+ ), + p: ({ children }: { children?: React.ReactNode }) => ( +

{children}

+ ), + // Listes avec le même style partout + ul: ({ children }: { children?: React.ReactNode }) => ( + + ), + ol: ({ children }: { children?: React.ReactNode }) => ( +
    {children}
+ ), + li: ({ + children, + className, + }: { + children?: React.ReactNode; + className?: string; + }) => { + const isTaskListItem = className?.includes('task-list-item'); + + if (isTaskListItem) { + return ( +
  • {children}
  • + ); + } + + return ( +
  • + + {children} +
  • + ); + }, + blockquote: ({ children }: { children?: React.ReactNode }) => ( +
    +
    {children}
    +
    + ), + code: (({ + inline, + className, + children, + ...props + }: { + inline?: boolean; + className?: string; + children?: React.ReactNode; + } & Record) => { + if (inline) { + return ( + + {children} + + ); + } + return ( + + {children} + + ); + }) as Components['code'], + pre: ({ children }: { children?: React.ReactNode }) => { + let codeElement: string | null = null; + let className = ''; + + if (React.isValidElement(children)) { + const props = children.props as { + children?: string; + className?: string; + }; + codeElement = props?.children || null; + className = props?.className || ''; + } + + const isMermaid = + className.includes('language-mermaid') || + (typeof codeElement === 'string' && + (codeElement.trim().startsWith('graph') || + codeElement.trim().startsWith('flowchart') || + codeElement.trim().startsWith('sequenceDiagram') || + codeElement.trim().startsWith('gantt') || + codeElement.trim().startsWith('pie') || + codeElement.trim().startsWith('gitgraph') || + codeElement.trim().startsWith('journey') || + codeElement.trim().startsWith('stateDiagram') || + codeElement.trim().startsWith('classDiagram') || + codeElement.trim().startsWith('erDiagram') || + codeElement.trim().startsWith('mindmap') || + codeElement.trim().startsWith('timeline'))); + + if (isMermaid && typeof codeElement === 'string') { + return ( +
    + +
    + ); + } + + const match = /language-(\w+)/.exec(className || ''); + const language = match ? match[1] : 'javascript'; + + return ( + + {({ + className: highlightClassName, + style, + tokens, + getLineProps, + getTokenProps, + }) => { + // Supprimer les lignes vides à la fin + const filteredTokens = tokens.filter((line, i) => { + // Garder toutes les lignes sauf la dernière si elle est vide + if (i === tokens.length - 1) { + return ( + line.length > 0 && + line.some((token) => token.content.trim() !== '') + ); + } + return true; + }); + + return ( +
    +              
    +                {filteredTokens.map((line, i) => (
    +                  
    + {line.map((token, key) => ( + + ))} +
    + ))} +
    +
    + ); + }} +
    + ); + }, + a: ({ children, href }: { children?: React.ReactNode; href?: string }) => ( + + {children} + + ), + table: ({ children }: { children?: React.ReactNode }) => ( +
    + + {children} +
    +
    + ), + thead: ({ children }: { children?: React.ReactNode }) => ( + {children} + ), + tbody: ({ children }: { children?: React.ReactNode }) => ( + {children} + ), + tr: ({ children }: { children?: React.ReactNode }) => ( + {children} + ), + th: ({ children }: { children?: React.ReactNode }) => ( + + {children} + + ), + td: ({ children }: { children?: React.ReactNode }) => ( + {children} + ), + hr: () =>
    , +}); interface MarkdownEditorProps { value: string; @@ -444,161 +704,18 @@ export function MarkdownEditor({
    ( -

    - {children} -

    - ), - h2: ({ children }) => ( -

    - {children} -

    - ), - h3: ({ children }) => ( -

    - - {children} -

    - ), - h4: ({ children }) => ( -

    - {children} -

    - ), - h5: ({ children }) => ( -
    - {children} -
    - ), - h6: ({ children }) => ( -
    - {children} -
    - ), - p: ({ children }) => ( -

    - {children} -

    - ), - ul: ({ children }) => ( -
      {children}
    - ), - ol: ({ children }) => ( -
      - {children} -
    - ), - li: ({ children }) => ( -
  • - - {children} -
  • - ), - blockquote: ({ children }) => ( -
    -
    - {children} -
    -
    - ), - code: ({ children, className }) => { - const isInline = !className; - if (isInline) { - return ( - - {children} - - ); - } - return {children}; - }, - pre: ({ children }) => { - // Check if this is a mermaid code block - let codeElement: string | null = null; - let className = ''; - - if (React.isValidElement(children)) { - const props = children.props as { - children?: string; - className?: string; - }; - codeElement = props?.children || null; - className = props?.className || ''; - } - - const isMermaid = - className.includes('language-mermaid') || - (typeof codeElement === 'string' && - (codeElement.trim().startsWith('graph') || - codeElement.trim().startsWith('flowchart') || - codeElement.trim().startsWith('sequenceDiagram') || - codeElement.trim().startsWith('gantt') || - codeElement.trim().startsWith('pie') || - codeElement.trim().startsWith('gitgraph') || - codeElement.trim().startsWith('journey') || - codeElement.trim().startsWith('stateDiagram') || - codeElement.trim().startsWith('classDiagram') || - codeElement.trim().startsWith('erDiagram') || - codeElement.trim().startsWith('mindmap') || - codeElement.trim().startsWith('timeline'))); - - if (isMermaid && typeof codeElement === 'string') { - return ( -
    - -
    - ); - } - - return ( -
    -                          {children}
    -                        
    - ); - }, - a: ({ children, href }) => ( - - {children} - - ), - table: ({ children }) => ( -
    - {children}
    -
    - ), - th: ({ children }) => ( - - {children} - - ), - td: ({ children }) => ( - - {children} - - ), - hr: () => ( -
    - ), - strong: ({ children }) => ( - - {children} - - ), - em: ({ children }) => ( - - {children} - - ), - }} + remarkPlugins={[ + remarkGfm, + [ + remarkToc, + { + heading: '(table[ -]of[ -])?contents?|toc|sommaire', + tight: true, + }, + ], + ]} + rehypePlugins={[rehypeRaw, rehypeSlug, rehypeSanitize]} + components={createMarkdownComponents(true)} > {value || "*Commencez à écrire pour voir l'aperçu...*"}
    @@ -670,163 +787,18 @@ export function MarkdownEditor({
    ( -

    - {children} -

    - ), - h2: ({ children }) => ( -

    - {children} -

    - ), - h3: ({ children }) => ( -

    - - {children} -

    - ), - h4: ({ children }) => ( -

    - {children} -

    - ), - h5: ({ children }) => ( -
    - {children} -
    - ), - h6: ({ children }) => ( -
    - {children} -
    - ), - p: ({ children }) => ( -

    - {children} -

    - ), - ul: ({ children }) => ( -
      {children}
    - ), - ol: ({ children }) => ( -
      - {children} -
    - ), - li: ({ children }) => ( -
  • - - {children} -
  • - ), - blockquote: ({ children }) => ( -
    -
    - {children} -
    -
    - ), - code: ({ children, className }) => { - const isInline = !className; - if (isInline) { - return ( - - {children} - - ); - } - return {children}; - }, - pre: ({ children }) => { - // Check if this is a mermaid code block - let codeElement: string | null = null; - let className = ''; - - if (React.isValidElement(children)) { - const props = children.props as { - children?: string; - className?: string; - }; - codeElement = props?.children || null; - className = props?.className || ''; - } - - const isMermaid = - className.includes('language-mermaid') || - (typeof codeElement === 'string' && - (codeElement.trim().startsWith('graph') || - codeElement.trim().startsWith('flowchart') || - codeElement - .trim() - .startsWith('sequenceDiagram') || - codeElement.trim().startsWith('gantt') || - codeElement.trim().startsWith('pie') || - codeElement.trim().startsWith('gitgraph') || - codeElement.trim().startsWith('journey') || - codeElement.trim().startsWith('stateDiagram') || - codeElement.trim().startsWith('classDiagram') || - codeElement.trim().startsWith('erDiagram') || - codeElement.trim().startsWith('mindmap') || - codeElement.trim().startsWith('timeline'))); - - if (isMermaid && typeof codeElement === 'string') { - return ( -
    - -
    - ); - } - - return ( -
    -                              {children}
    -                            
    - ); - }, - a: ({ children, href }) => ( - - {children} - - ), - table: ({ children }) => ( -
    - {children}
    -
    - ), - th: ({ children }) => ( - - {children} - - ), - td: ({ children }) => ( - - {children} - - ), - hr: () => ( -
    - ), - strong: ({ children }) => ( - - {children} - - ), - em: ({ children }) => ( - - {children} - - ), - }} + remarkPlugins={[ + remarkGfm, + [ + remarkToc, + { + heading: '(table[ -]of[ -])?contents?|toc|sommaire', + tight: true, + }, + ], + ]} + rehypePlugins={[rehypeRaw, rehypeSlug, rehypeSanitize]} + components={createMarkdownComponents(false)} > {value || "*Commencez à écrire pour voir l'aperçu...*"}