feat: add markdown rendering support and enhance file preview component
This commit is contained in:
parent
036f23b777
commit
60925ee6ac
195
package-lock.json
generated
195
package-lock.json
generated
@ -18,6 +18,8 @@
|
|||||||
"@trpc/react-query": "^11.0.0",
|
"@trpc/react-query": "^11.0.0",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"dompurify": "^3.2.5",
|
"dompurify": "^3.2.5",
|
||||||
|
"github-markdown-css": "^5.8.1",
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
"mermaid": "^11.6.0",
|
"mermaid": "^11.6.0",
|
||||||
"minio": "^8.0.5",
|
"minio": "^8.0.5",
|
||||||
"next": "^15.2.3",
|
"next": "^15.2.3",
|
||||||
@ -27,7 +29,9 @@
|
|||||||
"react-hot-toast": "^2.5.2",
|
"react-hot-toast": "^2.5.2",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
|
"remark": "^15.0.1",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
|
"remark-html": "^16.0.1",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"superjson": "^2.2.1",
|
"superjson": "^2.2.1",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
@ -4883,6 +4887,19 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/esprima": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"bin": {
|
||||||
|
"esparse": "bin/esparse.js",
|
||||||
|
"esvalidate": "bin/esvalidate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esquery": {
|
"node_modules/esquery": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
||||||
@ -4957,6 +4974,18 @@
|
|||||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/extend-shallow": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-extendable": "^0.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@ -5247,6 +5276,18 @@
|
|||||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/github-markdown-css": {
|
||||||
|
"version": "5.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.8.1.tgz",
|
||||||
|
"integrity": "sha512-8G+PFvqigBQSWLQjyzgpa2ThD9bo7+kDsriUIidGcRhXgmcaAWUIpCZf8DavJgc+xifjbCG+GvMyWr0XMXmc7g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@ -5325,6 +5366,43 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/gray-matter": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"js-yaml": "^3.13.1",
|
||||||
|
"kind-of": "^6.0.2",
|
||||||
|
"section-matter": "^1.0.0",
|
||||||
|
"strip-bom-string": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gray-matter/node_modules/argparse": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sprintf-js": "~1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gray-matter/node_modules/js-yaml": {
|
||||||
|
"version": "3.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||||
|
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"argparse": "^1.0.7",
|
||||||
|
"esprima": "^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"js-yaml": "bin/js-yaml.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hachure-fill": {
|
"node_modules/hachure-fill": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
|
||||||
@ -5479,6 +5557,44 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hast-util-sanitize": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"@ungap/structured-clone": "^1.0.0",
|
||||||
|
"unist-util-position": "^5.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hast-util-to-html": {
|
||||||
|
"version": "9.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
|
||||||
|
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"@types/unist": "^3.0.0",
|
||||||
|
"ccount": "^2.0.0",
|
||||||
|
"comma-separated-tokens": "^2.0.0",
|
||||||
|
"hast-util-whitespace": "^3.0.0",
|
||||||
|
"html-void-elements": "^3.0.0",
|
||||||
|
"mdast-util-to-hast": "^13.0.0",
|
||||||
|
"property-information": "^7.0.0",
|
||||||
|
"space-separated-tokens": "^2.0.0",
|
||||||
|
"stringify-entities": "^4.0.0",
|
||||||
|
"zwitch": "^2.0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hast-util-to-jsx-runtime": {
|
"node_modules/hast-util-to-jsx-runtime": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
||||||
@ -5880,6 +5996,15 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-extendable": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@ -6315,6 +6440,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
|
||||||
"integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
|
"integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/kind-of": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/kolorist": {
|
"node_modules/kolorist": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
|
||||||
@ -8716,6 +8850,22 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/remark": {
|
||||||
|
"version": "15.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz",
|
||||||
|
"integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mdast": "^4.0.0",
|
||||||
|
"remark-parse": "^11.0.0",
|
||||||
|
"remark-stringify": "^11.0.0",
|
||||||
|
"unified": "^11.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark-gfm": {
|
"node_modules/remark-gfm": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
||||||
@ -8734,6 +8884,23 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/remark-html": {
|
||||||
|
"version": "16.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/remark-html/-/remark-html-16.0.1.tgz",
|
||||||
|
"integrity": "sha512-B9JqA5i0qZe0Nsf49q3OXyGvyXuZFDzAP2iOFLEumymuYJITVpiH1IgsTEwTpdptDmZlMDMWeDmSawdaJIGCXQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mdast": "^4.0.0",
|
||||||
|
"hast-util-sanitize": "^5.0.0",
|
||||||
|
"hast-util-to-html": "^9.0.0",
|
||||||
|
"mdast-util-to-hast": "^13.0.0",
|
||||||
|
"unified": "^11.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark-parse": {
|
"node_modules/remark-parse": {
|
||||||
"version": "11.0.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||||
@ -8974,6 +9141,19 @@
|
|||||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/section-matter": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"extend-shallow": "^2.0.1",
|
||||||
|
"kind-of": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.1",
|
"version": "7.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||||
@ -9219,6 +9399,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sprintf-js": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/stable-hash": {
|
"node_modules/stable-hash": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
|
||||||
@ -9404,6 +9590,15 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/strip-bom-string": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/strip-json-comments": {
|
"node_modules/strip-json-comments": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||||
|
|||||||
@ -30,6 +30,8 @@
|
|||||||
"@trpc/react-query": "^11.0.0",
|
"@trpc/react-query": "^11.0.0",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"dompurify": "^3.2.5",
|
"dompurify": "^3.2.5",
|
||||||
|
"github-markdown-css": "^5.8.1",
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
"mermaid": "^11.6.0",
|
"mermaid": "^11.6.0",
|
||||||
"minio": "^8.0.5",
|
"minio": "^8.0.5",
|
||||||
"next": "^15.2.3",
|
"next": "^15.2.3",
|
||||||
@ -39,7 +41,9 @@
|
|||||||
"react-hot-toast": "^2.5.2",
|
"react-hot-toast": "^2.5.2",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
|
"remark": "^15.0.1",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
|
"remark-html": "^16.0.1",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"superjson": "^2.2.1",
|
"superjson": "^2.2.1",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
|
|||||||
@ -105,7 +105,7 @@ export default function FileGrid({ session }: FileGridProps) {
|
|||||||
key={file.id}
|
key={file.id}
|
||||||
className="flex place-content-end max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
|
className="flex place-content-end max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
|
||||||
>
|
>
|
||||||
{<div className=" self-center max-w-50"><FilePreview fileId={file.id} fileType={file.extension} /></div>}
|
{<div className=" self-center max-w-50"><FilePreview fileId={file.id} fileType={file.extension} share={false} /></div>}
|
||||||
|
|
||||||
<button onClick={() => router.push(pageUrl + file.url)}>
|
<button onClick={() => router.push(pageUrl + file.url)}>
|
||||||
<h3 className="text-2xl font-bold">{file.name}</h3>
|
<h3 className="text-2xl font-bold">{file.name}</h3>
|
||||||
|
|||||||
@ -1,16 +1,22 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { getFileType } from "~/utils/fileType"; // Adjust the import path as necessary
|
import { remark } from 'remark';
|
||||||
|
import html from 'remark-html';
|
||||||
|
import matter from 'gray-matter';
|
||||||
|
import "github-markdown-css/github-markdown.css";
|
||||||
|
import "../styles/custom.css"; // Adjust the path as necessary
|
||||||
|
import { MarkdownRenderer } from "../../components/MarkdownRenderer";
|
||||||
|
|
||||||
interface FilePreviewProps {
|
interface FilePreviewProps {
|
||||||
fileId: string;
|
fileId: string;
|
||||||
fileType: string; // Pass the file type as a prop
|
fileType: string; // Pass the file type as a prop
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FilePreview({ fileId, fileType }: FilePreviewProps) {
|
export function FilePreview({ fileId, fileType, share }: FilePreviewProps & { share: boolean }) {
|
||||||
const [mediaSrc, setMediaSrc] = useState<string | null>(null);
|
const [mediaSrc, setMediaSrc] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [markdownContent, setMarkdownContent] = useState<string | null>(null);
|
||||||
|
|
||||||
console.log("File Type:", fileType);
|
console.log("File Type:", fileType);
|
||||||
|
|
||||||
@ -47,20 +53,53 @@ export function FilePreview({ fileId, fileType }: FilePreviewProps) {
|
|||||||
};
|
};
|
||||||
}, [fileId]);
|
}, [fileId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (fileType.startsWith("markdown")) {
|
||||||
|
const fetchMarkdown = async () => {
|
||||||
|
try {
|
||||||
|
const result = await renderMarkdown({ id: fileId });
|
||||||
|
setMarkdownContent(result.props.postData.contentHtml);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to fetch markdown content:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchMarkdown();
|
||||||
|
}
|
||||||
|
}, [fileId, fileType]);
|
||||||
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div className="text-red-500">{error}</div>;
|
return <div className="text-red-500">{error}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mediaSrc) {
|
if (!mediaSrc && !markdownContent) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fileType.startsWith("markdown")) {
|
||||||
|
if (share) {
|
||||||
|
return (
|
||||||
|
<div className="overflow-y-auto max-h-96 rounded-lg shadow-md">
|
||||||
|
{markdownContent ? (
|
||||||
|
<MarkdownRenderer markdownContent={markdownContent} />
|
||||||
|
) : (
|
||||||
|
<div>Loading markdown...</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<img src="/icons/files/code.svg" alt="Code file preview" className="max-w-full max-h-96 rounded-lg invert" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (fileType.startsWith("video")) {
|
if (fileType.startsWith("video")) {
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
controls
|
controls
|
||||||
className="max-w-full max-h-96 rounded-lg shadow-md"
|
className="max-w-full max-h-96 rounded-lg shadow-md"
|
||||||
src={mediaSrc}
|
src={mediaSrc || ""}
|
||||||
>
|
>
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
@ -71,14 +110,14 @@ export function FilePreview({ fileId, fileType }: FilePreviewProps) {
|
|||||||
<audio
|
<audio
|
||||||
controls
|
controls
|
||||||
className="max-w-full max-h-96 rounded-lg shadow-md"
|
className="max-w-full max-h-96 rounded-lg shadow-md"
|
||||||
src={mediaSrc}
|
src={mediaSrc || ""}
|
||||||
>
|
>
|
||||||
Your browser does not support the audio tag.
|
Your browser does not support the audio tag.
|
||||||
</audio>
|
</audio>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (fileType.startsWith("image")) {
|
if (fileType.startsWith("image")) {
|
||||||
return <img src={mediaSrc} alt="Media preview" className="max-w-full max-h-96 rounded-lg shadow-md" />;
|
return <img src={mediaSrc || ""} alt="Media preview" className="max-w-full max-h-96 rounded-lg shadow-md" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileType.startsWith("text")) {
|
if (fileType.startsWith("text")) {
|
||||||
@ -91,17 +130,48 @@ export function FilePreview({ fileId, fileType }: FilePreviewProps) {
|
|||||||
<img src="/icons/files/archive.svg" alt="Archive file preview" className="max-w-full max-h-96 rounded-lg invert" />
|
<img src="/icons/files/archive.svg" alt="Archive file preview" className="max-w-full max-h-96 rounded-lg invert" />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (fileType.startsWith("code") || fileType.startsWith("markdown")) {
|
if (fileType.startsWith("code")) {
|
||||||
return (
|
return (
|
||||||
<img src="/icons/files/code.svg" alt="Code file preview" className="max-w-full max-h-96 rounded-lg invert" />
|
<img src="/icons/files/code.svg" alt="Code file preview" className="max-w-full max-h-96 rounded-lg invert" />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// if (fileType.startsWith("markdown")) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// log file type
|
|
||||||
console.log("Unsupported file type:", fileType);
|
console.log("Unsupported file type:", fileType);
|
||||||
|
|
||||||
return;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function rendererMarkdown(id: string) {
|
||||||
|
const fileContents = await fetch(`/api/files/serv?id=${encodeURIComponent(id)}`)
|
||||||
|
.then((res) => res.text())
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Failed to fetch file contents:", err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!fileContents) {
|
||||||
|
throw new Error("File contents could not be fetched.");
|
||||||
|
}
|
||||||
|
const matterResult = matter(fileContents);
|
||||||
|
|
||||||
|
const processedContent = await remark()
|
||||||
|
.use(html)
|
||||||
|
.process(matterResult.content);
|
||||||
|
const contentHtml = processedContent.toString();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
contentHtml,
|
||||||
|
...matterResult.data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function renderMarkdown({ id }: { id: string }) {
|
||||||
|
const postData = await rendererMarkdown(id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
postData,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@ -5,6 +5,7 @@ import crypto from "crypto";
|
|||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import { auth } from "~/server/auth";
|
import { auth } from "~/server/auth";
|
||||||
import { minioClient, ensureBucketExists } from "~/utils/minioClient";
|
import { minioClient, ensureBucketExists } from "~/utils/minioClient";
|
||||||
|
import { getFileType } from "~/utils/fileType";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
@ -52,7 +53,7 @@ export async function POST(req: Request) {
|
|||||||
url: `/share?id=${fileId}`,
|
url: `/share?id=${fileId}`,
|
||||||
name: fileName,
|
name: fileName,
|
||||||
size: fileBuffer.length,
|
size: fileBuffer.length,
|
||||||
extension: info.mimeType,
|
extension: getFileType(fileName),
|
||||||
uploadedById: session.user.id,
|
uploadedById: session.user.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -91,7 +91,7 @@ export default function SearchFile() {
|
|||||||
className="flex place-content-end w-xxs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
|
className="flex place-content-end w-xxs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
|
||||||
>
|
>
|
||||||
<div className="self-center max-w-100 sm:max-w-50">
|
<div className="self-center max-w-100 sm:max-w-50">
|
||||||
<FilePreview fileId={file.id} fileType={file.extension} />
|
<FilePreview fileId={file.id} fileType={file.extension} share={false} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onClick={() => router.push(pageUrl + file.url)}>
|
<button onClick={() => router.push(pageUrl + file.url)}>
|
||||||
|
|||||||
@ -129,7 +129,7 @@ export default async function FilePreviewContainer({
|
|||||||
</h1>
|
</h1>
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
{fileDetails.type !== "unknown" && (
|
{fileDetails.type !== "unknown" && (
|
||||||
<FilePreview fileId={fileDetails.id} fileType={fileDetails.type} />
|
<FilePreview fileId={fileDetails.id} fileType={fileDetails.type} share={true} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full max-w-md rounded-lg bg-white/10 p-6 text-white shadow-md">
|
<div className="w-full max-w-md rounded-lg bg-white/10 p-6 text-white shadow-md">
|
||||||
|
|||||||
9
src/app/styles/custom.css
Normal file
9
src/app/styles/custom.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.markdown-body ul,
|
||||||
|
.markdown-body ol {
|
||||||
|
list-style: initial; /* Ensures bullets or numbers are displayed */
|
||||||
|
margin-left: 1.5em; /* Adds proper indentation */
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body li {
|
||||||
|
margin-bottom: 0.5em; /* Adds spacing between list items */
|
||||||
|
}
|
||||||
78
src/components/MarkdownRenderer.tsx
Normal file
78
src/components/MarkdownRenderer.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import "github-markdown-css/github-markdown.css";
|
||||||
|
|
||||||
|
interface MarkdownRendererProps {
|
||||||
|
markdownContent: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MarkdownRenderer({ markdownContent }: MarkdownRendererProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (markdownContent) {
|
||||||
|
const markdownContainer = document.querySelector("#markdown-preview");
|
||||||
|
if (!markdownContainer) return;
|
||||||
|
|
||||||
|
const codeBlocks = markdownContainer.querySelectorAll("code");
|
||||||
|
|
||||||
|
codeBlocks.forEach((block) => {
|
||||||
|
// Check if the block is already wrapped
|
||||||
|
if (block.parentElement?.classList.contains("code-wrapper")) return;
|
||||||
|
|
||||||
|
// Check if the code block is multiline
|
||||||
|
const isMultiline = block.textContent?.includes("\n");
|
||||||
|
if (!isMultiline) return;
|
||||||
|
|
||||||
|
// Create a wrapper only if it doesn't already exist
|
||||||
|
const wrapper = document.createElement("div");
|
||||||
|
wrapper.className = "code-wrapper"; // Add a class to identify the wrapper
|
||||||
|
wrapper.style.display = "flex";
|
||||||
|
wrapper.style.alignItems = "flex-start";
|
||||||
|
wrapper.style.justifyContent = "space-between";
|
||||||
|
wrapper.style.gap = "8px";
|
||||||
|
wrapper.style.width = "100%";
|
||||||
|
wrapper.style.position = "relative";
|
||||||
|
|
||||||
|
const codeContainer = document.createElement("div");
|
||||||
|
codeContainer.style.flex = "1";
|
||||||
|
codeContainer.appendChild(block.cloneNode(true));
|
||||||
|
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.innerHTML = `
|
||||||
|
<img src="/icons/copy.svg" alt="Copy" class="h-4 w-4" style="filter: invert(1) sepia(1) saturate(5) hue-rotate(180deg);"/>
|
||||||
|
`;
|
||||||
|
button.className =
|
||||||
|
"copy-button inline-flex items-center justify-center bg-gray-200 rounded hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition";
|
||||||
|
button.style.marginLeft = "8px";
|
||||||
|
button.style.width = "1.5rem";
|
||||||
|
button.style.height = "1.5rem";
|
||||||
|
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
navigator.clipboard.writeText(block.textContent || "").then(() => {
|
||||||
|
button.innerHTML = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
setTimeout(() => {
|
||||||
|
button.innerHTML = `
|
||||||
|
<img src="/icons/copy.svg" alt="Copy" class="h-4 w-4" style="filter: invert(1) sepia(1) saturate(5) hue-rotate(180deg);"/>
|
||||||
|
`;
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.appendChild(codeContainer);
|
||||||
|
wrapper.appendChild(button);
|
||||||
|
|
||||||
|
// Replace the original block with the wrapper
|
||||||
|
block.replaceWith(wrapper);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [markdownContent]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="markdown-body max-w-full p-4 pt-0 pb-0" id="markdown-preview">
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: markdownContent }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user