fix file handling and previews
This commit is contained in:
parent
ba52ca0b91
commit
8eb7fdd90f
7 changed files with 1321 additions and 780 deletions
59
bun.lock
59
bun.lock
|
@ -8,23 +8,28 @@
|
||||||
"@astrojs/react": "^4.2.0",
|
"@astrojs/react": "^4.2.0",
|
||||||
"@astrojs/tailwind": "5.1.4",
|
"@astrojs/tailwind": "5.1.4",
|
||||||
"@iconify-json/heroicons": "^1.2.2",
|
"@iconify-json/heroicons": "^1.2.2",
|
||||||
|
"@types/highlight.js": "^10.1.0",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/react": "^19.0.8",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.3",
|
||||||
"astro": "5.1.1",
|
"astro": "5.1.1",
|
||||||
"astro-expressive-code": "^0.38.3",
|
"astro-expressive-code": "^0.40.2",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"motion": "^11.15.0",
|
"motion": "^11.15.0",
|
||||||
"next": "^15.1.2",
|
"next": "^15.1.2",
|
||||||
"pocketbase": "^0.25.1",
|
"pocketbase": "^0.25.1",
|
||||||
|
"prismjs": "^1.29.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
|
"rehype-expressive-code": "^0.40.2",
|
||||||
"tailwindcss": "^3.4.16",
|
"tailwindcss": "^3.4.16",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/prismjs": "^1.26.5",
|
||||||
"daisyui": "^4.12.23",
|
"daisyui": "^4.12.23",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
|
@ -150,13 +155,13 @@
|
||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
|
||||||
|
|
||||||
"@expressive-code/core": ["@expressive-code/core@0.38.3", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-s0/OtdRpBONwcn23O8nVwDNQqpBGKscysejkeBkwlIeHRLZWgiTVrusT5Idrdz1d8cW5wRk9iGsAIQmwDPXgJg=="],
|
"@expressive-code/core": ["@expressive-code/core@0.40.2", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-gXY3v7jbgz6nWKvRpoDxK4AHUPkZRuJsM79vHX/5uhV9/qX6Qnctp/U/dMHog/LCVXcuOps+5nRmf1uxQVPb3w=="],
|
||||||
|
|
||||||
"@expressive-code/plugin-frames": ["@expressive-code/plugin-frames@0.38.3", "", { "dependencies": { "@expressive-code/core": "^0.38.3" } }, "sha512-qL2oC6FplmHNQfZ8ZkTR64/wKo9x0c8uP2WDftR/ydwN/yhe1ed7ZWYb8r3dezxsls+tDokCnN4zYR594jbpvg=="],
|
"@expressive-code/plugin-frames": ["@expressive-code/plugin-frames@0.40.2", "", { "dependencies": { "@expressive-code/core": "^0.40.2" } }, "sha512-aLw5IlDlZWb10Jo/TTDCVsmJhKfZ7FJI83Zo9VDrV0OBlmHAg7klZqw68VDz7FlftIBVAmMby53/MNXPnMjTSQ=="],
|
||||||
|
|
||||||
"@expressive-code/plugin-shiki": ["@expressive-code/plugin-shiki@0.38.3", "", { "dependencies": { "@expressive-code/core": "^0.38.3", "shiki": "^1.22.2" } }, "sha512-kqHnglZeesqG3UKrb6e9Fq5W36AZ05Y9tCREmSN2lw8LVTqENIeCIkLDdWtQ5VoHlKqwUEQFTVlRehdwoY7Gmw=="],
|
"@expressive-code/plugin-shiki": ["@expressive-code/plugin-shiki@0.40.2", "", { "dependencies": { "@expressive-code/core": "^0.40.2", "shiki": "^1.26.1" } }, "sha512-t2HMR5BO6GdDW1c1ISBTk66xO503e/Z8ecZdNcr6E4NpUfvY+MRje+LtrcvbBqMwWBBO8RpVKcam/Uy+1GxwKQ=="],
|
||||||
|
|
||||||
"@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.38.3", "", { "dependencies": { "@expressive-code/core": "^0.38.3" } }, "sha512-dPK3+BVGTbTmGQGU3Fkj3jZ3OltWUAlxetMHI6limUGCWBCucZiwoZeFM/WmqQa71GyKRzhBT+iEov6kkz2xVA=="],
|
"@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.40.2", "", { "dependencies": { "@expressive-code/core": "^0.40.2" } }, "sha512-/XoLjD67K9nfM4TgDlXAExzMJp6ewFKxNpfUw4F7q5Ecy+IU3/9zQQG/O70Zy+RxYTwKGw2MA9kd7yelsxnSmw=="],
|
||||||
|
|
||||||
"@iconify-json/heroicons": ["@iconify-json/heroicons@1.2.2", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-qoW4pXr5kTTL6juEjgTs83OJIwpePu7q1tdtKVEdj+i0zyyVHgg/dd9grsXJQnpTpBt6/VwNjrXBvFjRsKPENg=="],
|
"@iconify-json/heroicons": ["@iconify-json/heroicons@1.2.2", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-qoW4pXr5kTTL6juEjgTs83OJIwpePu7q1tdtKVEdj+i0zyyVHgg/dd9grsXJQnpTpBt6/VwNjrXBvFjRsKPENg=="],
|
||||||
|
|
||||||
|
@ -320,6 +325,10 @@
|
||||||
|
|
||||||
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.24.0", "", { "dependencies": { "@shikijs/types": "1.24.0", "@shikijs/vscode-textmate": "^9.3.0" } }, "sha512-Eua0qNOL73Y82lGA4GF5P+G2+VXX9XnuUxkiUuwcxQPH4wom+tE39kZpBFXfUuwNYxHSkrSxpB1p4kyRW0moSg=="],
|
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.24.0", "", { "dependencies": { "@shikijs/types": "1.24.0", "@shikijs/vscode-textmate": "^9.3.0" } }, "sha512-Eua0qNOL73Y82lGA4GF5P+G2+VXX9XnuUxkiUuwcxQPH4wom+tE39kZpBFXfUuwNYxHSkrSxpB1p4kyRW0moSg=="],
|
||||||
|
|
||||||
|
"@shikijs/langs": ["@shikijs/langs@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ=="],
|
||||||
|
|
||||||
|
"@shikijs/themes": ["@shikijs/themes@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g=="],
|
||||||
|
|
||||||
"@shikijs/types": ["@shikijs/types@1.24.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4" } }, "sha512-aptbEuq1Pk88DMlCe+FzXNnBZ17LCiLIGWAeCWhoFDzia5Q5Krx3DgnULLiouSdd6+LUM39XwXGppqYE0Ghtug=="],
|
"@shikijs/types": ["@shikijs/types@1.24.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4" } }, "sha512-aptbEuq1Pk88DMlCe+FzXNnBZ17LCiLIGWAeCWhoFDzia5Q5Krx3DgnULLiouSdd6+LUM39XwXGppqYE0Ghtug=="],
|
||||||
|
|
||||||
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@9.3.0", "", {}, "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA=="],
|
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@9.3.0", "", {}, "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA=="],
|
||||||
|
@ -350,6 +359,8 @@
|
||||||
|
|
||||||
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
||||||
|
|
||||||
|
"@types/highlight.js": ["@types/highlight.js@10.1.0", "", { "dependencies": { "highlight.js": "*" } }, "sha512-77hF2dGBsOgnvZll1vymYiNUtqJ8cJfXPD6GG/2M0aLRc29PkvB7Au6sIDjIEFcSICBhCh2+Pyq6WSRS7LUm6A=="],
|
||||||
|
|
||||||
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
|
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
|
||||||
|
|
||||||
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
||||||
|
@ -362,6 +373,8 @@
|
||||||
|
|
||||||
"@types/node": ["@types/node@22.10.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ=="],
|
"@types/node": ["@types/node@22.10.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ=="],
|
||||||
|
|
||||||
|
"@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.0.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw=="],
|
"@types/react": ["@types/react@19.0.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw=="],
|
||||||
|
|
||||||
"@types/react-dom": ["@types/react-dom@19.0.3", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA=="],
|
"@types/react-dom": ["@types/react-dom@19.0.3", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA=="],
|
||||||
|
@ -402,7 +415,7 @@
|
||||||
|
|
||||||
"astro": ["astro@5.1.1", "", { "dependencies": { "@astrojs/compiler": "^2.10.3", "@astrojs/internal-helpers": "0.4.2", "@astrojs/markdown-remark": "6.0.1", "@astrojs/telemetry": "3.2.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.3", "@types/cookie": "^0.6.0", "acorn": "^8.14.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.1.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^0.7.2", "cssesc": "^3.0.0", "debug": "^4.3.7", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.5.4", "esbuild": "^0.21.5", "estree-walker": "^3.0.3", "fast-glob": "^3.3.2", "flattie": "^1.1.1", "github-slugger": "^2.0.0", "html-escaper": "^3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.14", "magicast": "^0.3.5", "micromatch": "^4.0.8", "mrmime": "^2.0.0", "neotraverse": "^0.6.18", "p-limit": "^6.1.0", "p-queue": "^8.0.1", "preferred-pm": "^4.0.0", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.6.3", "shiki": "^1.23.1", "tinyexec": "^0.3.1", "tsconfck": "^3.1.4", "ultrahtml": "^1.5.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.14.0", "vfile": "^6.0.3", "vite": "^6.0.5", "vitefu": "^1.0.4", "which-pm": "^3.0.0", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.1.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-prpWC2PRs4P3FKQg6gZaU+VNMqbZi5pDvORGB2nrjfRjkrvF6/l4BqhvkJ6YQ0Ohm5rIMVz8ljgaRI77mLHbwg=="],
|
"astro": ["astro@5.1.1", "", { "dependencies": { "@astrojs/compiler": "^2.10.3", "@astrojs/internal-helpers": "0.4.2", "@astrojs/markdown-remark": "6.0.1", "@astrojs/telemetry": "3.2.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.3", "@types/cookie": "^0.6.0", "acorn": "^8.14.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.1.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^0.7.2", "cssesc": "^3.0.0", "debug": "^4.3.7", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.5.4", "esbuild": "^0.21.5", "estree-walker": "^3.0.3", "fast-glob": "^3.3.2", "flattie": "^1.1.1", "github-slugger": "^2.0.0", "html-escaper": "^3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.14", "magicast": "^0.3.5", "micromatch": "^4.0.8", "mrmime": "^2.0.0", "neotraverse": "^0.6.18", "p-limit": "^6.1.0", "p-queue": "^8.0.1", "preferred-pm": "^4.0.0", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.6.3", "shiki": "^1.23.1", "tinyexec": "^0.3.1", "tsconfck": "^3.1.4", "ultrahtml": "^1.5.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.14.0", "vfile": "^6.0.3", "vite": "^6.0.5", "vitefu": "^1.0.4", "which-pm": "^3.0.0", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.1.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-prpWC2PRs4P3FKQg6gZaU+VNMqbZi5pDvORGB2nrjfRjkrvF6/l4BqhvkJ6YQ0Ohm5rIMVz8ljgaRI77mLHbwg=="],
|
||||||
|
|
||||||
"astro-expressive-code": ["astro-expressive-code@0.38.3", "", { "dependencies": { "rehype-expressive-code": "^0.38.3" }, "peerDependencies": { "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" } }, "sha512-Tvdc7RV0G92BbtyEOsfJtXU35w41CkM94fOAzxbQP67Wj5jArfserJ321FO4XA7WG9QMV0GIBmQq77NBIRDzpQ=="],
|
"astro-expressive-code": ["astro-expressive-code@0.40.2", "", { "dependencies": { "rehype-expressive-code": "^0.40.2" }, "peerDependencies": { "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" } }, "sha512-yJMQId0yXSAbW9I6yqvJ3FcjKzJ8zRL7elbJbllkv1ZJPlsI0NI83Pxn1YL1IapEM347EvOOkSW2GL+2+NO61w=="],
|
||||||
|
|
||||||
"astro-icon": ["astro-icon@1.1.5", "", { "dependencies": { "@iconify/tools": "^4.0.5", "@iconify/types": "^2.0.0", "@iconify/utils": "^2.1.30" } }, "sha512-CJYS5nWOw9jz4RpGWmzNQY7D0y2ZZacH7atL2K9DeJXJVaz7/5WrxeyIxO8KASk1jCM96Q4LjRx/F3R+InjJrw=="],
|
"astro-icon": ["astro-icon@1.1.5", "", { "dependencies": { "@iconify/tools": "^4.0.5", "@iconify/types": "^2.0.0", "@iconify/utils": "^2.1.30" } }, "sha512-CJYS5nWOw9jz4RpGWmzNQY7D0y2ZZacH7atL2K9DeJXJVaz7/5WrxeyIxO8KASk1jCM96Q4LjRx/F3R+InjJrw=="],
|
||||||
|
|
||||||
|
@ -626,7 +639,7 @@
|
||||||
|
|
||||||
"execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
|
"execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
|
||||||
|
|
||||||
"expressive-code": ["expressive-code@0.38.3", "", { "dependencies": { "@expressive-code/core": "^0.38.3", "@expressive-code/plugin-frames": "^0.38.3", "@expressive-code/plugin-shiki": "^0.38.3", "@expressive-code/plugin-text-markers": "^0.38.3" } }, "sha512-COM04AiUotHCKJgWdn7NtW2lqu8OW8owAidMpkXt1qxrZ9Q2iC7+tok/1qIn2ocGnczvr9paIySgGnEwFeEQ8Q=="],
|
"expressive-code": ["expressive-code@0.40.2", "", { "dependencies": { "@expressive-code/core": "^0.40.2", "@expressive-code/plugin-frames": "^0.40.2", "@expressive-code/plugin-shiki": "^0.40.2", "@expressive-code/plugin-text-markers": "^0.40.2" } }, "sha512-1zIda2rB0qiDZACawzw2rbdBQiWHBT56uBctS+ezFe5XMAaFaHLnnSYND/Kd+dVzO9HfCXRDpzH3d+3fvOWRcw=="],
|
||||||
|
|
||||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||||
|
|
||||||
|
@ -720,6 +733,8 @@
|
||||||
|
|
||||||
"hastscript": ["hastscript@9.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw=="],
|
"hastscript": ["hastscript@9.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw=="],
|
||||||
|
|
||||||
|
"highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
|
||||||
|
|
||||||
"html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="],
|
"html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="],
|
||||||
|
|
||||||
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
|
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
|
||||||
|
@ -1132,7 +1147,7 @@
|
||||||
|
|
||||||
"rehype": ["rehype@13.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="],
|
"rehype": ["rehype@13.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="],
|
||||||
|
|
||||||
"rehype-expressive-code": ["rehype-expressive-code@0.38.3", "", { "dependencies": { "expressive-code": "^0.38.3" } }, "sha512-RYSSDkMBikoTbycZPkcWp6ELneANT4eTpND1DSRJ6nI2eVFUwTBDCvE2vO6jOOTaavwnPiydi4i/87NRyjpdOA=="],
|
"rehype-expressive-code": ["rehype-expressive-code@0.40.2", "", { "dependencies": { "expressive-code": "^0.40.2" } }, "sha512-+kn+AMGCrGzvtH8Q5lC6Y5lnmTV/r33fdmi5QU/IH1KPHKobKr5UnLwJuqHv5jBTSN/0v2wLDS7RTM73FVzqmQ=="],
|
||||||
|
|
||||||
"rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="],
|
"rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="],
|
||||||
|
|
||||||
|
@ -1384,6 +1399,8 @@
|
||||||
|
|
||||||
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
|
"@expressive-code/plugin-shiki/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="],
|
||||||
|
|
||||||
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||||
|
|
||||||
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||||
|
@ -1392,6 +1409,10 @@
|
||||||
|
|
||||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
|
"@shikijs/langs/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
||||||
|
|
||||||
|
"@shikijs/themes/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
||||||
|
|
||||||
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
@ -1456,8 +1477,22 @@
|
||||||
|
|
||||||
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
|
"@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/core@1.29.2", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="],
|
||||||
|
|
||||||
|
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="],
|
||||||
|
|
||||||
|
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA=="],
|
||||||
|
|
||||||
|
"@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
||||||
|
|
||||||
|
"@expressive-code/plugin-shiki/shiki/@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.1", "", {}, "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg=="],
|
||||||
|
|
||||||
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||||
|
|
||||||
|
"@shikijs/langs/@shikijs/types/@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.1", "", {}, "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg=="],
|
||||||
|
|
||||||
|
"@shikijs/themes/@shikijs/types/@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.1", "", {}, "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg=="],
|
||||||
|
|
||||||
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
"ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
"ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
@ -1522,6 +1557,10 @@
|
||||||
|
|
||||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"@expressive-code/plugin-shiki/shiki/@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.4", "", { "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": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA=="],
|
||||||
|
|
||||||
|
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
||||||
|
|
||||||
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
"astro/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw=="],
|
"astro/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw=="],
|
||||||
|
@ -1571,5 +1610,9 @@
|
||||||
"astro/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw=="],
|
"astro/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw=="],
|
||||||
|
|
||||||
"astro/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.0", "", { "os": "win32", "cpu": "x64" }, "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA=="],
|
"astro/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.0", "", { "os": "win32", "cpu": "x64" }, "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA=="],
|
||||||
|
|
||||||
|
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
|
||||||
|
|
||||||
|
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
208
package-lock.json
generated
208
package-lock.json
generated
|
@ -12,23 +12,26 @@
|
||||||
"@astrojs/node": "^9.0.0",
|
"@astrojs/node": "^9.0.0",
|
||||||
"@astrojs/react": "^4.2.0",
|
"@astrojs/react": "^4.2.0",
|
||||||
"@astrojs/tailwind": "5.1.4",
|
"@astrojs/tailwind": "5.1.4",
|
||||||
|
"@iconify-json/heroicons": "^1.2.2",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/react": "^19.0.8",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.3",
|
||||||
"astro": "5.1.1",
|
"astro": "5.1.1",
|
||||||
"astro-expressive-code": "^0.38.3",
|
"astro-expressive-code": "^0.40.2",
|
||||||
"astro-icon": "^1.1.4",
|
"astro-icon": "^1.1.5",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"motion": "^11.15.0",
|
"motion": "^11.15.0",
|
||||||
"next": "^15.1.2",
|
"next": "^15.1.2",
|
||||||
"pocketbase": "^0.25.1",
|
"pocketbase": "^0.25.1",
|
||||||
|
"prismjs": "^1.29.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
"tailwindcss": "^3.4.16"
|
"tailwindcss": "^3.4.16"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/prismjs": "^1.26.5",
|
||||||
"daisyui": "^4.12.23",
|
"daisyui": "^4.12.23",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
|
@ -484,7 +487,6 @@
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz",
|
||||||
"integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==",
|
"integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
|
@ -900,10 +902,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@expressive-code/core": {
|
"node_modules/@expressive-code/core": {
|
||||||
"version": "0.38.3",
|
"version": "0.40.2",
|
||||||
"resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.38.3.tgz",
|
"resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.40.2.tgz",
|
||||||
"integrity": "sha512-s0/OtdRpBONwcn23O8nVwDNQqpBGKscysejkeBkwlIeHRLZWgiTVrusT5Idrdz1d8cW5wRk9iGsAIQmwDPXgJg==",
|
"integrity": "sha512-gXY3v7jbgz6nWKvRpoDxK4AHUPkZRuJsM79vHX/5uhV9/qX6Qnctp/U/dMHog/LCVXcuOps+5nRmf1uxQVPb3w==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ctrl/tinycolor": "^4.0.4",
|
"@ctrl/tinycolor": "^4.0.4",
|
||||||
"hast-util-select": "^6.0.2",
|
"hast-util-select": "^6.0.2",
|
||||||
|
@ -917,31 +918,36 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@expressive-code/plugin-frames": {
|
"node_modules/@expressive-code/plugin-frames": {
|
||||||
"version": "0.38.3",
|
"version": "0.40.2",
|
||||||
"resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.38.3.tgz",
|
"resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.40.2.tgz",
|
||||||
"integrity": "sha512-qL2oC6FplmHNQfZ8ZkTR64/wKo9x0c8uP2WDftR/ydwN/yhe1ed7ZWYb8r3dezxsls+tDokCnN4zYR594jbpvg==",
|
"integrity": "sha512-aLw5IlDlZWb10Jo/TTDCVsmJhKfZ7FJI83Zo9VDrV0OBlmHAg7klZqw68VDz7FlftIBVAmMby53/MNXPnMjTSQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expressive-code/core": "^0.38.3"
|
"@expressive-code/core": "^0.40.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@expressive-code/plugin-shiki": {
|
"node_modules/@expressive-code/plugin-shiki": {
|
||||||
"version": "0.38.3",
|
"version": "0.40.2",
|
||||||
"resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.38.3.tgz",
|
"resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.40.2.tgz",
|
||||||
"integrity": "sha512-kqHnglZeesqG3UKrb6e9Fq5W36AZ05Y9tCREmSN2lw8LVTqENIeCIkLDdWtQ5VoHlKqwUEQFTVlRehdwoY7Gmw==",
|
"integrity": "sha512-t2HMR5BO6GdDW1c1ISBTk66xO503e/Z8ecZdNcr6E4NpUfvY+MRje+LtrcvbBqMwWBBO8RpVKcam/Uy+1GxwKQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expressive-code/core": "^0.38.3",
|
"@expressive-code/core": "^0.40.2",
|
||||||
"shiki": "^1.22.2"
|
"shiki": "^1.26.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@expressive-code/plugin-text-markers": {
|
"node_modules/@expressive-code/plugin-text-markers": {
|
||||||
"version": "0.38.3",
|
"version": "0.40.2",
|
||||||
"resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.38.3.tgz",
|
"resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.40.2.tgz",
|
||||||
"integrity": "sha512-dPK3+BVGTbTmGQGU3Fkj3jZ3OltWUAlxetMHI6limUGCWBCucZiwoZeFM/WmqQa71GyKRzhBT+iEov6kkz2xVA==",
|
"integrity": "sha512-/XoLjD67K9nfM4TgDlXAExzMJp6ewFKxNpfUw4F7q5Ecy+IU3/9zQQG/O70Zy+RxYTwKGw2MA9kd7yelsxnSmw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expressive-code/core": "^0.38.3"
|
"@expressive-code/core": "^0.40.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@iconify-json/heroicons": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify-json/heroicons/-/heroicons-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-qoW4pXr5kTTL6juEjgTs83OJIwpePu7q1tdtKVEdj+i0zyyVHgg/dd9grsXJQnpTpBt6/VwNjrXBvFjRsKPENg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/types": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@iconify/tools": {
|
"node_modules/@iconify/tools": {
|
||||||
|
@ -1960,73 +1966,66 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/core": {
|
"node_modules/@shikijs/core": {
|
||||||
"version": "1.25.1",
|
"version": "1.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.29.2.tgz",
|
||||||
"integrity": "sha512-0j5k3ZkLTQViOuNzPVyWGoW1zgH3kiFdUT/JOCkTm7TU74mz+dF+NID+YoiCBzHQxgsDpcGYPjKDJRcuVLSt4A==",
|
"integrity": "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/engine-javascript": "1.25.1",
|
"@shikijs/engine-javascript": "1.29.2",
|
||||||
"@shikijs/engine-oniguruma": "1.25.1",
|
"@shikijs/engine-oniguruma": "1.29.2",
|
||||||
"@shikijs/types": "1.25.1",
|
"@shikijs/types": "1.29.2",
|
||||||
"@shikijs/vscode-textmate": "^9.3.1",
|
"@shikijs/vscode-textmate": "^10.0.1",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"hast-util-to-html": "^9.0.4"
|
"hast-util-to-html": "^9.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/engine-javascript": {
|
"node_modules/@shikijs/engine-javascript": {
|
||||||
"version": "1.25.1",
|
"version": "1.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz",
|
||||||
"integrity": "sha512-zQ7UWKnRCfD/Q1M+XOSyjsbhpE0qv8LUnmn82HYCeOsgAHgUZGEDIQ63bbuK3kU5sQg+2CtI+dPfOqD/mjSY9w==",
|
"integrity": "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "1.25.1",
|
"@shikijs/types": "1.29.2",
|
||||||
"@shikijs/vscode-textmate": "^9.3.1",
|
"@shikijs/vscode-textmate": "^10.0.1",
|
||||||
"oniguruma-to-es": "0.10.0"
|
"oniguruma-to-es": "^2.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/engine-oniguruma": {
|
"node_modules/@shikijs/engine-oniguruma": {
|
||||||
"version": "1.25.1",
|
"version": "1.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz",
|
||||||
"integrity": "sha512-iKPMh3H+0USHtWfZ1irfMTH6tGmIUFSnqt3E2K8BgI1VEsqiPh0RYkG2WTwzNiM1/WHN4FzYx/nrKR7PDHiRyw==",
|
"integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "1.25.1",
|
"@shikijs/types": "1.29.2",
|
||||||
"@shikijs/vscode-textmate": "^9.3.1"
|
"@shikijs/vscode-textmate": "^10.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/langs": {
|
"node_modules/@shikijs/langs": {
|
||||||
"version": "1.25.1",
|
"version": "1.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-1.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-1.29.2.tgz",
|
||||||
"integrity": "sha512-hdYjq9aRJplAzGe2qF51PR9IDgEoyGb4IkXvr3Ts6lEdg4Z8M/kdknKRo2EIuv3IR/aKkJXTlBQRM+wr3t20Ew==",
|
"integrity": "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "1.25.1"
|
"@shikijs/types": "1.29.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/themes": {
|
"node_modules/@shikijs/themes": {
|
||||||
"version": "1.25.1",
|
"version": "1.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-1.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-1.29.2.tgz",
|
||||||
"integrity": "sha512-JO0lDn4LgGqg5QKvgich5ScUmC2okK+LxM9a3iLUH7YMeI2c8UGXThuJv6sZduS7pdJbYQHPrvWq9t/V4GhpbQ==",
|
"integrity": "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "1.25.1"
|
"@shikijs/types": "1.29.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/types": {
|
"node_modules/@shikijs/types": {
|
||||||
"version": "1.25.1",
|
"version": "1.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz",
|
||||||
"integrity": "sha512-dceqFUoO95eY4tpOj3OGq8wE8EgJ4ey6Me1HQEu5UbwIYszFndEll/bjlB8Kp9wl4fx3uM7n4+y9XCYuDBmcXA==",
|
"integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/vscode-textmate": "^9.3.1",
|
"@shikijs/vscode-textmate": "^10.0.1",
|
||||||
"@types/hast": "^3.0.4"
|
"@types/hast": "^3.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/vscode-textmate": {
|
"node_modules/@shikijs/vscode-textmate": {
|
||||||
"version": "9.3.1",
|
"version": "10.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz",
|
||||||
"integrity": "sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==",
|
"integrity": "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/@swc/counter": {
|
"node_modules/@swc/counter": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
|
@ -2185,6 +2184,12 @@
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.20.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/prismjs": {
|
||||||
|
"version": "1.26.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz",
|
||||||
|
"integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.0.8",
|
"version": "19.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz",
|
||||||
|
@ -2497,12 +2502,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/astro-expressive-code": {
|
"node_modules/astro-expressive-code": {
|
||||||
"version": "0.38.3",
|
"version": "0.40.2",
|
||||||
"resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.38.3.tgz",
|
"resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.40.2.tgz",
|
||||||
"integrity": "sha512-Tvdc7RV0G92BbtyEOsfJtXU35w41CkM94fOAzxbQP67Wj5jArfserJ321FO4XA7WG9QMV0GIBmQq77NBIRDzpQ==",
|
"integrity": "sha512-yJMQId0yXSAbW9I6yqvJ3FcjKzJ8zRL7elbJbllkv1ZJPlsI0NI83Pxn1YL1IapEM347EvOOkSW2GL+2+NO61w==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rehype-expressive-code": "^0.38.3"
|
"rehype-expressive-code": "^0.40.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0"
|
"astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0"
|
||||||
|
@ -2620,7 +2624,6 @@
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
|
||||||
"integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==",
|
"integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==",
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
|
@ -3142,8 +3145,7 @@
|
||||||
"type": "patreon",
|
"type": "patreon",
|
||||||
"url": "https://patreon.com/mdevils"
|
"url": "https://patreon.com/mdevils"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/css-selector-tokenizer": {
|
"node_modules/css-selector-tokenizer": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
|
@ -3398,7 +3400,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz",
|
||||||
"integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==",
|
"integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==",
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"direction": "cli.js"
|
"direction": "cli.js"
|
||||||
},
|
},
|
||||||
|
@ -3504,8 +3505,7 @@
|
||||||
"node_modules/emoji-regex-xs": {
|
"node_modules/emoji-regex-xs": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz",
|
||||||
"integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==",
|
"integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/encodeurl": {
|
"node_modules/encodeurl": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
@ -3773,15 +3773,14 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/expressive-code": {
|
"node_modules/expressive-code": {
|
||||||
"version": "0.38.3",
|
"version": "0.40.2",
|
||||||
"resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.38.3.tgz",
|
"resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.40.2.tgz",
|
||||||
"integrity": "sha512-COM04AiUotHCKJgWdn7NtW2lqu8OW8owAidMpkXt1qxrZ9Q2iC7+tok/1qIn2ocGnczvr9paIySgGnEwFeEQ8Q==",
|
"integrity": "sha512-1zIda2rB0qiDZACawzw2rbdBQiWHBT56uBctS+ezFe5XMAaFaHLnnSYND/Kd+dVzO9HfCXRDpzH3d+3fvOWRcw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expressive-code/core": "^0.38.3",
|
"@expressive-code/core": "^0.40.2",
|
||||||
"@expressive-code/plugin-frames": "^0.38.3",
|
"@expressive-code/plugin-frames": "^0.40.2",
|
||||||
"@expressive-code/plugin-shiki": "^0.38.3",
|
"@expressive-code/plugin-shiki": "^0.40.2",
|
||||||
"@expressive-code/plugin-text-markers": "^0.38.3"
|
"@expressive-code/plugin-text-markers": "^0.40.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/extend": {
|
"node_modules/extend": {
|
||||||
|
@ -4227,7 +4226,6 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz",
|
||||||
"integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==",
|
"integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/hast": "^3.0.0"
|
"@types/hast": "^3.0.0"
|
||||||
},
|
},
|
||||||
|
@ -4291,7 +4289,6 @@
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.3.tgz",
|
||||||
"integrity": "sha512-OVRQlQ1XuuLP8aFVLYmC2atrfWHS5UD3shonxpnyrjcCkwtvmt/+N6kYJdcY4mkMJhxp4kj2EFIxQ9kvkkt/eQ==",
|
"integrity": "sha512-OVRQlQ1XuuLP8aFVLYmC2atrfWHS5UD3shonxpnyrjcCkwtvmt/+N6kYJdcY4mkMJhxp4kj2EFIxQ9kvkkt/eQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/hast": "^3.0.0",
|
"@types/hast": "^3.0.0",
|
||||||
"@types/unist": "^3.0.0",
|
"@types/unist": "^3.0.0",
|
||||||
|
@ -4430,7 +4427,6 @@
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz",
|
||||||
"integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==",
|
"integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/hast": "^3.0.0"
|
"@types/hast": "^3.0.0"
|
||||||
},
|
},
|
||||||
|
@ -6490,10 +6486,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/oniguruma-to-es": {
|
"node_modules/oniguruma-to-es": {
|
||||||
"version": "0.10.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-0.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz",
|
||||||
"integrity": "sha512-zapyOUOCJxt+xhiNRPPMtfJkHGsZ98HHB9qJEkdT8BGytO/+kpe4m1Ngf0MzbzTmhacn11w9yGeDP6tzDhnCdg==",
|
"integrity": "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"emoji-regex-xs": "^1.0.0",
|
"emoji-regex-xs": "^1.0.0",
|
||||||
"regex": "^5.1.1",
|
"regex": "^5.1.1",
|
||||||
|
@ -7244,7 +7239,6 @@
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz",
|
||||||
"integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==",
|
"integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regex-utilities": "^2.3.0"
|
"regex-utilities": "^2.3.0"
|
||||||
}
|
}
|
||||||
|
@ -7253,7 +7247,6 @@
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.1.1.tgz",
|
||||||
"integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==",
|
"integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regex": "^5.1.1",
|
"regex": "^5.1.1",
|
||||||
"regex-utilities": "^2.3.0"
|
"regex-utilities": "^2.3.0"
|
||||||
|
@ -7262,8 +7255,7 @@
|
||||||
"node_modules/regex-utilities": {
|
"node_modules/regex-utilities": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
|
||||||
"integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
|
"integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/rehype": {
|
"node_modules/rehype": {
|
||||||
"version": "13.0.2",
|
"version": "13.0.2",
|
||||||
|
@ -7282,12 +7274,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rehype-expressive-code": {
|
"node_modules/rehype-expressive-code": {
|
||||||
"version": "0.38.3",
|
"version": "0.40.2",
|
||||||
"resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.38.3.tgz",
|
"resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.40.2.tgz",
|
||||||
"integrity": "sha512-RYSSDkMBikoTbycZPkcWp6ELneANT4eTpND1DSRJ6nI2eVFUwTBDCvE2vO6jOOTaavwnPiydi4i/87NRyjpdOA==",
|
"integrity": "sha512-+kn+AMGCrGzvtH8Q5lC6Y5lnmTV/r33fdmi5QU/IH1KPHKobKr5UnLwJuqHv5jBTSN/0v2wLDS7RTM73FVzqmQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"expressive-code": "^0.38.3"
|
"expressive-code": "^0.40.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rehype-parse": {
|
"node_modules/rehype-parse": {
|
||||||
|
@ -7754,18 +7745,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/shiki": {
|
"node_modules/shiki": {
|
||||||
"version": "1.25.1",
|
"version": "1.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-1.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/shiki/-/shiki-1.29.2.tgz",
|
||||||
"integrity": "sha512-/1boRvNYwRW3GLG9Y6dXdnZ/Ha+J5T/5y3hV7TGQUcDSBM185D3FCbXlz2eTGNKG2iWCbWqo+P0yhGKZ4/CUrw==",
|
"integrity": "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/core": "1.25.1",
|
"@shikijs/core": "1.29.2",
|
||||||
"@shikijs/engine-javascript": "1.25.1",
|
"@shikijs/engine-javascript": "1.29.2",
|
||||||
"@shikijs/engine-oniguruma": "1.25.1",
|
"@shikijs/engine-oniguruma": "1.29.2",
|
||||||
"@shikijs/langs": "1.25.1",
|
"@shikijs/langs": "1.29.2",
|
||||||
"@shikijs/themes": "1.25.1",
|
"@shikijs/themes": "1.29.2",
|
||||||
"@shikijs/types": "1.25.1",
|
"@shikijs/types": "1.29.2",
|
||||||
"@shikijs/vscode-textmate": "^9.3.1",
|
"@shikijs/vscode-textmate": "^10.0.1",
|
||||||
"@types/hast": "^3.0.4"
|
"@types/hast": "^3.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,23 +15,28 @@
|
||||||
"@astrojs/react": "^4.2.0",
|
"@astrojs/react": "^4.2.0",
|
||||||
"@astrojs/tailwind": "5.1.4",
|
"@astrojs/tailwind": "5.1.4",
|
||||||
"@iconify-json/heroicons": "^1.2.2",
|
"@iconify-json/heroicons": "^1.2.2",
|
||||||
|
"@types/highlight.js": "^10.1.0",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/react": "^19.0.8",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.3",
|
||||||
"astro": "5.1.1",
|
"astro": "5.1.1",
|
||||||
"astro-expressive-code": "^0.38.3",
|
"astro-expressive-code": "^0.40.2",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"motion": "^11.15.0",
|
"motion": "^11.15.0",
|
||||||
"next": "^15.1.2",
|
"next": "^15.1.2",
|
||||||
"pocketbase": "^0.25.1",
|
"pocketbase": "^0.25.1",
|
||||||
|
"prismjs": "^1.29.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
|
"rehype-expressive-code": "^0.40.2",
|
||||||
"tailwindcss": "^3.4.16"
|
"tailwindcss": "^3.4.16"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/prismjs": "^1.26.5",
|
||||||
"daisyui": "^4.12.23",
|
"daisyui": "^4.12.23",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Authentication } from "../pocketbase/Authentication";
|
||||||
import { Update } from "../pocketbase/Update";
|
import { Update } from "../pocketbase/Update";
|
||||||
import { FileManager } from "../pocketbase/FileManager";
|
import { FileManager } from "../pocketbase/FileManager";
|
||||||
import { SendLog } from "../pocketbase/SendLog";
|
import { SendLog } from "../pocketbase/SendLog";
|
||||||
|
import FilePreview from "../modals/FilePreview";
|
||||||
|
|
||||||
// Get instances
|
// Get instances
|
||||||
const get = Get.getInstance();
|
const get = Get.getInstance();
|
||||||
|
@ -92,6 +93,7 @@ declare global {
|
||||||
hideLoading: () => void;
|
hideLoading: () => void;
|
||||||
deleteEvent: (eventId: string, eventName: string) => Promise<void>;
|
deleteEvent: (eventId: string, eventName: string) => Promise<void>;
|
||||||
resetAndCloseModal: () => void;
|
resetAndCloseModal: () => void;
|
||||||
|
previewFileInEditModal: (url: string, filename: string) => void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
@ -621,187 +623,190 @@ declare global {
|
||||||
<!-- Edit Event Modal -->
|
<!-- Edit Event Modal -->
|
||||||
<dialog id="editEventModal" class="modal">
|
<dialog id="editEventModal" class="modal">
|
||||||
<div class="modal-box max-w-2xl">
|
<div class="modal-box max-w-2xl">
|
||||||
<h3 class="font-bold text-lg mb-4" id="editModalTitle">Edit Event</h3>
|
<!-- Main Edit Form Section -->
|
||||||
<form id="editEventForm" class="space-y-4">
|
<div id="editFormSection">
|
||||||
<input type="hidden" id="editEventId" />
|
<h3 class="font-bold text-lg mb-4" id="editModalTitle">
|
||||||
|
Edit Event
|
||||||
|
</h3>
|
||||||
|
<form id="editEventForm" class="space-y-4">
|
||||||
|
<input type="hidden" id="editEventId" />
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<!-- Event Name -->
|
<!-- Event Name -->
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text">Event Name</span>
|
<span class="label-text">Event Name</span>
|
||||||
<span class="label-text-alt text-error">*</span>
|
<span class="label-text-alt text-error">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="editEventName"
|
id="editEventName"
|
||||||
name="editEventName"
|
name="editEventName"
|
||||||
class="input input-bordered"
|
class="input input-bordered"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Event Code -->
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label">
|
|
||||||
<span class="label-text">Event Code</span>
|
|
||||||
<span class="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="editEventCode"
|
|
||||||
name="editEventCode"
|
|
||||||
class="input input-bordered"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Location -->
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label">
|
|
||||||
<span class="label-text">Location</span>
|
|
||||||
<span class="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="editEventLocation"
|
|
||||||
name="editEventLocation"
|
|
||||||
class="input input-bordered"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Points to Reward -->
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label">
|
|
||||||
<span class="label-text">Points to Reward</span>
|
|
||||||
<span class="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
id="editEventPoints"
|
|
||||||
name="editEventPoints"
|
|
||||||
class="input input-bordered"
|
|
||||||
min="0"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Start Date -->
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label">
|
|
||||||
<span class="label-text">Start Date</span>
|
|
||||||
<span class="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="datetime-local"
|
|
||||||
id="editEventStartDate"
|
|
||||||
name="editEventStartDate"
|
|
||||||
class="input input-bordered"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- End Date -->
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label">
|
|
||||||
<span class="label-text">End Date</span>
|
|
||||||
<span class="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="datetime-local"
|
|
||||||
id="editEventEndDate"
|
|
||||||
name="editEventEndDate"
|
|
||||||
class="input input-bordered"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Description -->
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label">
|
|
||||||
<span class="label-text">Description</span>
|
|
||||||
<span class="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
id="editEventDescription"
|
|
||||||
name="editEventDescription"
|
|
||||||
class="textarea textarea-bordered"
|
|
||||||
rows="3"
|
|
||||||
required></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Files -->
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label">
|
|
||||||
<span class="label-text">Upload Files</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
id="editEventFiles"
|
|
||||||
class="file-input file-input-bordered"
|
|
||||||
multiple
|
|
||||||
/>
|
|
||||||
<div class="mt-4 space-y-2">
|
|
||||||
<div id="newFiles" class="space-y-2">
|
|
||||||
<!-- New files will be listed here -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="divider">Current Files</div>
|
|
||||||
<div id="currentFiles" class="space-y-2">
|
<!-- Event Code -->
|
||||||
<!-- Current files will be listed here -->
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Event Code</span>
|
||||||
|
<span class="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="editEventCode"
|
||||||
|
name="editEventCode"
|
||||||
|
class="input input-bordered"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Location</span>
|
||||||
|
<span class="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="editEventLocation"
|
||||||
|
name="editEventLocation"
|
||||||
|
class="input input-bordered"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Points to Reward -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Points to Reward</span>
|
||||||
|
<span class="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="editEventPoints"
|
||||||
|
name="editEventPoints"
|
||||||
|
class="input input-bordered"
|
||||||
|
min="0"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Start Date -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Start Date</span>
|
||||||
|
<span class="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
id="editEventStartDate"
|
||||||
|
name="editEventStartDate"
|
||||||
|
class="input input-bordered"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- End Date -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">End Date</span>
|
||||||
|
<span class="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
id="editEventEndDate"
|
||||||
|
name="editEventEndDate"
|
||||||
|
class="input input-bordered"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Published -->
|
<!-- Description -->
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label cursor-pointer justify-start gap-4">
|
<label class="label">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
<span class="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="editEventDescription"
|
||||||
|
name="editEventDescription"
|
||||||
|
class="textarea textarea-bordered"
|
||||||
|
rows="3"
|
||||||
|
required></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Files -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Upload Files</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="file"
|
||||||
id="editEventPublished"
|
id="editEventFiles"
|
||||||
name="editEventPublished"
|
class="file-input file-input-bordered"
|
||||||
class="toggle"
|
multiple
|
||||||
/>
|
/>
|
||||||
<span class="label-text">Publish Event</span>
|
<div class="mt-4 space-y-2">
|
||||||
</label>
|
<div id="newFiles" class="space-y-2">
|
||||||
<label class="label">
|
<!-- New files will be listed here -->
|
||||||
<span class="label-text-alt text-info"
|
</div>
|
||||||
>This has to be clicked if you want to make this event
|
<div class="divider">Current Files</div>
|
||||||
available to the public</span
|
<div id="currentFiles" class="space-y-2">
|
||||||
>
|
<!-- Current files will be listed here -->
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Has Food -->
|
<!-- Published -->
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label cursor-pointer justify-start gap-4">
|
<label class="label cursor-pointer justify-start gap-4">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="editEventHasFood"
|
id="editEventPublished"
|
||||||
name="editEventHasFood"
|
name="editEventPublished"
|
||||||
class="toggle"
|
class="toggle"
|
||||||
/>
|
/>
|
||||||
<span class="label-text">Has Food</span>
|
<span class="label-text">Publish Event</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text-alt text-info"
|
<span class="label-text-alt text-info"
|
||||||
>Check this if food will be provided at the event</span
|
>This has to be clicked if you want to make this
|
||||||
>
|
event available to the public</span
|
||||||
</label>
|
>
|
||||||
</div>
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal-action">
|
<!-- Has Food -->
|
||||||
<button type="submit" class="btn btn-primary"
|
<div class="form-control">
|
||||||
>Save Changes</button
|
<label class="label cursor-pointer justify-start gap-4">
|
||||||
>
|
<input
|
||||||
<button
|
type="checkbox"
|
||||||
type="button"
|
id="editEventHasFood"
|
||||||
class="btn"
|
name="editEventHasFood"
|
||||||
onclick="editEventModal.close()">Cancel</button
|
class="toggle"
|
||||||
>
|
/>
|
||||||
</div>
|
<span class="label-text">Has Food</span>
|
||||||
</form>
|
</label>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt text-info"
|
||||||
|
>Check this if food will be provided at the event</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-action">
|
||||||
|
<button type="submit" class="btn btn-primary" form="editEventForm"
|
||||||
|
>Save Changes</button
|
||||||
|
>
|
||||||
|
<button type="button" class="btn" onclick="editEventModal.close()"
|
||||||
|
>Cancel</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form method="dialog" class="modal-backdrop">
|
<form method="dialog" class="modal-backdrop">
|
||||||
<button>close</button>
|
<button>close</button>
|
||||||
|
@ -812,9 +817,7 @@ declare global {
|
||||||
<dialog id="eventDetailsModal" class="modal">
|
<dialog id="eventDetailsModal" class="modal">
|
||||||
<div class="modal-box max-w-4xl">
|
<div class="modal-box max-w-4xl">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<div class="flex items-center gap-3">
|
<h3 class="font-bold text-lg">Event Details</h3>
|
||||||
<h3 class="font-bold text-lg" id="modalTitle">Event Details</h3>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-circle btn-ghost"
|
class="btn btn-circle btn-ghost"
|
||||||
onclick="eventDetailsModal.close()"
|
onclick="eventDetailsModal.close()"
|
||||||
|
@ -867,29 +870,41 @@ declare global {
|
||||||
</div>
|
</div>
|
||||||
<!-- Attendees list will be populated here -->
|
<!-- Attendees list will be populated here -->
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button>close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<!-- File Preview Section -->
|
<!-- Universal File Preview Modal -->
|
||||||
<div id="filePreviewSection" class="hidden">
|
<dialog id="filePreviewModal" class="modal">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="modal-box max-w-4xl">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<button
|
<div class="flex items-center gap-3">
|
||||||
class="btn btn-ghost btn-sm"
|
<button
|
||||||
onclick="backToFileList()"
|
class="btn btn-ghost btn-sm"
|
||||||
>
|
onclick="window.closeFilePreview()"
|
||||||
← Back
|
|
||||||
</button>
|
|
||||||
<h3 class="font-bold text-lg truncate" id="previewFileName">
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative" id="previewContainer">
|
|
||||||
<div
|
|
||||||
id="loadingSpinner"
|
|
||||||
class="absolute inset-0 flex items-center justify-center bg-base-200 bg-opacity-50 hidden"
|
|
||||||
>
|
>
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
← Back
|
||||||
</div>
|
</button>
|
||||||
<div id="previewContent" class="w-full"></div>
|
<h3 class="font-bold text-lg truncate" id="previewFileName">
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative" id="previewContainer">
|
||||||
|
<div
|
||||||
|
id="previewLoadingSpinner"
|
||||||
|
class="absolute inset-0 flex items-center justify-center bg-base-200 bg-opacity-50 hidden"
|
||||||
|
>
|
||||||
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
<div id="previewContent" class="w-full">
|
||||||
|
<FilePreview
|
||||||
|
client:load
|
||||||
|
url=""
|
||||||
|
filename=""
|
||||||
|
id="universalFilePreview"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -904,6 +919,10 @@ declare global {
|
||||||
import { Update } from "../pocketbase/Update";
|
import { Update } from "../pocketbase/Update";
|
||||||
import { FileManager } from "../pocketbase/FileManager";
|
import { FileManager } from "../pocketbase/FileManager";
|
||||||
import { SendLog } from "../pocketbase/SendLog";
|
import { SendLog } from "../pocketbase/SendLog";
|
||||||
|
import FilePreview from "../modals/FilePreview";
|
||||||
|
|
||||||
|
// Add file storage
|
||||||
|
const selectedFileStorage = new Map<string, File>();
|
||||||
|
|
||||||
interface AttendeeEntry {
|
interface AttendeeEntry {
|
||||||
user_id: string;
|
user_id: string;
|
||||||
|
@ -1839,28 +1858,10 @@ declare global {
|
||||||
freshEventData.files &&
|
freshEventData.files &&
|
||||||
freshEventData.files.length > 0
|
freshEventData.files.length > 0
|
||||||
) {
|
) {
|
||||||
currentFiles.innerHTML = freshEventData.files
|
currentFiles.innerHTML = updateFilePreviewButtons(
|
||||||
.map(
|
freshEventData.files,
|
||||||
(filename) => `
|
freshEventData.id
|
||||||
<div class="flex items-center justify-between p-2 bg-base-200 rounded-lg">
|
);
|
||||||
<span class="truncate">${filename}</span>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button type="button" class="btn btn-ghost btn-xs" onclick="window.previewFile('${fileManager.getFileUrl("events", freshEventData.id, filename)}', '${filename}')">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
|
|
||||||
<path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-ghost btn-xs text-error" onclick="window.deleteFile('${freshEventData.id}', '${filename}')">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)
|
|
||||||
.join("");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1871,4 +1872,492 @@ declare global {
|
||||||
// Show error toast or alert
|
// Show error toast or alert
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Update the previewFileInEditModal function
|
||||||
|
window.previewFileInEditModal = async function (
|
||||||
|
url: string,
|
||||||
|
filename: string
|
||||||
|
) {
|
||||||
|
const editFormSection = document.getElementById("editFormSection");
|
||||||
|
const previewSection = document.getElementById(
|
||||||
|
"editModalPreviewSection"
|
||||||
|
);
|
||||||
|
const editFilePreview = document.getElementById("editFilePreview");
|
||||||
|
const previewFileName = document.getElementById("editPreviewFileName");
|
||||||
|
const loadingSpinner = document.getElementById("editLoadingSpinner");
|
||||||
|
|
||||||
|
if (
|
||||||
|
!editFormSection ||
|
||||||
|
!previewSection ||
|
||||||
|
!editFilePreview ||
|
||||||
|
!previewFileName ||
|
||||||
|
!loadingSpinner
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Hide form and show preview section
|
||||||
|
editFormSection.classList.add("hidden");
|
||||||
|
previewSection.classList.remove("hidden");
|
||||||
|
previewFileName.textContent = filename;
|
||||||
|
|
||||||
|
// Show loading spinner
|
||||||
|
loadingSpinner.classList.remove("hidden");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Dispatch a custom event to update the FilePreview
|
||||||
|
const event = new CustomEvent("updateFilePreview", {
|
||||||
|
detail: { url, filename },
|
||||||
|
});
|
||||||
|
editFilePreview.dispatchEvent(event);
|
||||||
|
} finally {
|
||||||
|
// Hide loading spinner
|
||||||
|
loadingSpinner.classList.add("hidden");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the showFilePreview function
|
||||||
|
window.showFilePreview = function (file: { url: string; name: string }) {
|
||||||
|
const fileListSection = document.getElementById("filesContent");
|
||||||
|
const previewSection = document.getElementById("filePreviewSection");
|
||||||
|
const mainFilePreview = document.getElementById("mainFilePreview");
|
||||||
|
const previewFileName = document.getElementById("previewFileName");
|
||||||
|
|
||||||
|
if (
|
||||||
|
!fileListSection ||
|
||||||
|
!previewSection ||
|
||||||
|
!mainFilePreview ||
|
||||||
|
!previewFileName
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Hide file list and show preview section
|
||||||
|
fileListSection.classList.add("hidden");
|
||||||
|
previewSection.classList.remove("hidden");
|
||||||
|
previewFileName.textContent = file.name;
|
||||||
|
|
||||||
|
// Dispatch a custom event to update the FilePreview
|
||||||
|
const event = new CustomEvent("updateFilePreview", {
|
||||||
|
detail: { url: file.url, filename: file.name },
|
||||||
|
});
|
||||||
|
mainFilePreview.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add backToEditForm function
|
||||||
|
window.backToEditForm = function () {
|
||||||
|
const editFormSection = document.getElementById("editFormSection");
|
||||||
|
const previewSection = document.getElementById(
|
||||||
|
"editModalPreviewSection"
|
||||||
|
);
|
||||||
|
const editFilePreview = document.getElementById("editFilePreview");
|
||||||
|
const previewFileName = document.getElementById("editPreviewFileName");
|
||||||
|
|
||||||
|
if (
|
||||||
|
editFormSection &&
|
||||||
|
previewSection &&
|
||||||
|
editFilePreview &&
|
||||||
|
previewFileName
|
||||||
|
) {
|
||||||
|
editFormSection.classList.remove("hidden");
|
||||||
|
previewSection.classList.add("hidden");
|
||||||
|
// Reset the preview
|
||||||
|
const event = new CustomEvent("updateFilePreview", {
|
||||||
|
detail: { url: "", filename: "" },
|
||||||
|
});
|
||||||
|
editFilePreview.dispatchEvent(event);
|
||||||
|
previewFileName.textContent = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Universal file preview function
|
||||||
|
window.previewFile = function (url: string, filename: string) {
|
||||||
|
const modal = document.getElementById(
|
||||||
|
"filePreviewModal"
|
||||||
|
) as HTMLDialogElement;
|
||||||
|
const previewFileName = document.getElementById("previewFileName");
|
||||||
|
const filePreview = document.getElementById("universalFilePreview");
|
||||||
|
const loadingSpinner = document.getElementById("previewLoadingSpinner");
|
||||||
|
|
||||||
|
if (!modal || !previewFileName || !filePreview || !loadingSpinner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Show modal and update filename
|
||||||
|
modal.showModal();
|
||||||
|
previewFileName.textContent = filename;
|
||||||
|
|
||||||
|
// Show loading spinner
|
||||||
|
loadingSpinner.classList.remove("hidden");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update the FilePreview component
|
||||||
|
const event = new CustomEvent("updateFilePreview", {
|
||||||
|
detail: { url, filename },
|
||||||
|
});
|
||||||
|
filePreview.dispatchEvent(event);
|
||||||
|
} finally {
|
||||||
|
// Hide loading spinner
|
||||||
|
loadingSpinner.classList.add("hidden");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close file preview
|
||||||
|
window.closeFilePreview = function () {
|
||||||
|
const modal = document.getElementById(
|
||||||
|
"filePreviewModal"
|
||||||
|
) as HTMLDialogElement;
|
||||||
|
const filePreview = document.getElementById("universalFilePreview");
|
||||||
|
|
||||||
|
if (modal && filePreview) {
|
||||||
|
// Reset the preview
|
||||||
|
const event = new CustomEvent("updateFilePreview", {
|
||||||
|
detail: { url: "", filename: "" },
|
||||||
|
});
|
||||||
|
filePreview.dispatchEvent(event);
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update all file preview buttons to use the universal preview
|
||||||
|
function updateFilePreviewButtons(files: string[], eventId: string) {
|
||||||
|
return files
|
||||||
|
.map(
|
||||||
|
(filename) => `
|
||||||
|
<div class="flex items-center justify-between p-2 bg-base-200 rounded-lg">
|
||||||
|
<span class="truncate">${filename}</span>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button type="button" class="btn btn-ghost btn-xs" onclick="window.previewFile('${fileManager.getFileUrl("events", eventId, filename)}', '${filename}')">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
|
||||||
|
<path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-ghost btn-xs text-error" onclick="window.deleteFile('${eventId}', '${filename}')">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the openDetailsModal function to use the universal preview
|
||||||
|
window.openDetailsModal = function (event: Event) {
|
||||||
|
const modal = document.getElementById(
|
||||||
|
"eventDetailsModal"
|
||||||
|
) as HTMLDialogElement;
|
||||||
|
const filesContent = document.getElementById("filesContent");
|
||||||
|
const attendeesContent = document.getElementById("attendeesContent");
|
||||||
|
|
||||||
|
if (!modal || !filesContent || !attendeesContent) return;
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.showModal();
|
||||||
|
|
||||||
|
// Update files list
|
||||||
|
if (event.files && event.files.length > 0) {
|
||||||
|
filesContent.innerHTML = `
|
||||||
|
<div class="space-y-2">
|
||||||
|
${updateFilePreviewButtons(event.files, event.id)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
filesContent.innerHTML = `
|
||||||
|
<div class="text-center py-8 text-base-content/70">
|
||||||
|
<p>No files attached to this event</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add file input change handler to show selected files
|
||||||
|
document
|
||||||
|
.getElementById("editEventFiles")
|
||||||
|
?.addEventListener("change", function (e) {
|
||||||
|
const fileInput = e.target as HTMLInputElement;
|
||||||
|
const newFiles = document.getElementById("newFiles");
|
||||||
|
|
||||||
|
if (newFiles && fileInput.files) {
|
||||||
|
// Get existing files if any
|
||||||
|
const existingFiles = newFiles.querySelectorAll(".file-item");
|
||||||
|
const existingFilesArray = Array.from(existingFiles).map(
|
||||||
|
(item) => {
|
||||||
|
const nameSpan = item.querySelector(".file-name");
|
||||||
|
return nameSpan ? nameSpan.textContent : "";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store new files in the storage and update UI
|
||||||
|
Array.from(fileInput.files)
|
||||||
|
.filter((file) => !existingFilesArray.includes(file.name))
|
||||||
|
.forEach((file) => {
|
||||||
|
selectedFileStorage.set(file.name, file);
|
||||||
|
const fileDiv = document.createElement("div");
|
||||||
|
fileDiv.className =
|
||||||
|
"flex items-center justify-between p-2 bg-base-200 rounded-lg file-item";
|
||||||
|
fileDiv.innerHTML = `
|
||||||
|
<span class="truncate file-name">${file.name}</span>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="badge badge-primary">New</div>
|
||||||
|
<button type="button" class="btn btn-ghost btn-xs text-error" onclick="this.closest('.file-item').remove(); selectedFileStorage.delete('${file.name}');">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
newFiles.appendChild(fileDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the file input
|
||||||
|
fileInput.value = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Modify form submission handler to use selectedFileStorage
|
||||||
|
document
|
||||||
|
.getElementById("editEventForm")
|
||||||
|
?.addEventListener("submit", async function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const form = e.target as HTMLFormElement;
|
||||||
|
// Get the submit and cancel buttons from the modal-action div
|
||||||
|
const modalAction = document.querySelector(".modal-action");
|
||||||
|
const submitButton = modalAction?.querySelector(
|
||||||
|
".btn-primary"
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
const cancelButton = modalAction?.querySelector(
|
||||||
|
".btn:not(.btn-primary)"
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
|
||||||
|
if (!submitButton || !cancelButton) {
|
||||||
|
console.error("Could not find submit or cancel buttons");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalText = submitButton.innerHTML;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const eventId = (
|
||||||
|
document.getElementById("editEventId") as HTMLInputElement
|
||||||
|
)?.value;
|
||||||
|
|
||||||
|
// Get files from storage
|
||||||
|
const selectedFiles = Array.from(selectedFileStorage.values());
|
||||||
|
|
||||||
|
// Disable buttons and show loading state
|
||||||
|
submitButton.disabled = true;
|
||||||
|
cancelButton.disabled = true;
|
||||||
|
submitButton.innerHTML = `
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
<span>Saving...</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
window.showLoading?.();
|
||||||
|
|
||||||
|
// Prepare event data
|
||||||
|
const eventData = {
|
||||||
|
event_name: formData.get("editEventName"),
|
||||||
|
event_code: formData.get("editEventCode"),
|
||||||
|
event_description: formData.get("editEventDescription"),
|
||||||
|
location: formData.get("editEventLocation"),
|
||||||
|
points_to_reward: Number(formData.get("editEventPoints")),
|
||||||
|
start_date: new Date(
|
||||||
|
formData.get("editEventStartDate") as string
|
||||||
|
).toISOString(),
|
||||||
|
end_date: new Date(
|
||||||
|
formData.get("editEventEndDate") as string
|
||||||
|
).toISOString(),
|
||||||
|
published: formData.get("editEventPublished") === "on",
|
||||||
|
has_food: formData.get("editEventHasFood") === "on",
|
||||||
|
};
|
||||||
|
|
||||||
|
let updatedEvent;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (eventId) {
|
||||||
|
// Update existing event
|
||||||
|
submitButton.innerHTML = `
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
<span>Updating event...</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
updatedEvent = await update.updateFields(
|
||||||
|
"events",
|
||||||
|
eventId,
|
||||||
|
eventData
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle file uploads if any
|
||||||
|
if (selectedFiles.length > 0) {
|
||||||
|
submitButton.innerHTML = `
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
<span>Uploading files (0/${selectedFiles.length})...</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
await fileManager.appendFiles(
|
||||||
|
"events",
|
||||||
|
eventId,
|
||||||
|
"files",
|
||||||
|
selectedFiles
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendLog.send(
|
||||||
|
"update",
|
||||||
|
"event",
|
||||||
|
`Updated event: ${eventData.event_name}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Create new event
|
||||||
|
submitButton.innerHTML = `
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
<span>Creating event...</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const pb = auth.getPocketBase();
|
||||||
|
const newEvent = await pb
|
||||||
|
.collection("events")
|
||||||
|
.create(eventData);
|
||||||
|
|
||||||
|
// Handle file uploads if any
|
||||||
|
if (selectedFiles.length > 0) {
|
||||||
|
submitButton.innerHTML = `
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
<span>Uploading files (0/${selectedFiles.length})...</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
await fileManager.uploadFiles(
|
||||||
|
"events",
|
||||||
|
newEvent.id,
|
||||||
|
"files",
|
||||||
|
selectedFiles
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendLog.send(
|
||||||
|
"create",
|
||||||
|
"event",
|
||||||
|
`Created event: ${eventData.event_name}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success state briefly
|
||||||
|
submitButton.innerHTML = `
|
||||||
|
<div class="flex items-center gap-2 text-success">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
<span>Saved!</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
// Close modal and refresh events list
|
||||||
|
const modal = document.getElementById(
|
||||||
|
"editEventModal"
|
||||||
|
) as HTMLDialogElement;
|
||||||
|
modal?.close();
|
||||||
|
|
||||||
|
// Force cache refresh and update events list
|
||||||
|
lastCacheUpdate = 0; // Reset cache timestamp to force refresh
|
||||||
|
await refreshCache(); // Refresh the cache
|
||||||
|
await fetchEvents(); // Update the UI
|
||||||
|
|
||||||
|
// Clear form inputs
|
||||||
|
const formFileInput = document.getElementById(
|
||||||
|
"editEventFiles"
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const newFiles = document.getElementById("newFiles");
|
||||||
|
if (formFileInput) formFileInput.value = "";
|
||||||
|
if (newFiles) newFiles.innerHTML = "";
|
||||||
|
|
||||||
|
// Clear storage after successful upload
|
||||||
|
selectedFileStorage.clear();
|
||||||
|
} catch (error) {
|
||||||
|
throw error; // Re-throw to be caught by outer try-catch
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to save event:", error);
|
||||||
|
// Show error message in the button with icon
|
||||||
|
submitButton.innerHTML = `
|
||||||
|
<div class="flex items-center gap-2 text-error">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
<span>Failed</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
// Show detailed error to user
|
||||||
|
alert("Failed to save event. Please try again.");
|
||||||
|
} finally {
|
||||||
|
// Reset button state
|
||||||
|
submitButton.disabled = false;
|
||||||
|
cancelButton.disabled = false;
|
||||||
|
submitButton.innerHTML = originalText;
|
||||||
|
window.hideLoading?.();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear storage when modal is closed
|
||||||
|
document.getElementById("editEventModal")?.addEventListener("close", () => {
|
||||||
|
selectedFileStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add delete file handler
|
||||||
|
window.deleteFile = async function (eventId: string, filename: string) {
|
||||||
|
if (!confirm("Are you sure you want to delete this file?")) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.showLoading?.();
|
||||||
|
const pb = auth.getPocketBase();
|
||||||
|
|
||||||
|
// Get current event data
|
||||||
|
const event = await pb.collection("events").getOne(eventId);
|
||||||
|
|
||||||
|
// Filter out the file to delete
|
||||||
|
const updatedFiles = event.files.filter(
|
||||||
|
(f: string) => f !== filename
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the event with the new files array
|
||||||
|
await pb.collection("events").update(eventId, {
|
||||||
|
files: updatedFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await sendLog.send(
|
||||||
|
"delete",
|
||||||
|
"event_file",
|
||||||
|
`Deleted file ${filename} from event ${event.event_name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Refresh the current files display
|
||||||
|
const currentFiles = document.getElementById("currentFiles");
|
||||||
|
if (currentFiles && updatedFiles.length > 0) {
|
||||||
|
currentFiles.innerHTML = updateFilePreviewButtons(
|
||||||
|
updatedFiles,
|
||||||
|
eventId
|
||||||
|
);
|
||||||
|
} else if (currentFiles) {
|
||||||
|
currentFiles.innerHTML = `
|
||||||
|
<div class="text-center py-4 text-base-content/70">
|
||||||
|
<p>No files attached</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to delete file:", error);
|
||||||
|
alert("Failed to delete file. Please try again.");
|
||||||
|
} finally {
|
||||||
|
window.hideLoading?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
415
src/components/modals/FilePreview.tsx
Normal file
415
src/components/modals/FilePreview.tsx
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
import React from 'react';
|
||||||
|
import hljs from 'highlight.js';
|
||||||
|
import 'highlight.js/styles/github-dark.css';
|
||||||
|
|
||||||
|
interface FilePreviewProps {
|
||||||
|
url: string;
|
||||||
|
filename: string;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSXElement = React.ReactElement;
|
||||||
|
|
||||||
|
const FilePreview: React.FC<FilePreviewProps> = ({ url: initialUrl, filename: initialFilename, id }) => {
|
||||||
|
const [url, setUrl] = React.useState(initialUrl);
|
||||||
|
const [filename, setFilename] = React.useState(initialFilename);
|
||||||
|
const [visibleLines, setVisibleLines] = React.useState(20);
|
||||||
|
const elementRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Constants for text preview
|
||||||
|
const INITIAL_LINES = 20;
|
||||||
|
const INCREMENT_LINES = 50;
|
||||||
|
const MAX_CHARS_PER_LINE = 120;
|
||||||
|
const TRUNCATION_MESSAGE = '...';
|
||||||
|
|
||||||
|
// Determine file type from extension
|
||||||
|
const fileExtension = filename.split('.').pop()?.toLowerCase() || '';
|
||||||
|
|
||||||
|
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(fileExtension);
|
||||||
|
const isPDF = fileExtension === 'pdf';
|
||||||
|
const isCode = [
|
||||||
|
'py', 'js', 'jsx', 'ts', 'tsx', 'html', 'htm', 'css', 'scss',
|
||||||
|
'java', 'c', 'cpp', 'cs', 'go', 'rs', 'sql', 'php', 'rb',
|
||||||
|
'swift', 'kt', 'sh', 'bash', 'yaml', 'yml', 'json', 'md',
|
||||||
|
'astro', 'vue', 'svelte', 'xml', 'toml', 'ini', 'env',
|
||||||
|
'graphql', 'prisma', 'dockerfile', 'nginx'
|
||||||
|
].includes(fileExtension);
|
||||||
|
const isText = isCode || ['txt', 'log', 'csv'].includes(fileExtension);
|
||||||
|
const isVideo = ['mp4', 'webm', 'mov', 'avi', 'mkv'].includes(fileExtension);
|
||||||
|
const isAudio = ['mp3', 'wav', 'm4a', 'ogg'].includes(fileExtension);
|
||||||
|
|
||||||
|
// Function to highlight code using highlight.js
|
||||||
|
const highlightCode = (code: string, language?: string): string => {
|
||||||
|
if (!language) return code;
|
||||||
|
try {
|
||||||
|
return hljs.highlight(code, { language }).value;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to highlight code for language ${language}:`, error);
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to get the appropriate language for highlight.js
|
||||||
|
const getHighlightLanguage = (ext: string): string | undefined => {
|
||||||
|
// Map file extensions to highlight.js languages
|
||||||
|
const languageMap: { [key: string]: string } = {
|
||||||
|
'py': 'python',
|
||||||
|
'js': 'javascript',
|
||||||
|
'jsx': 'javascript',
|
||||||
|
'ts': 'typescript',
|
||||||
|
'tsx': 'typescript',
|
||||||
|
'html': 'html',
|
||||||
|
'htm': 'html',
|
||||||
|
'css': 'css',
|
||||||
|
'scss': 'scss',
|
||||||
|
'java': 'java',
|
||||||
|
'c': 'c',
|
||||||
|
'cpp': 'cpp',
|
||||||
|
'cs': 'csharp',
|
||||||
|
'go': 'go',
|
||||||
|
'rs': 'rust',
|
||||||
|
'sql': 'sql',
|
||||||
|
'php': 'php',
|
||||||
|
'rb': 'ruby',
|
||||||
|
'swift': 'swift',
|
||||||
|
'kt': 'kotlin',
|
||||||
|
'sh': 'bash',
|
||||||
|
'bash': 'bash',
|
||||||
|
'yaml': 'yaml',
|
||||||
|
'yml': 'yaml',
|
||||||
|
'json': 'json',
|
||||||
|
'md': 'markdown',
|
||||||
|
'xml': 'xml',
|
||||||
|
'toml': 'ini',
|
||||||
|
'ini': 'ini',
|
||||||
|
'dockerfile': 'dockerfile',
|
||||||
|
'prisma': 'prisma',
|
||||||
|
'graphql': 'graphql'
|
||||||
|
};
|
||||||
|
return languageMap[ext];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to truncate text content
|
||||||
|
const truncateContent = (text: string, maxLines: number): string => {
|
||||||
|
const lines = text.split('\n');
|
||||||
|
if (lines.length <= maxLines) return text;
|
||||||
|
|
||||||
|
const truncatedLines = lines.slice(0, maxLines).map(line =>
|
||||||
|
line.length > MAX_CHARS_PER_LINE
|
||||||
|
? line.slice(0, MAX_CHARS_PER_LINE) + '...'
|
||||||
|
: line
|
||||||
|
);
|
||||||
|
return truncatedLines.join('\n') + '\n' + TRUNCATION_MESSAGE;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset visible lines when file changes
|
||||||
|
React.useEffect(() => {
|
||||||
|
setVisibleLines(INITIAL_LINES);
|
||||||
|
}, [url]);
|
||||||
|
|
||||||
|
// Function to show more lines
|
||||||
|
const showMoreLines = () => {
|
||||||
|
setVisibleLines(prev => prev + INCREMENT_LINES);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to reset to initial view
|
||||||
|
const resetView = () => {
|
||||||
|
setVisibleLines(INITIAL_LINES);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Listen for custom events to update the preview
|
||||||
|
React.useEffect(() => {
|
||||||
|
const element = elementRef.current;
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
const handleUpdatePreview = (e: CustomEvent<{ url: string; filename: string }>) => {
|
||||||
|
setUrl(e.detail.url);
|
||||||
|
setFilename(e.detail.filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
element.addEventListener('updateFilePreview', handleUpdatePreview as EventListener);
|
||||||
|
return () => {
|
||||||
|
element.removeEventListener('updateFilePreview', handleUpdatePreview as EventListener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Update state when props change
|
||||||
|
React.useEffect(() => {
|
||||||
|
setUrl(initialUrl);
|
||||||
|
setFilename(initialFilename);
|
||||||
|
}, [initialUrl, initialFilename]);
|
||||||
|
|
||||||
|
// For text files, we need to fetch and display the content
|
||||||
|
const [textContent, setTextContent] = React.useState<string>('');
|
||||||
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
async function fetchTextContent() {
|
||||||
|
if (!isText) return;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const text = await response.text();
|
||||||
|
setTextContent(text);
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to load text content');
|
||||||
|
console.error('Error fetching text content:', err);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isText) {
|
||||||
|
fetchTextContent();
|
||||||
|
}
|
||||||
|
}, [url, isText]);
|
||||||
|
|
||||||
|
// Function to parse CSV text into array
|
||||||
|
const parseCSV = (text: string): string[][] => {
|
||||||
|
const rows = text.split(/\r?\n/).filter(row => row.trim());
|
||||||
|
return rows.map(row => {
|
||||||
|
// Handle both quoted and unquoted CSV
|
||||||
|
const matches = row.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
|
||||||
|
return matches.map(cell => cell.replace(/^"|"$/g, '').trim());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to format JSON with syntax highlighting
|
||||||
|
const formatJSON = (text: string): string => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(text);
|
||||||
|
return highlightCode(JSON.stringify(parsed, null, 2), 'json');
|
||||||
|
} catch {
|
||||||
|
return text; // Return original text if not valid JSON
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to render CSV as table
|
||||||
|
const renderCSVTable = (csvData: string[][]): JSXElement => {
|
||||||
|
if (csvData.length === 0) return <p>No data</p>;
|
||||||
|
|
||||||
|
const headers = csvData[0];
|
||||||
|
const allRows = csvData.slice(1);
|
||||||
|
const rows = allRows.slice(0, visibleLines);
|
||||||
|
const remainingRows = allRows.length - visibleLines;
|
||||||
|
const hasMore = remainingRows > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="overflow-x-auto max-h-[60vh] overflow-y-auto">
|
||||||
|
<table className="table table-zebra w-full">
|
||||||
|
<thead className="sticky top-0 z-10">
|
||||||
|
<tr>
|
||||||
|
{headers.map((header, i) => (
|
||||||
|
<th key={i} className="bg-base-200">{header}</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{rows.map((row, i) => (
|
||||||
|
<tr key={i}>
|
||||||
|
{row.map((cell, j) => (
|
||||||
|
<td key={j}>{cell}</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
{hasMore && (
|
||||||
|
<button
|
||||||
|
className="btn btn-ghost btn-sm"
|
||||||
|
onClick={showMoreLines}
|
||||||
|
>
|
||||||
|
Show More ({Math.min(remainingRows, INCREMENT_LINES)} of {remainingRows} rows)
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{visibleLines > INITIAL_LINES && (
|
||||||
|
<button
|
||||||
|
className="btn btn-ghost btn-sm"
|
||||||
|
onClick={resetView}
|
||||||
|
>
|
||||||
|
Reset View
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to render text content based on file type
|
||||||
|
const renderTextContent = (): JSXElement => {
|
||||||
|
if (!textContent) return <p>No content</p>;
|
||||||
|
|
||||||
|
if (fileExtension === 'csv') {
|
||||||
|
const csvData = parseCSV(textContent);
|
||||||
|
return renderCSVTable(csvData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = textContent.split('\n');
|
||||||
|
const content = truncateContent(textContent, visibleLines);
|
||||||
|
const remainingLines = lines.length - visibleLines;
|
||||||
|
const hasMore = remainingLines > 0;
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
if (isCode) {
|
||||||
|
const language = getHighlightLanguage(fileExtension);
|
||||||
|
const highlightedCode = highlightCode(content, language);
|
||||||
|
return (
|
||||||
|
<code
|
||||||
|
className="text-sm font-mono"
|
||||||
|
dangerouslySetInnerHTML={{ __html: highlightedCode }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <code className="text-sm font-mono">{content}</code>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="max-h-[60vh] overflow-y-auto">
|
||||||
|
<pre className="whitespace-pre-wrap bg-base-200 p-4 rounded-lg overflow-x-auto">
|
||||||
|
{renderContent()}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
{hasMore && (
|
||||||
|
<button
|
||||||
|
className="btn btn-ghost btn-sm"
|
||||||
|
onClick={showMoreLines}
|
||||||
|
>
|
||||||
|
Show More ({Math.min(remainingLines, INCREMENT_LINES)} of {remainingLines} lines)
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{visibleLines > INITIAL_LINES && (
|
||||||
|
<button
|
||||||
|
className="btn btn-ghost btn-sm"
|
||||||
|
onClick={resetView}
|
||||||
|
>
|
||||||
|
Reset View
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div ref={elementRef} id={id} className="flex justify-center items-center p-8">
|
||||||
|
<span className="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div ref={elementRef} id={id} className="alert alert-error">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isImage) {
|
||||||
|
return (
|
||||||
|
<div ref={elementRef} id={id} className="relative w-full max-h-[60vh] overflow-y-auto">
|
||||||
|
<img
|
||||||
|
src={url}
|
||||||
|
alt={filename}
|
||||||
|
className="max-w-full h-auto rounded-lg"
|
||||||
|
onError={() => setError('Failed to load image')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPDF) {
|
||||||
|
return (
|
||||||
|
<div ref={elementRef} id={id} className="relative w-full h-[60vh]">
|
||||||
|
<iframe
|
||||||
|
src={url}
|
||||||
|
className="w-full h-full rounded-lg"
|
||||||
|
title={filename}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVideo) {
|
||||||
|
return (
|
||||||
|
<div ref={elementRef} id={id} className="relative w-full max-h-[60vh]">
|
||||||
|
<video
|
||||||
|
controls
|
||||||
|
className="w-full rounded-lg"
|
||||||
|
onError={() => setError('Failed to load video')}
|
||||||
|
>
|
||||||
|
<source src={url} type={`video/${fileExtension}`} />
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAudio) {
|
||||||
|
return (
|
||||||
|
<div ref={elementRef} id={id} className="relative w-full">
|
||||||
|
<audio
|
||||||
|
controls
|
||||||
|
className="w-full"
|
||||||
|
onError={() => setError('Failed to load audio')}
|
||||||
|
>
|
||||||
|
<source src={url} type={`audio/${fileExtension}`} />
|
||||||
|
Your browser does not support the audio tag.
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isText) {
|
||||||
|
return (
|
||||||
|
<div ref={elementRef} id={id} className="relative w-full">
|
||||||
|
{renderTextContent()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default case for unsupported file types
|
||||||
|
return (
|
||||||
|
<div ref={elementRef} id={id} className="text-center py-8">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-12 w-12 mx-auto mb-4 opacity-50"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<p className="text-base-content/70">Preview not available for this file type</p>
|
||||||
|
<a
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="btn btn-primary btn-sm mt-4"
|
||||||
|
>
|
||||||
|
Download File
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilePreview;
|
|
@ -1,444 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import JSZip from 'jszip';
|
|
||||||
|
|
||||||
interface FileType {
|
|
||||||
url: string;
|
|
||||||
type: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FileViewerModalProps {
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
files: FileType | FileType[];
|
|
||||||
modalId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a wrapper component that listens to custom events
|
|
||||||
export const FileViewerModalWrapper: React.FC = () => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [files, setFiles] = useState<FileType[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let mounted = true;
|
|
||||||
|
|
||||||
// Listen for custom events to open/close modal and set files
|
|
||||||
const handleShowFiles = (event: CustomEvent) => {
|
|
||||||
if (mounted) {
|
|
||||||
const { files } = event.detail;
|
|
||||||
setFiles(Array.isArray(files) ? files : [files]);
|
|
||||||
setIsOpen(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add event listeners
|
|
||||||
window.addEventListener('showFileViewer' as any, handleShowFiles);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
return () => {
|
|
||||||
mounted = false;
|
|
||||||
window.removeEventListener('showFileViewer' as any, handleShowFiles);
|
|
||||||
// Reset state on unmount
|
|
||||||
setIsOpen(false);
|
|
||||||
setFiles([]);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle tab visibility changes
|
|
||||||
useEffect(() => {
|
|
||||||
const handleVisibilityChange = () => {
|
|
||||||
if (document.hidden) {
|
|
||||||
setIsOpen(false);
|
|
||||||
setFiles([]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setIsOpen(false);
|
|
||||||
setFiles([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only render the modal if we have files and it should be open
|
|
||||||
if (!isOpen || files.length === 0) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FileViewerModal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={handleClose}
|
|
||||||
files={files}
|
|
||||||
modalId="file-viewer"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const FileViewerModal: React.FC<FileViewerModalProps> = ({ isOpen, onClose, files, modalId = 'file-viewer' }) => {
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [selectedFile, setSelectedFile] = useState<FileType | null>(null);
|
|
||||||
const [showPreview, setShowPreview] = useState(false);
|
|
||||||
const [downloadingAll, setDownloadingAll] = useState(false);
|
|
||||||
|
|
||||||
const fileArray = Array.isArray(files) ? files : [files];
|
|
||||||
|
|
||||||
// Reset state when modal closes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen) {
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
setSelectedFile(null);
|
|
||||||
setShowPreview(false);
|
|
||||||
}
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
// Helper function to check if file type is previewable
|
|
||||||
const isPreviewableType = (fileType: string): boolean => {
|
|
||||||
return (
|
|
||||||
fileType.startsWith('image/') ||
|
|
||||||
fileType.startsWith('video/') ||
|
|
||||||
fileType.startsWith('audio/') ||
|
|
||||||
fileType === 'application/pdf' ||
|
|
||||||
fileType.startsWith('text/') ||
|
|
||||||
fileType === 'application/json'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen) {
|
|
||||||
// Only show file directly if there's exactly one file
|
|
||||||
if (fileArray.length === 1) {
|
|
||||||
const fileToShow = fileArray[0];
|
|
||||||
setSelectedFile(fileToShow);
|
|
||||||
setShowPreview(true);
|
|
||||||
setLoading(isPreviewableType(fileToShow.type));
|
|
||||||
} else {
|
|
||||||
// For multiple files, show the file browser
|
|
||||||
setShowPreview(false);
|
|
||||||
setSelectedFile(null);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
setError(null);
|
|
||||||
}
|
|
||||||
}, [isOpen, files]);
|
|
||||||
|
|
||||||
const handleLoadSuccess = () => {
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLoadError = () => {
|
|
||||||
setLoading(false);
|
|
||||||
setError('Failed to load file');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFileSelect = (file: FileType) => {
|
|
||||||
setSelectedFile(file);
|
|
||||||
setShowPreview(true);
|
|
||||||
setLoading(isPreviewableType(file.type));
|
|
||||||
setError(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBackToList = () => {
|
|
||||||
setShowPreview(false);
|
|
||||||
setSelectedFile(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to download all files as zip
|
|
||||||
const downloadAllFiles = async () => {
|
|
||||||
if (fileArray.length === 0) return;
|
|
||||||
|
|
||||||
setDownloadingAll(true);
|
|
||||||
const zip = new JSZip();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Download all files
|
|
||||||
const filePromises = fileArray.map(async (file) => {
|
|
||||||
const response = await fetch(file.url);
|
|
||||||
const blob = await response.blob();
|
|
||||||
zip.file(file.name, blob);
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(filePromises);
|
|
||||||
|
|
||||||
// Generate and download zip
|
|
||||||
const content = await zip.generateAsync({ type: "blob" });
|
|
||||||
const zipUrl = URL.createObjectURL(content);
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = zipUrl;
|
|
||||||
link.download = 'files.zip';
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
URL.revokeObjectURL(zipUrl);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to download files:', err);
|
|
||||||
setError('Failed to download files');
|
|
||||||
} finally {
|
|
||||||
setDownloadingAll(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderFileContent = (file: FileType) => {
|
|
||||||
const fileType = file.type.toLowerCase();
|
|
||||||
|
|
||||||
// If not a previewable type, don't show loading state
|
|
||||||
if (!isPreviewableType(fileType)) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center justify-center p-8">
|
|
||||||
<div className="text-4xl mb-4">📄</div>
|
|
||||||
<p className="text-center">
|
|
||||||
This file type ({file.type}) cannot be previewed.
|
|
||||||
<br />
|
|
||||||
<a
|
|
||||||
href={file.url}
|
|
||||||
download={file.name}
|
|
||||||
className="btn btn-primary mt-4"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Open in New Tab
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileType.startsWith('image/')) {
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
src={file.url}
|
|
||||||
alt={file.name}
|
|
||||||
className="max-w-full max-h-[70vh] object-contain"
|
|
||||||
onLoad={handleLoadSuccess}
|
|
||||||
onError={handleLoadError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileType.startsWith('video/')) {
|
|
||||||
return (
|
|
||||||
<video
|
|
||||||
controls
|
|
||||||
className="max-w-full max-h-[70vh]"
|
|
||||||
onLoadedData={handleLoadSuccess}
|
|
||||||
onError={handleLoadError}
|
|
||||||
>
|
|
||||||
<source src={file.url} type={file.type} />
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileType === 'application/pdf') {
|
|
||||||
return (
|
|
||||||
<iframe
|
|
||||||
src={file.url}
|
|
||||||
className="w-full h-[70vh]"
|
|
||||||
onLoad={handleLoadSuccess}
|
|
||||||
onError={handleLoadError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileType.startsWith('text/') || fileType === 'application/json') {
|
|
||||||
return (
|
|
||||||
<iframe
|
|
||||||
src={file.url}
|
|
||||||
className="w-full h-[70vh] font-mono"
|
|
||||||
onLoad={handleLoadSuccess}
|
|
||||||
onError={handleLoadError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileType.startsWith('audio/')) {
|
|
||||||
return (
|
|
||||||
<audio
|
|
||||||
controls
|
|
||||||
className="w-full"
|
|
||||||
onLoadedData={handleLoadSuccess}
|
|
||||||
onError={handleLoadError}
|
|
||||||
>
|
|
||||||
<source src={file.url} type={file.type} />
|
|
||||||
Your browser does not support the audio element.
|
|
||||||
</audio>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center justify-center p-8">
|
|
||||||
<div className="text-4xl mb-4">📄</div>
|
|
||||||
<p className="text-center">
|
|
||||||
This file type ({file.type}) cannot be previewed.
|
|
||||||
<br />
|
|
||||||
<a
|
|
||||||
href={file.url}
|
|
||||||
download={file.name}
|
|
||||||
className="btn btn-primary mt-4"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Open in New Tab
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderFileList = () => {
|
|
||||||
return (
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h3 className="font-bold text-lg">Files ({fileArray.length})</h3>
|
|
||||||
{fileArray.length > 1 && (
|
|
||||||
<button
|
|
||||||
className={`btn btn-primary btn-sm ${downloadingAll ? 'loading' : ''}`}
|
|
||||||
onClick={downloadAllFiles}
|
|
||||||
disabled={downloadingAll}
|
|
||||||
>
|
|
||||||
{!downloadingAll && (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
className="h-4 w-4 mr-2"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
{downloadingAll ? 'Preparing Download...' : 'Download All'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="overflow-y-auto max-h-[60vh]">
|
|
||||||
{fileArray.map((file, index) => (
|
|
||||||
<div
|
|
||||||
key={`${file.name}-${index}`}
|
|
||||||
className="flex items-center justify-between p-4 hover:bg-base-200 rounded-lg cursor-pointer mb-2"
|
|
||||||
onClick={() => handleFileSelect(file)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="text-2xl">
|
|
||||||
{file.type.startsWith('image/') ? '🖼️' :
|
|
||||||
file.type.startsWith('video/') ? '🎥' :
|
|
||||||
file.type.startsWith('audio/') ? '🎵' :
|
|
||||||
file.type === 'application/pdf' ? '📄' :
|
|
||||||
file.type.startsWith('text/') ? '📝' : '📎'}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="font-semibold">{file.name}</div>
|
|
||||||
<div className="text-sm opacity-70">{file.type}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button className="btn btn-ghost btn-sm">
|
|
||||||
Preview
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isOpen) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id={modalId}
|
|
||||||
className="modal-toggle"
|
|
||||||
checked={isOpen}
|
|
||||||
onChange={onClose}
|
|
||||||
/>
|
|
||||||
<div className="modal ">
|
|
||||||
<div className="modal-box max-w-4xl">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
{showPreview && selectedFile ? (
|
|
||||||
<>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
{fileArray.length > 1 && (
|
|
||||||
<button
|
|
||||||
className="btn btn-ghost btn-sm"
|
|
||||||
onClick={handleBackToList}
|
|
||||||
>
|
|
||||||
← Back
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<h3 className="font-bold text-lg truncate">{selectedFile.name}</h3>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<h3 className="font-bold text-lg">File Browser</h3>
|
|
||||||
)}
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<button
|
|
||||||
className="btn btn-circle btn-ghost"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
className="h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M6 18L18 6M6 6l12 12"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative">
|
|
||||||
{loading && showPreview && (
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-base-200 bg-opacity-50">
|
|
||||||
<span className="loading loading-spinner loading-lg"></span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{error ? (
|
|
||||||
<div className="alert alert-error">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
className="stroke-current shrink-0 h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span>{error}</span>
|
|
||||||
</div>
|
|
||||||
) : showPreview && selectedFile ? (
|
|
||||||
renderFileContent(selectedFile)
|
|
||||||
) : (
|
|
||||||
renderFileList()
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<label className="modal-backdrop" htmlFor={modalId}>
|
|
||||||
Close
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FileViewerModalWrapper;
|
|
|
@ -3,3 +3,46 @@ officer:
|
||||||
- "IEEE Officer"
|
- "IEEE Officer"
|
||||||
- "IEEE Executive"
|
- "IEEE Executive"
|
||||||
- "IEEE Administrator"
|
- "IEEE Administrator"
|
||||||
|
|
||||||
|
supported_file_types:
|
||||||
|
- "image"
|
||||||
|
- "video"
|
||||||
|
- "audio"
|
||||||
|
- "pdf"
|
||||||
|
- "doc"
|
||||||
|
- "docx"
|
||||||
|
- "ppt"
|
||||||
|
- "pptx"
|
||||||
|
- "txt"
|
||||||
|
- "csv"
|
||||||
|
- "mp4"
|
||||||
|
- "mov"
|
||||||
|
- "avi"
|
||||||
|
- "mkv"
|
||||||
|
- "webm"
|
||||||
|
- "m4a"
|
||||||
|
- "mp3"
|
||||||
|
- "wav"
|
||||||
|
- "m4v"
|
||||||
|
- "m4b"
|
||||||
|
- "m4p"
|
||||||
|
- "m4v"
|
||||||
|
- "m4b"
|
||||||
|
- "png"
|
||||||
|
- "jpg"
|
||||||
|
- "jpeg"
|
||||||
|
- "gif"
|
||||||
|
- "bmp"
|
||||||
|
- "tiff"
|
||||||
|
- "ico"
|
||||||
|
- "webp"
|
||||||
|
- "heic"
|
||||||
|
- "heif"
|
||||||
|
- "hevc"
|
||||||
|
- "json"
|
||||||
|
- "md"
|
||||||
|
- "csv"
|
||||||
|
- "txt"
|
||||||
|
- "xml"
|
||||||
|
- "yaml"
|
||||||
|
- "yml"
|
||||||
|
|
Loading…
Reference in a new issue