28 Commits

Author SHA1 Message Date
Emrik Östling
24394ca3c5 Merge pull request #226 from C4illin/release-please--branches--main--components--convertx-frontend
chore(main): release 0.12.0
2025-03-06 18:47:25 +01:00
Emrik Östling
10ff0b464a chore(main): release 0.12.0 2025-03-06 18:17:28 +01:00
C4illin
9263d17609 feat: replace exec with execFile 2025-03-06 18:16:51 +01:00
Emrik Östling
c1b75a13fd chore: sanitize filename 2025-03-04 09:23:06 +01:00
Emrik Östling
a8ed60d48f Merge pull request #233 from Lacni135/feature-progress
Added progress bar for file upload
2025-02-28 09:57:39 +01:00
lacni
dc82a438d4 fix: refactored uploadFile to only accept a single file instead of multiple 2025-02-27 21:11:52 -05:00
lacni
cc54bdcbe7 feat: made every upload file independent 2025-02-27 19:18:13 -05:00
lacni
ae4bbc8baa fix: added onerror log 2025-02-27 19:15:58 -05:00
C4illin
ad98499da0 chore: move libheif below vips 2025-02-27 22:17:02 +01:00
lacni
db60f355b2 feat: added progress bar for file upload 2025-02-26 23:31:31 -05:00
Emrik Östling
eb91d8b298 Merge pull request #232 from C4illin/renovate/oven-bun-1.x
chore(deps): update oven/bun docker tag to v1.2.4
2025-02-26 16:07:27 +01:00
renovate[bot]
b8312be4b7 chore(deps): update oven/bun docker tag to v1.2.4 2025-02-26 14:32:53 +00:00
Emrik Östling
326a8e3404 Merge pull request #230 from C4illin/renovate/oven-bun-1.x 2025-02-23 12:45:09 +01:00
renovate[bot]
f017e13ac1 chore(deps): update oven/bun docker tag to v1.2.3 2025-02-23 01:15:18 +00:00
Emrik Östling
67a5fe353e Merge pull request #229 from C4illin/renovate/oven-bun-1.x
chore(deps): update oven/bun docker tag to v1.2.3
2025-02-22 11:47:50 +01:00
renovate[bot]
51d49d7ff3 chore(deps): update oven/bun docker tag to v1.2.3 2025-02-22 10:05:09 +00:00
Emrik Östling
d42b820b36 Merge pull request #227 from C4illin/renovate/eslint__js-9.x
chore(deps): update dependency @types/eslint__js to v9
2025-02-22 11:04:42 +01:00
renovate[bot]
07d32776d3 chore(deps): update dependency @types/eslint__js to v9 2025-02-21 23:05:04 +00:00
Emrik Östling
ef027e81b5 Merge pull request #228 from C4illin/renovate/globals-16.x
chore(deps): update dependency globals to v16
2025-02-22 00:04:37 +01:00
renovate[bot]
a75e4b495d chore(deps): update dependency globals to v16 2025-02-21 20:05:07 +00:00
C4illin
fba5e212e8 fix: update libheif to 1.19.5
issue: #202
2025-02-18 21:24:54 +01:00
C4illin
62f44fb052 chore: print libheif version 2025-02-18 20:05:46 +01:00
Emrik Östling
6b9254047c Merge pull request #225 from C4illin/fix/#202/add-libheif
fix: add libheif
2025-02-16 23:04:35 +01:00
C4illin
decfea5dc9 fix: add libheif
issue #202
2025-02-16 21:18:33 +01:00
Emrik Östling
eacded6848 Merge pull request #224 from C4illin/release-please--branches--main--components--convertx-frontend
chore(main): release 0.11.1
2025-02-07 22:52:29 +01:00
Emrik Östling
279ca72c64 chore(main): release 0.11.1 2025-02-07 16:15:21 +01:00
C4illin
b8fc9383ca chore: update deps 2025-02-07 16:14:46 +01:00
C4illin
bec58ac59f fix: mobile view overflow 2025-02-06 19:57:07 +01:00
21 changed files with 1507 additions and 1222 deletions

View File

@@ -1,5 +1,30 @@
# Changelog # Changelog
## [0.12.0](https://github.com/C4illin/ConvertX/compare/v0.11.1...v0.12.0) (2025-03-06)
### Features
* added progress bar for file upload ([db60f35](https://github.com/C4illin/ConvertX/commit/db60f355b2973f43f8e5990e6fe4e351b959b659))
* made every upload file independent ([cc54bdc](https://github.com/C4illin/ConvertX/commit/cc54bdcbe764c41cc3273485d072fd3178ad2dca))
* replace exec with execFile ([9263d17](https://github.com/C4illin/ConvertX/commit/9263d17609dc4b2b367eb7fee67b3182e283b3a3))
### Bug Fixes
* add libheif ([6b92540](https://github.com/C4illin/ConvertX/commit/6b9254047c0598963aee1d99e20ba1650a0368bf))
* add libheif ([decfea5](https://github.com/C4illin/ConvertX/commit/decfea5dc9627b216bb276a9e1578c32cfa1deb6)), closes [#202](https://github.com/C4illin/ConvertX/issues/202)
* added onerror log ([ae4bbc8](https://github.com/C4illin/ConvertX/commit/ae4bbc8baacbaf67763c62ea44140bb21cc17230))
* refactored uploadFile to only accept a single file instead of multiple ([dc82a43](https://github.com/C4illin/ConvertX/commit/dc82a438d4104b79ff423d502a6779a43928968a))
* update libheif to 1.19.5 ([fba5e21](https://github.com/C4illin/ConvertX/commit/fba5e212e8d0eaba8971e239e35aeb521f3cd813)), closes [#202](https://github.com/C4illin/ConvertX/issues/202)
## [0.11.1](https://github.com/C4illin/ConvertX/compare/v0.11.0...v0.11.1) (2025-02-07)
### Bug Fixes
* mobile view overflow ([bec58ac](https://github.com/C4illin/ConvertX/commit/bec58ac59f9600e35385b9e21d174f3ab1b42b1d))
## [0.11.0](https://github.com/C4illin/ConvertX/compare/v0.10.1...v0.11.0) (2025-02-05) ## [0.11.0](https://github.com/C4illin/ConvertX/compare/v0.10.1...v0.11.0) (2025-02-05)

View File

@@ -1,4 +1,4 @@
FROM oven/bun:1.2.2-alpine AS base FROM oven/bun:1.2.4-alpine AS base
LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX" LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX"
WORKDIR /app WORKDIR /app
@@ -23,7 +23,7 @@ RUN cargo install resvg
# copy node_modules from temp directory # copy node_modules from temp directory
# then copy all (non-ignored) project files into the image # then copy all (non-ignored) project files into the image
# will switch to alpine again when it works # will switch to alpine again when it works
FROM oven/bun:1.2.2-slim AS prerelease FROM oven/bun:1.2.4-slim AS prerelease
WORKDIR /app WORKDIR /app
COPY --from=install /temp/dev/node_modules node_modules COPY --from=install /temp/dev/node_modules node_modules
COPY . . COPY . .
@@ -37,6 +37,8 @@ LABEL maintainer="Emrik Östling (C4illin)"
LABEL description="ConvertX: self-hosted online file converter supporting 700+ file formats." LABEL description="ConvertX: self-hosted online file converter supporting 700+ file formats."
LABEL repo="https://github.com/C4illin/ConvertX" LABEL repo="https://github.com/C4illin/ConvertX"
RUN apk --no-cache add qt6-qtbase-private-dev libheif-tools --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/
# install additional dependencies # install additional dependencies
RUN apk --no-cache add \ RUN apk --no-cache add \
pandoc \ pandoc \
@@ -49,6 +51,8 @@ RUN apk --no-cache add \
vips-tools \ vips-tools \
vips-poppler \ vips-poppler \
vips-jxl \ vips-jxl \
vips-heif \
vips-magick \
libjxl-tools \ libjxl-tools \
assimp \ assimp \
inkscape \ inkscape \
@@ -57,8 +61,6 @@ RUN apk --no-cache add \
libva-utils \ libva-utils \
py3-numpy py3-numpy
RUN apk --no-cache add qt6-qtbase-private-dev --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/
RUN apk --no-cache add calibre --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/ RUN apk --no-cache add calibre --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/
# this might be needed for some latex use cases, will add it if needed. # this might be needed for some latex use cases, will add it if needed.

View File

@@ -27,6 +27,7 @@ A self-hosted online file converter. Supports over a thousand different formats.
| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 | | [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 |
| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 | | [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 |
| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 | | [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 |
| [libheif](https://github.com/strukturag/libheif) | HEIF | 2 | 4 |
| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 | | [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 |
| [Calibre](https://calibre-ebook.com/) | E-books | 26 | 19 | | [Calibre](https://calibre-ebook.com/) | E-books | 26 | 19 |
| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 | | [Pandoc](https://pandoc.org/) | Documents | 43 | 65 |

119
bun.lock
View File

@@ -9,35 +9,36 @@
"@elysiajs/jwt": "^1.2.0", "@elysiajs/jwt": "^1.2.0",
"@elysiajs/static": "^1.2.0", "@elysiajs/static": "^1.2.0",
"@kitajs/html": "^4.2.7", "@kitajs/html": "^4.2.7",
"elysia": "^1.2.10", "elysia": "^1.2.12",
"sanitize-filename": "^1.6.3",
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.19.0", "@eslint/js": "^9.19.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.1", "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@kitajs/ts-html-plugin": "^4.1.1", "@kitajs/ts-html-plugin": "^4.1.1",
"@tailwindcss/cli": "^4.0.3", "@tailwindcss/cli": "^4.0.4",
"@tailwindcss/postcss": "^4.0.3", "@tailwindcss/postcss": "^4.0.4",
"@total-typescript/ts-reset": "^0.6.1", "@total-typescript/ts-reset": "^0.6.1",
"@types/bun": "^1.2.0", "@types/bun": "^1.2.2",
"@types/eslint-plugin-tailwindcss": "^3.17.0", "@types/eslint-plugin-tailwindcss": "^3.17.0",
"@types/eslint__js": "^8.42.3", "@types/eslint__js": "^9.0.0",
"@types/node": "^22.10.10", "@types/node": "^22.13.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"cssnano": "^7.0.6", "cssnano": "^7.0.6",
"eslint": "^9.19.0", "eslint": "^9.19.0",
"eslint-plugin-readable-tailwind": "^2.0.0-beta.1", "eslint-plugin-readable-tailwind": "^2.0.0-beta.1",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-tailwindcss": "4.0.0-alpha.0", "eslint-plugin-tailwindcss": "4.0.0-alpha.0",
"globals": "^15.14.0", "globals": "^16.0.0",
"knip": "^5.43.1", "knip": "^5.43.6",
"npm-run-all2": "^7.0.2", "npm-run-all2": "^7.0.2",
"postcss": "^8.5.1", "postcss": "^8.5.1",
"postcss-cli": "^11.0.0", "postcss-cli": "^11.0.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"tailwind-scrollbar": "^4.0.0", "tailwind-scrollbar": "^4.0.0",
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.4",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"typescript-eslint": "^8.22.0", "typescript-eslint": "^8.23.0",
}, },
}, },
}, },
@@ -154,35 +155,35 @@
"@snyk/github-codeowners": ["@snyk/github-codeowners@1.1.0", "", { "dependencies": { "commander": "^4.1.1", "ignore": "^5.1.8", "p-map": "^4.0.0" }, "bin": { "github-codeowners": "dist/cli.js" } }, "sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw=="], "@snyk/github-codeowners": ["@snyk/github-codeowners@1.1.0", "", { "dependencies": { "commander": "^4.1.1", "ignore": "^5.1.8", "p-map": "^4.0.0" }, "bin": { "github-codeowners": "dist/cli.js" } }, "sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw=="],
"@tailwindcss/cli": ["@tailwindcss/cli@4.0.3", "", { "dependencies": { "@parcel/watcher": "^2.5.0", "@tailwindcss/node": "^4.0.3", "@tailwindcss/oxide": "^4.0.3", "enhanced-resolve": "^5.18.0", "lightningcss": "^1.29.1", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.0.3" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-EPmuqS5e1yax6Qe1vRoWFbCCttMS/Thc+yFGSE/nzYe/BdYDlUc+OoE7D5DawAz4sI5H8v8Zx/mYyEdy+saB0w=="], "@tailwindcss/cli": ["@tailwindcss/cli@4.0.4", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "^4.0.4", "@tailwindcss/oxide": "^4.0.4", "enhanced-resolve": "^5.18.0", "lightningcss": "^1.29.1", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.0.4" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-GhdVjJUrzpjN8aGbtoq5te4G/Xh6XKiIjnDEZT6cefL+bX+CiXiOdrRs+Rjxi9A3oObXRsqa93mPYLhTTfc62w=="],
"@tailwindcss/node": ["@tailwindcss/node@4.0.3", "", { "dependencies": { "enhanced-resolve": "^5.18.0", "jiti": "^2.4.2", "tailwindcss": "4.0.3" } }, "sha512-QsVJokOl0pJ4AbJV33D2npvLcHGPWi5MOSZtrtE0GT3tSx+3D0JE2lokLA8yHS1x3oCY/3IyRyy7XX6tmzid7A=="], "@tailwindcss/node": ["@tailwindcss/node@4.0.4", "", { "dependencies": { "enhanced-resolve": "^5.18.0", "jiti": "^2.4.2", "tailwindcss": "4.0.4" } }, "sha512-VLFq80IyoV1hsHPcCm1mmlyPyUT6NlovQLoO2y7PGm84mW94ZrNJ7ax5H6K4M7Aj/fdMfem5IX7Ka+LXWZpDGg=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.3", "@tailwindcss/oxide-darwin-arm64": "4.0.3", "@tailwindcss/oxide-darwin-x64": "4.0.3", "@tailwindcss/oxide-freebsd-x64": "4.0.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.3", "@tailwindcss/oxide-linux-arm64-musl": "4.0.3", "@tailwindcss/oxide-linux-x64-gnu": "4.0.3", "@tailwindcss/oxide-linux-x64-musl": "4.0.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.3", "@tailwindcss/oxide-win32-x64-msvc": "4.0.3" } }, "sha512-FFcp3VNvRjjmFA39ORM27g2mbflMQljhvM7gxBAujHxUy4LXlKa6yMF9wbHdTbPqTONiCyyOYxccvJyVyI/XBg=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.4", "@tailwindcss/oxide-darwin-arm64": "4.0.4", "@tailwindcss/oxide-darwin-x64": "4.0.4", "@tailwindcss/oxide-freebsd-x64": "4.0.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.4", "@tailwindcss/oxide-linux-arm64-musl": "4.0.4", "@tailwindcss/oxide-linux-x64-gnu": "4.0.4", "@tailwindcss/oxide-linux-x64-musl": "4.0.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.4", "@tailwindcss/oxide-win32-x64-msvc": "4.0.4" } }, "sha512-vPpu30KFLiGyPOoElkYt8WRvzGKVrrOz49KpfiGGtnQGmyUpL8VCbJzzEEcpKT5BpaaQidhFok+OXscf6hHjOQ=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-S8XOTQuMnpijZRlPm5HBzPJjZ28quB+40LSRHjRnQF6rRYKsvpr1qkY7dfwsetNdd+kMLOMDsvmuT8WnqqETvg=="], "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.0.4", "", { "os": "android", "cpu": "arm64" }, "sha512-hiGUA8d15ynH/LdurQNObnuTjri7i4ApAzhesusNxoz4br7vhZ6QO5CFgniYAYNZvf8Q8wCTBg0nj61RalBeVQ=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-smrY2DpzhXvgDhZtQlYAl8+vxJ04lv2/64C1eiRxvsRT2nkw/q+zA1/eAYKvUHat6cIuwqDku3QucmrUT6pCeg=="], "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vTca+ysNl8BYmYJTni9pLC+L3S4bvrj0ai1eUV3yYXYa5Cpugr5Fni6ylV0gcTZOyETm2RCCJ/0azU6MgqE6HA=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-NTz8x/LcGUjpZAWUxz0ZuzHao90Wj9spoQgomwB+/hgceh5gcJDfvaBYqxLFpKzVglpnbDSq1Fg0p0zI4oa5Pg=="], "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-rxPWb5AQJ/aAM/5UDCjaQaMYIcrZHe/Dr9xZu9+P9nJf3WAweNsGi+e+SW9EYGRiF3hkBtP2dvxVNAkTiEbNQQ=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.0.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yQc9Q0JCOp3kkAV8gKgDctXO60IkQhHpqGB+KgOccDtD5UmN6Q5+gd+lcsDyQ7N8dRuK1fAud51xQpZJgKfm7g=="], "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.0.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UOnRHzlS5V5cxaMgBo6rk1E92tTDUtO/falc9vOpNiRdWhNcofYNN9zvZP63Wuo5FC6/XCyAnJo6OXUm18TwrQ=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-e1ivVMLSnxTOU1O3npnxN16FEyWM/g3SuH2pP6udxXwa0/SnSAijRwcAYRpqIlhVKujr158S8UeHxQjC4fGl4w=="], "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-0Ry9Qfnf22rmJwHxsCFmHQIl5RZw+yOUUGHaqNT42REL8r308cU/bi4UqdrjqVRfAlu51gOGxTRf2NRueczuIA=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-PLrToqQqX6sdJ9DmMi8IxZWWrfjc9pdi9AEEPTrtMts3Jm9HBi1WqEeF1VwZZ2aW9TXloE5OwA35zuuq1Bhb/Q=="], "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-5a7WD30nVdI7Rl1ohZ0Ojj9t5yRnZkJBizvh3uIW52h9UeNpon8TfoknF6rU/TwD32dQ0Cjo5CcCHtQ2wW9PCA=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YlzRxx7N1ampfgSKzEDw0iwDkJXUInR4cgNEqmR4TzHkU2Vhg59CGPJrTI7dxOBofD8+O35R13Nk9Ytyv0JUFg=="], "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-m6s5jKSqos07l6NtHFd49Ljcaw4jIWHE7jq6eNPNz9SCzQqRzs4esP1t7jH8UljQ7JffKOl7yZPwK5Nf+irliw=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Xfc3z/li6XkuD7Hs+Uk6pjyCXnfnd9zuQTKOyDTZJ544xc2yoMKUkuDw6Et9wb31MzU2/c0CIUpTDa71lL9KHw=="], "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-K5dBjGHzby9eyUBwy9YHFhKY+5i8fzIBZM1NBWp6L2xpM7OzW9WJDgNcgESkZami9g+EozkQLt3ZmMZHAieXkw=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-ugKVqKzwa/cjmqSQG17aS9DYrEcQ/a5NITcgmOr3JLW4Iz64C37eoDlkC8tIepD3S/Td/ywKAolTQ8fKbjEL4g=="], "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-J8sskt+fA5ooq+kxy0Tf4E2TRWZD9Y8j3K+pnBwp9zdilLmSd8OHrB3e0/rO78KveZ6BE9ae75cKOWrT6wONmw=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-qHPDMl+UUwsk1RMJMgAXvhraWqUUT+LR/tkXix5RA39UGxtTrHwsLIN1AhNxI5i2RFXAXfmFXDqZCdyQ4dWmAQ=="], "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.0.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-flFaaMc77NQbz0Fq73wBs9EH2lX1Oc2Z/3JuxoewpnGHpAGJ/j05tvBNMyTaGrKcHvf/+dk+mCDxb6+PmzGgnQ=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-+ujwN4phBGyOsPyLgGgeCyUm4Mul+gqWVCIGuSXWgrx9xVUnf6LVXrw0BDBc9Aq1S2qMyOTX4OkCGbZeoIo8Qw=="], "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-WzMA0aL/24/JyNrv2Yhr/Og24QGRPWJMjRyCJ4HRoGMs6/8svOQKrnnZ/9LUFwn56irAndFBjWWnlaqykH+g5Q=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.0.3", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "^4.0.3", "@tailwindcss/oxide": "^4.0.3", "lightningcss": "^1.29.1", "postcss": "^8.4.41", "tailwindcss": "4.0.3" } }, "sha512-qUyxuhuI2eTgRJ+qfCQRAr69Cw7BdSz+PoNFUNoRuhPjikNC8+sxK+Mi/chaXAXewjv/zbf6if6z6ItVLh+e9Q=="], "@tailwindcss/postcss": ["@tailwindcss/postcss@4.0.4", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "^4.0.4", "@tailwindcss/oxide": "^4.0.4", "lightningcss": "^1.29.1", "postcss": "^8.4.41", "tailwindcss": "4.0.4" } }, "sha512-Up8fB+DUhy8qvDqlHgZAWaL5iVEbypcuOjzlW4K6EyU+aGEvXK0/wrcKBKOTvg3KKP5givJMexJ0aG1hDPOuRg=="],
"@total-typescript/ts-reset": ["@total-typescript/ts-reset@0.6.1", "", {}, "sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg=="], "@total-typescript/ts-reset": ["@total-typescript/ts-reset@0.6.1", "", {}, "sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg=="],
@@ -198,25 +199,25 @@
"@types/eslint-plugin-tailwindcss": ["@types/eslint-plugin-tailwindcss@3.17.0", "", { "dependencies": { "@types/eslint": "*" } }, "sha512-ucQGf2YIdTcndYcxRU3UdZgmhUHsOlbIF4BaRtl0op+7k2JmqM2i3aXZ6XIcfZgVq1ZKov7VM5c/BR81ukmkyg=="], "@types/eslint-plugin-tailwindcss": ["@types/eslint-plugin-tailwindcss@3.17.0", "", { "dependencies": { "@types/eslint": "*" } }, "sha512-ucQGf2YIdTcndYcxRU3UdZgmhUHsOlbIF4BaRtl0op+7k2JmqM2i3aXZ6XIcfZgVq1ZKov7VM5c/BR81ukmkyg=="],
"@types/eslint__js": ["@types/eslint__js@8.42.3", "", { "dependencies": { "@types/eslint": "*" } }, "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw=="], "@types/eslint__js": ["@types/eslint__js@9.14.0", "", { "dependencies": { "@eslint/js": "*" } }, "sha512-s0jepCjOJWB/GKcuba4jISaVpBudw3ClXJ3fUK4tugChUMQsp6kSwuA8Dcx6wFd/JsJqcY8n4rEpa5RTHs5ypA=="],
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@22.13.0", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA=="], "@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
"@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="], "@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="],
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.22.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.22.0", "@typescript-eslint/type-utils": "8.22.0", "@typescript-eslint/utils": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.23.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/type-utils": "8.23.0", "@typescript-eslint/utils": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.22.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.22.0", "@typescript-eslint/types": "8.22.0", "@typescript-eslint/typescript-estree": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.23.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/types": "8.23.0", "@typescript-eslint/typescript-estree": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.22.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.22.0", "@typescript-eslint/utils": "8.22.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA=="], "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.23.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.23.0", "@typescript-eslint/utils": "8.23.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="], "@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="],
@@ -224,7 +225,7 @@
"@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="], "@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w=="], "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
@@ -342,7 +343,7 @@
"electron-to-chromium": ["electron-to-chromium@1.5.90", "", {}, "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug=="], "electron-to-chromium": ["electron-to-chromium@1.5.90", "", {}, "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug=="],
"elysia": ["elysia@1.2.11", "", { "dependencies": { "@sinclair/typebox": "^0.34.15", "cookie": "^1.0.2", "memoirist": "^0.3.0", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-9bt2tsru9LcFAVrWDfcREJFFSIz0pauzo/XO+5kYPjtHNbjsVkRLQTNDYydy3mQQmz4Acxavoi2MCBbay3DETw=="], "elysia": ["elysia@1.2.12", "", { "dependencies": { "@sinclair/typebox": "^0.34.15", "cookie": "^1.0.2", "memoirist": "^0.3.0", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-X1bZo09qe8/Poa/5tz08Y+sE/77B/wLwnA5xDDENU3FCrsUtYJuBVcy6BPXGRCgnJ1fPQpc0Ov2ZU5MYJXluTg=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -414,7 +415,7 @@
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@15.14.0", "", {}, "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig=="], "globals": ["globals@16.0.0", "", {}, "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A=="],
"globby": ["globby@14.0.2", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", "ignore": "^5.2.4", "path-type": "^5.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.1.0" } }, "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw=="], "globby": ["globby@14.0.2", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", "ignore": "^5.2.4", "path-type": "^5.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.1.0" } }, "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw=="],
@@ -686,6 +687,8 @@
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"sanitize-filename": ["sanitize-filename@1.6.3", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg=="],
"semver": ["semver@7.7.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ=="], "semver": ["semver@7.7.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
@@ -720,7 +723,7 @@
"tailwind-scrollbar": ["tailwind-scrollbar@4.0.0", "", { "dependencies": { "prism-react-renderer": "^2.4.1" }, "peerDependencies": { "tailwindcss": "4.x" } }, "sha512-elqx9m09VHY8gkrMiyimFO09JlS3AyLFXT0eaLaWPi7ImwHlbZj1ce/AxSis2LtR+ewBGEyUV7URNEMcjP1Z2w=="], "tailwind-scrollbar": ["tailwind-scrollbar@4.0.0", "", { "dependencies": { "prism-react-renderer": "^2.4.1" }, "peerDependencies": { "tailwindcss": "4.x" } }, "sha512-elqx9m09VHY8gkrMiyimFO09JlS3AyLFXT0eaLaWPi7ImwHlbZj1ce/AxSis2LtR+ewBGEyUV7URNEMcjP1Z2w=="],
"tailwindcss": ["tailwindcss@4.0.3", "", {}, "sha512-ImmZF0Lon5RrQpsEAKGxRvHwCvMgSC4XVlFRqmbzTEDb/3wvin9zfEZrMwgsa3yqBbPqahYcVI6lulM2S7IZAA=="], "tailwindcss": ["tailwindcss@4.0.4", "", {}, "sha512-/ezDLEkOLf1lXkr9F2iI5BHJbexJpty5zkV2B8bGHCqAdbc9vk85Jgdkq+ZOvNkNPa3yAaqJ8DjRt584Bc84kw=="],
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="], "tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
@@ -730,6 +733,8 @@
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="],
"ts-api-utils": ["ts-api-utils@2.0.1", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w=="], "ts-api-utils": ["ts-api-utils@2.0.1", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -740,7 +745,7 @@
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
"typescript-eslint": ["typescript-eslint@8.22.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.22.0", "@typescript-eslint/parser": "8.22.0", "@typescript-eslint/utils": "8.22.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-Y2rj210FW1Wb6TWXzQc5+P+EWI9/zdS57hLEc0gnyuvdzWo8+Y8brKlbj0muejonhMI/xAZCnZZwjbIfv1CkOw=="], "typescript-eslint": ["typescript-eslint@8.23.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.23.0", "@typescript-eslint/parser": "8.23.0", "@typescript-eslint/utils": "8.23.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-/LBRo3HrXr5LxmrdYSOCvoAMm7p2jNizNfbIpCgvG4HMsnoprRUOce/+8VJ9BDYWW68rqIENE/haVLWPeFZBVQ=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
@@ -752,6 +757,8 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
@@ -790,21 +797,25 @@
"@nodelib/fs.scandir/@nodelib/fs.stat": ["@nodelib/fs.stat@4.0.0", "", {}, "sha512-ctr6bByzksKRCV0bavi8WoQevU6plSp2IkllIsEqaiKe2mwNNnaluhnRhcsgGZHrrHk57B3lf95MkLMO3STYcg=="], "@nodelib/fs.scandir/@nodelib/fs.stat": ["@nodelib/fs.stat@4.0.0", "", {}, "sha512-ctr6bByzksKRCV0bavi8WoQevU6plSp2IkllIsEqaiKe2mwNNnaluhnRhcsgGZHrrHk57B3lf95MkLMO3STYcg=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0" } }, "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ=="], "@types/cookie-signature/@types/node": ["@types/node@22.13.0", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.22.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.22.0", "@typescript-eslint/types": "8.22.0", "@typescript-eslint/typescript-estree": "8.22.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg=="], "@types/ws/@types/node": ["@types/node@22.13.0", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA=="],
"@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0" } }, "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0" } }, "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw=="],
"@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.22.0", "", {}, "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.23.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/types": "8.23.0", "@typescript-eslint/typescript-estree": "8.23.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA=="],
"@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w=="], "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0" } }, "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw=="],
"@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="],
"@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ=="],
"@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="], "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="],
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w=="], "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ=="],
"@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.22.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.22.0", "@typescript-eslint/types": "8.22.0", "@typescript-eslint/typescript-estree": "8.22.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg=="], "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.23.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/types": "8.23.0", "@typescript-eslint/typescript-estree": "8.23.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA=="],
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="], "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="],
@@ -814,10 +825,12 @@
"@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="], "@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="],
"@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.22.0", "", {}, "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A=="], "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"bun-types/@types/node": ["@types/node@22.13.0", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA=="],
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@@ -856,27 +869,27 @@
"svgo/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], "svgo/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
"typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.22.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.22.0", "@typescript-eslint/types": "8.22.0", "@typescript-eslint/typescript-estree": "8.22.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg=="], "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.23.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/types": "8.23.0", "@typescript-eslint/typescript-estree": "8.23.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA=="],
"wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.22.0", "", {}, "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.22.0", "", {}, "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ=="],
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.22.0", "", {}, "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A=="], "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="],
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0" } }, "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ=="], "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0" } }, "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw=="],
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.22.0", "", {}, "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A=="], "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="],
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
@@ -908,11 +921,11 @@
"npm-run-all2/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], "npm-run-all2/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0" } }, "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ=="], "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0" } }, "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw=="],
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.22.0", "", {}, "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A=="], "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="],
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w=="], "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],

View File

@@ -1,6 +1,6 @@
{ {
"name": "convertx-frontend", "name": "convertx-frontend",
"version": "0.11.0", "version": "0.12.0",
"scripts": { "scripts": {
"dev": "bun run --watch src/index.tsx", "dev": "bun run --watch src/index.tsx",
"hot": "bun run --hot src/index.tsx", "hot": "bun run --hot src/index.tsx",
@@ -17,7 +17,8 @@
"@elysiajs/jwt": "^1.2.0", "@elysiajs/jwt": "^1.2.0",
"@elysiajs/static": "^1.2.0", "@elysiajs/static": "^1.2.0",
"@kitajs/html": "^4.2.7", "@kitajs/html": "^4.2.7",
"elysia": "^1.2.10" "elysia": "^1.2.12",
"sanitize-filename": "^1.6.3"
}, },
"module": "src/index.tsx", "module": "src/index.tsx",
"type": "module", "type": "module",
@@ -28,28 +29,28 @@
"@eslint/js": "^9.19.0", "@eslint/js": "^9.19.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.1", "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@kitajs/ts-html-plugin": "^4.1.1", "@kitajs/ts-html-plugin": "^4.1.1",
"@tailwindcss/cli": "^4.0.3", "@tailwindcss/cli": "^4.0.4",
"@tailwindcss/postcss": "^4.0.3", "@tailwindcss/postcss": "^4.0.4",
"@total-typescript/ts-reset": "^0.6.1", "@total-typescript/ts-reset": "^0.6.1",
"@types/bun": "^1.2.0", "@types/bun": "^1.2.2",
"@types/eslint-plugin-tailwindcss": "^3.17.0", "@types/eslint-plugin-tailwindcss": "^3.17.0",
"@types/eslint__js": "^8.42.3", "@types/eslint__js": "^9.0.0",
"@types/node": "^22.10.10", "@types/node": "^22.13.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"cssnano": "^7.0.6", "cssnano": "^7.0.6",
"eslint": "^9.19.0", "eslint": "^9.19.0",
"eslint-plugin-readable-tailwind": "^2.0.0-beta.1", "eslint-plugin-readable-tailwind": "^2.0.0-beta.1",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-tailwindcss": "4.0.0-alpha.0", "eslint-plugin-tailwindcss": "4.0.0-alpha.0",
"globals": "^15.14.0", "globals": "^16.0.0",
"knip": "^5.43.1", "knip": "^5.43.6",
"npm-run-all2": "^7.0.2", "npm-run-all2": "^7.0.2",
"postcss": "^8.5.1", "postcss": "^8.5.1",
"postcss-cli": "^11.0.0", "postcss-cli": "^11.0.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"tailwind-scrollbar": "^4.0.0", "tailwind-scrollbar": "^4.0.0",
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.4",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"typescript-eslint": "^8.22.0" "typescript-eslint": "^8.23.0"
} }
} }

View File

@@ -1,236 +1,257 @@
const webroot = document.querySelector("meta[name='webroot']").content; const webroot = document.querySelector("meta[name='webroot']").content;
const fileInput = document.querySelector('input[type="file"]'); const fileInput = document.querySelector('input[type="file"]');
const dropZone = document.getElementById("dropzone"); const dropZone = document.getElementById("dropzone");
const convertButton = document.querySelector("input[type='submit']"); const convertButton = document.querySelector("input[type='submit']");
const fileNames = []; const fileNames = [];
let fileType; let fileType;
let pendingFiles = 0; let pendingFiles = 0;
let formatSelected = false; let formatSelected = false;
dropZone.addEventListener("dragover", () => { dropZone.addEventListener("dragover", () => {
dropZone.classList.add("dragover"); dropZone.classList.add("dragover");
}); });
dropZone.addEventListener("dragleave", () => { dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("dragover"); dropZone.classList.remove("dragover");
}); });
dropZone.addEventListener("drop", () => { dropZone.addEventListener("drop", () => {
dropZone.classList.remove("dragover"); dropZone.classList.remove("dragover");
}); });
const selectContainer = document.querySelector("form .select_container"); const selectContainer = document.querySelector("form .select_container");
const updateSearchBar = () => { const updateSearchBar = () => {
const convertToInput = document.querySelector( const convertToInput = document.querySelector(
"input[name='convert_to_search']", "input[name='convert_to_search']",
); );
const convertToPopup = document.querySelector(".convert_to_popup"); const convertToPopup = document.querySelector(".convert_to_popup");
const convertToGroupElements = document.querySelectorAll(".convert_to_group"); const convertToGroupElements = document.querySelectorAll(".convert_to_group");
const convertToGroups = {}; const convertToGroups = {};
const convertToElement = document.querySelector("select[name='convert_to']"); const convertToElement = document.querySelector("select[name='convert_to']");
const showMatching = (search) => { const showMatching = (search) => {
for (const [targets, groupElement] of Object.values(convertToGroups)) { for (const [targets, groupElement] of Object.values(convertToGroups)) {
let matchingTargetsFound = 0; let matchingTargetsFound = 0;
for (const target of targets) { for (const target of targets) {
if (target.dataset.target.includes(search)) { if (target.dataset.target.includes(search)) {
matchingTargetsFound++; matchingTargetsFound++;
target.classList.remove("hidden"); target.classList.remove("hidden");
target.classList.add("flex"); target.classList.add("flex");
} else { } else {
target.classList.add("hidden"); target.classList.add("hidden");
target.classList.remove("flex"); target.classList.remove("flex");
} }
} }
if (matchingTargetsFound === 0) { if (matchingTargetsFound === 0) {
groupElement.classList.add("hidden"); groupElement.classList.add("hidden");
groupElement.classList.remove("flex"); groupElement.classList.remove("flex");
} else { } else {
groupElement.classList.remove("hidden"); groupElement.classList.remove("hidden");
groupElement.classList.add("flex"); groupElement.classList.add("flex");
} }
} }
}; };
for (const groupElement of convertToGroupElements) { for (const groupElement of convertToGroupElements) {
const groupName = groupElement.dataset.converter; const groupName = groupElement.dataset.converter;
const targetElements = groupElement.querySelectorAll(".target"); const targetElements = groupElement.querySelectorAll(".target");
const targets = Array.from(targetElements); const targets = Array.from(targetElements);
for (const target of targets) { for (const target of targets) {
target.onmousedown = () => { target.onmousedown = () => {
convertToElement.value = target.dataset.value; convertToElement.value = target.dataset.value;
convertToInput.value = `${target.dataset.target} using ${target.dataset.converter}`; convertToInput.value = `${target.dataset.target} using ${target.dataset.converter}`;
formatSelected = true; formatSelected = true;
if (pendingFiles === 0 && fileNames.length > 0) { if (pendingFiles === 0 && fileNames.length > 0) {
convertButton.disabled = false; convertButton.disabled = false;
} }
showMatching(""); showMatching("");
}; };
} }
convertToGroups[groupName] = [targets, groupElement]; convertToGroups[groupName] = [targets, groupElement];
} }
convertToInput.addEventListener("input", (e) => { convertToInput.addEventListener("input", (e) => {
showMatching(e.target.value.toLowerCase()); showMatching(e.target.value.toLowerCase());
}); });
convertToInput.addEventListener("search", () => { convertToInput.addEventListener("search", () => {
// when the user clears the search bar using the 'x' button // when the user clears the search bar using the 'x' button
convertButton.disabled = true; convertButton.disabled = true;
formatSelected = false; formatSelected = false;
}); });
convertToInput.addEventListener("blur", (e) => { convertToInput.addEventListener("blur", (e) => {
// Keep the popup open even when clicking on a target button // Keep the popup open even when clicking on a target button
// for a split second to allow the click to go through // for a split second to allow the click to go through
if (e?.relatedTarget?.classList?.contains("target")) { if (e?.relatedTarget?.classList?.contains("target")) {
convertToPopup.classList.add("hidden"); convertToPopup.classList.add("hidden");
convertToPopup.classList.remove("flex"); convertToPopup.classList.remove("flex");
return; return;
} }
convertToPopup.classList.add("hidden"); convertToPopup.classList.add("hidden");
convertToPopup.classList.remove("flex"); convertToPopup.classList.remove("flex");
}); });
convertToInput.addEventListener("focus", () => { convertToInput.addEventListener("focus", () => {
convertToPopup.classList.remove("hidden"); convertToPopup.classList.remove("hidden");
convertToPopup.classList.add("flex"); convertToPopup.classList.add("flex");
}); });
}; };
// Add a 'change' event listener to the file input element // Add a 'change' event listener to the file input element
fileInput.addEventListener("change", (e) => { fileInput.addEventListener("change", (e) => {
// Get the selected files from the event target // Get the selected files from the event target
const files = e.target.files; const files = e.target.files;
// Select the file-list table // Select the file-list table
const fileList = document.querySelector("#file-list"); const fileList = document.querySelector("#file-list");
// Loop through the selected files // Loop through the selected files
for (const file of files) { for (const file of files) {
// Create a new table row for each file // Create a new table row for each file
const row = document.createElement("tr"); const row = document.createElement("tr");
row.innerHTML = ` row.innerHTML = `
<td>${file.name}</td> <td>${file.name}</td>
<td>${(file.size / 1024).toFixed(2)} kB</td> <td><progress max="100"></progress></td>
<td><a onclick="deleteRow(this)">Remove</a></td> <td>${(file.size / 1024).toFixed(2)} kB</td>
`; <td><a onclick="deleteRow(this)">Remove</a></td>
`;
if (!fileType) {
fileType = file.name.split(".").pop(); if (!fileType) {
fileInput.setAttribute("accept", `.${fileType}`); fileType = file.name.split(".").pop();
setTitle(); fileInput.setAttribute("accept", `.${fileType}`);
setTitle();
// choose the option that matches the file type
// for (const option of convertFromSelect.children) { // choose the option that matches the file type
// console.log(option.value); // for (const option of convertFromSelect.children) {
// if (option.value === fileType) { // console.log(option.value);
// option.selected = true; // if (option.value === fileType) {
// } // option.selected = true;
// } // }
// }
fetch(`${webroot}/conversions`, {
method: "POST", fetch(`${webroot}/conversions`, {
body: JSON.stringify({ fileType: fileType }), method: "POST",
headers: { body: JSON.stringify({ fileType: fileType }),
"Content-Type": "application/json", headers: {
}, "Content-Type": "application/json",
}) },
.then((res) => res.text()) })
.then((html) => { .then((res) => res.text())
selectContainer.innerHTML = html; .then((html) => {
updateSearchBar(); selectContainer.innerHTML = html;
}) updateSearchBar();
.catch((err) => console.log(err)); })
} .catch((err) => console.log(err));
}
// Append the row to the file-list table
fileList.appendChild(row); // Append the row to the file-list table
fileList.appendChild(row);
// Append the file to the hidden input
fileNames.push(file.name); //Imbed row into the file to reference later
} file.htmlRow = row;
uploadFiles(files);
}); // Append the file to the hidden input
fileNames.push(file.name);
const setTitle = () => {
const title = document.querySelector("h1"); uploadFile(file);
title.textContent = `Convert ${fileType ? `.${fileType}` : ""}`; }
}; });
// Add a onclick for the delete button const setTitle = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars const title = document.querySelector("h1");
const deleteRow = (target) => { title.textContent = `Convert ${fileType ? `.${fileType}` : ""}`;
const filename = target.parentElement.parentElement.children[0].textContent; };
const row = target.parentElement.parentElement;
row.remove(); // Add a onclick for the delete button
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// remove from fileNames const deleteRow = (target) => {
const index = fileNames.indexOf(filename); const filename = target.parentElement.parentElement.children[0].textContent;
fileNames.splice(index, 1); const row = target.parentElement.parentElement;
row.remove();
// reset fileInput
fileInput.value = ""; // remove from fileNames
const index = fileNames.indexOf(filename);
// if fileNames is empty, reset fileType fileNames.splice(index, 1);
if (fileNames.length === 0) {
fileType = null; // reset fileInput
fileInput.removeAttribute("accept"); fileInput.value = "";
convertButton.disabled = true;
setTitle(); // if fileNames is empty, reset fileType
} if (fileNames.length === 0) {
fileType = null;
fetch(`${webroot}/delete`, { fileInput.removeAttribute("accept");
method: "POST", convertButton.disabled = true;
body: JSON.stringify({ filename: filename }), setTitle();
headers: { }
"Content-Type": "application/json",
}, fetch(`${webroot}/delete`, {
}) method: "POST",
.catch((err) => console.log(err)); body: JSON.stringify({ filename: filename }),
}; headers: {
"Content-Type": "application/json",
const uploadFiles = (files) => { },
convertButton.disabled = true; })
convertButton.textContent = "Uploading..."; .catch((err) => console.log(err));
pendingFiles += 1; };
const formData = new FormData(); const uploadFile = (file) => {
convertButton.disabled = true;
for (const file of files) { convertButton.textContent = "Uploading...";
formData.append("file", file, file.name); pendingFiles += 1;
}
const formData = new FormData();
fetch(`${webroot}/upload`, { formData.append("file", file, file.name);
method: "POST",
body: formData, let xhr = new XMLHttpRequest();
})
.then((res) => res.json()) xhr.open("POST", `${webroot}/upload`, true);
.then((data) => {
pendingFiles -= 1; xhr.onload = (e) => {
if (pendingFiles === 0) { let data = JSON.parse(xhr.responseText);
if (formatSelected) {
convertButton.disabled = false; pendingFiles -= 1;
} if (pendingFiles === 0) {
convertButton.textContent = "Convert"; if (formatSelected) {
} convertButton.disabled = false;
console.log(data); }
}) convertButton.textContent = "Convert";
.catch((err) => console.log(err)); }
};
//Remove the progress bar when upload is done
const formConvert = document.querySelector(`form[action='${webroot}/convert']`); let progressbar = file.htmlRow.getElementsByTagName("progress");
progressbar[0].parentElement.remove();
formConvert.addEventListener("submit", () => { console.log(data);
const hiddenInput = document.querySelector("input[name='file_names']"); };
hiddenInput.value = JSON.stringify(fileNames);
}); xhr.upload.onprogress = (e) => {
let sent = e.loaded;
updateSearchBar(); let total = e.total;
console.log(`upload progress (${file.name}):`, (100 * sent) / total);
let progressbar = file.htmlRow.getElementsByTagName("progress");
progressbar[0].value = ((100 * sent) / total);
};
xhr.onerror = (e) => {
console.log(e);
};
xhr.send(formData);
};
const formConvert = document.querySelector(`form[action='${webroot}/convert']`);
formConvert.addEventListener("submit", () => {
const hiddenInput = document.querySelector("input[name='file_names']");
hiddenInput.value = JSON.stringify(fileNames);
});
updateSearchBar();

View File

@@ -1,4 +1,4 @@
import { exec } from "node:child_process"; import { execFile } from "node:child_process";
export const properties = { export const properties = {
from: { from: {
@@ -119,10 +119,8 @@ export async function convert(
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown, options?: unknown,
): Promise<string> { ): Promise<string> {
const command = `assimp export "${filePath}" "${targetPath}"`;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => { execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => {
if (error) { if (error) {
reject(`error: ${error}`); reject(`error: ${error}`);
} }

View File

@@ -1,4 +1,4 @@
import { exec } from "node:child_process"; import { execFile } from "node:child_process";
export const properties = { export const properties = {
from: { from: {
@@ -64,10 +64,8 @@ export async function convert(
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown, options?: unknown,
): Promise<string> { ): Promise<string> {
const command = `ebook-convert "${filePath}" "${targetPath}"`;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => { execFile("ebook-convert", [filePath, targetPath], (error, stdout, stderr) => {
if (error) { if (error) {
reject(`error: ${error}`); reject(`error: ${error}`);
} }

View File

@@ -1,4 +1,4 @@
import { exec } from "node:child_process"; import { execFile } from "node:child_process";
// This could be done dynamically by running `ffmpeg -formats` and parsing the output // This could be done dynamically by running `ffmpeg -formats` and parsing the output
export const properties = { export const properties = {
@@ -691,19 +691,28 @@ export async function convert(
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown, options?: unknown,
): Promise<string> { ): Promise<string> {
let extra = ""; let extraArgs: string[] = [];
let message = "Done"; let message = "Done";
if (convertTo === "ico") { if (convertTo === "ico") {
// make sure image is 256x256 or smaller // make sure image is 256x256 or smaller
extra = `-filter:v "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease"`; extraArgs = ['-filter:v', "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease"];
message = "Done: resized to 256x256"; message = "Done: resized to 256x256";
} }
const command = `ffmpeg ${process.env.FFMPEG_ARGS || ""} -i "${filePath}" ${extra} "${targetPath}"`; // Parse FFMPEG_ARGS environment variable into array
const ffmpegArgs = process.env.FFMPEG_ARGS ? process.env.FFMPEG_ARGS.split(/\s+/) : [];
// Build arguments array
const args = [
...ffmpegArgs,
"-i", filePath,
...extraArgs,
targetPath
];
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => { execFile("ffmpeg", args, (error, stdout, stderr) => {
if (error) { if (error) {
reject(`error: ${error}`); reject(`error: ${error}`);
} }

View File

@@ -1,339 +1,340 @@
import { exec } from "node:child_process"; import { execFile } from "node:child_process";
export const properties = { export const properties = {
from: { from: {
image: [ image: [
"3fr", "3fr",
"8bim", "8bim",
"8bimtext", "8bimtext",
"8bimwtext", "8bimwtext",
"app1", "app1",
"app1jpeg", "app1jpeg",
"art", "art",
"arw", "arw",
"avs", "avs",
"b", "b",
"bie", "bie",
"bigtiff", "bigtiff",
"bmp", "bmp",
"c", "c",
"cals", "cals",
"caption", "caption",
"cin", "cin",
"cmyk", "cmyk",
"cmyka", "cmyka",
"cr2", "cr2",
"crw", "crw",
"cur", "cur",
"cut", "cut",
"dcm", "dcm",
"dcr", "dcr",
"dcx", "dcx",
"dng", "dng",
"dpx", "dpx",
"epdf", "epdf",
"epi", "epi",
"eps", "eps",
"epsf", "epsf",
"epsi", "epsi",
"ept", "ept",
"ept2", "ept2",
"ept3", "ept3",
"erf", "erf",
"exif", "exif",
"fax", "fax",
"file", "file",
"fits", "fits",
"fractal", "fractal",
"ftp", "ftp",
"g", "g",
"gif", "gif",
"gif87", "gif87",
"gradient", "gradient",
"gray", "gray",
"graya", "graya",
"heic", "heic",
"heif", "heif",
"hrz", "hrz",
"http", "http",
"icb", "icb",
"icc", "icc",
"icm", "icm",
"ico", "ico",
"icon", "icon",
"identity", "identity",
"image", "image",
"iptc", "iptc",
"iptctext", "iptctext",
"iptcwtext", "iptcwtext",
"jbg", "jbg",
"jbig", "jbig",
"jng", "jng",
"jnx", "jnx",
"jpeg", "jpeg",
"jpg", "jpg",
"k", "k",
"k25", "k25",
"kdc", "kdc",
"label", "label",
"m", "m",
"mac", "mac",
"map", "map",
"mat", "mat",
"mef", "mef",
"miff", "miff",
"mng", "mng",
"mono", "mono",
"mpc", "mpc",
"mrw", "mrw",
"msl", "msl",
"mtv", "mtv",
"mvg", "mvg",
"nef", "nef",
"null", "null",
"o", "o",
"orf", "orf",
"otb", "otb",
"p7", "p7",
"pal", "pal",
"palm", "palm",
"pam", "pam",
"pbm", "pbm",
"pcd", "pcd",
"pcds", "pcds",
"pct", "pct",
"pcx", "pcx",
"pdb", "pdb",
"pdf", "pdf",
"pef", "pef",
"pfa", "pfa",
"pfb", "pfb",
"pgm", "pgm",
"picon", "picon",
"pict", "pict",
"pix", "pix",
"plasma", "plasma",
"png", "png",
"png00", "png00",
"png24", "png24",
"png32", "png32",
"png48", "png48",
"png64", "png64",
"png8", "png8",
"pnm", "pnm",
"ppm", "ppm",
"ps", "ps",
"ptif", "ptif",
"pwp", "pwp",
"r", "r",
"raf", "raf",
"ras", "ras",
"rgb", "rgb",
"rgba", "rgba",
"rla", "rla",
"rle", "rle",
"sct", "sct",
"sfw", "sfw",
"sgi", "sgi",
"sr2", "sr2",
"srf", "srf",
"stegano", "stegano",
"sun", "sun",
"svg", "svg",
"svgz", "svgz",
"text", "text",
"tga", "tga",
"tif", "tif",
"tiff", "tiff",
"tile", "tile",
"tim", "tim",
"topol", "topol",
"ttf", "ttf",
"txt", "txt",
"uyvy", "uyvy",
"vda", "vda",
"vicar", "vicar",
"vid", "vid",
"viff", "viff",
"vst", "vst",
"wbmp", "wbmp",
"webp", "webp",
"wmf", "wmf",
"wpg", "wpg",
"x3f", "x3f",
"xbm", "xbm",
"xc", "xc",
"xcf", "xcf",
"xmp", "xmp",
"xpm", "xpm",
"xv", "xv",
"xwd", "xwd",
"y", "y",
"yuv", "yuv",
], ],
}, },
to: { to: {
image: [ image: [
"8bim", "8bim",
"8bimtext", "8bimtext",
"8bimwtext", "8bimwtext",
"app1", "app1",
"app1jpeg", "app1jpeg",
"art", "art",
"avs", "avs",
"b", "b",
"bie", "bie",
"bigtiff", "bigtiff",
"bmp", "bmp",
"bmp2", "bmp2",
"bmp3", "bmp3",
"brf", "brf",
"c", "c",
"cals", "cals",
"cin", "cin",
"cmyk", "cmyk",
"cmyka", "cmyka",
"dcx", "dcx",
"dpx", "dpx",
"epdf", "epdf",
"epi", "epi",
"eps", "eps",
"eps2", "eps2",
"eps3", "eps3",
"epsf", "epsf",
"epsi", "epsi",
"ept", "ept",
"ept2", "ept2",
"ept3", "ept3",
"exif", "exif",
"fax", "fax",
"fits", "fits",
"g", "g",
"gif", "gif",
"gif87", "gif87",
"gray", "gray",
"graya", "graya",
"histogram", "histogram",
"html", "html",
"icb", "icb",
"icc", "icc",
"icm", "icm",
"info", "info",
"iptc", "iptc",
"iptctext", "iptctext",
"iptcwtext", "iptcwtext",
"isobrl", "isobrl",
"isobrl6", "isobrl6",
"jbg", "jbg",
"jbig", "jbig",
"jng", "jng",
"jpeg", "jpeg",
"k", "k",
"m", "m",
"m2v", "m2v",
"map", "map",
"mat", "mat",
"matte", "matte",
"miff", "miff",
"mng", "mng",
"mono", "mono",
"mpc", "mpc",
"mpeg", "mpeg",
"mpg", "mpg",
"msl", "msl",
"mtv", "mtv",
"mvg", "mvg",
"null", "null",
"o", "o",
"otb", "otb",
"p7", "p7",
"pal", "pal",
"pam", "pam",
"pbm", "pbm",
"pcd", "pcd",
"pcds", "pcds",
"pcl", "pcl",
"pct", "pct",
"pcx", "pcx",
"pdb", "pdb",
"pdf", "pdf",
"pgm", "pgm",
"picon", "picon",
"pict", "pict",
"png", "png",
"png00", "png00",
"png24", "png24",
"png32", "png32",
"png48", "png48",
"png64", "png64",
"png8", "png8",
"pnm", "pnm",
"ppm", "ppm",
"preview", "preview",
"ps", "ps",
"ps2", "ps2",
"ps3", "ps3",
"ptif", "ptif",
"r", "r",
"ras", "ras",
"rgb", "rgb",
"rgba", "rgba",
"sgi", "sgi",
"shtml", "shtml",
"sun", "sun",
"text", "text",
"tga", "tga",
"tiff", "tiff",
"txt", "txt",
"ubrl", "ubrl",
"ubrl6", "ubrl6",
"uil", "uil",
"uyvy", "uyvy",
"vda", "vda",
"vicar", "vicar",
"vid", "vid",
"viff", "viff",
"vst", "vst",
"wbmp", "wbmp",
"webp", "webp",
"x", "x",
"xbm", "xbm",
"xmp", "xmp",
"xpm", "xpm",
"xv", "xv",
"xwd", "xwd",
"y", "y",
"yuv", "yuv",
], ],
}, },
}; };
export function convert( export function convert(
filePath: string, filePath: string,
fileType: string, fileType: string,
convertTo: string, convertTo: string,
targetPath: string, targetPath: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown, options?: unknown,
): Promise<string> { ): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec( execFile(
`gm convert "${filePath}" "${targetPath}"`, "gm",
(error, stdout, stderr) => { ["convert", filePath, targetPath],
if (error) { (error, stdout, stderr) => {
reject(`error: ${error}`); if (error) {
} reject(`error: ${error}`);
}
if (stdout) {
console.log(`stdout: ${stdout}`); if (stdout) {
} console.log(`stdout: ${stdout}`);
}
if (stderr) {
console.error(`stderr: ${stderr}`); if (stderr) {
} console.error(`stderr: ${stderr}`);
}
resolve("Done");
}, resolve("Done");
); },
}); );
} });
}

View File

@@ -1,64 +1,59 @@
import { exec } from "node:child_process"; import { execFile } from "node:child_process";
export const properties = { export const properties = {
from: { from: {
images: [ images: ["svg", "pdf", "eps", "ps", "wmf", "emf", "png"],
"svg", },
"pdf", to: {
"eps", images: [
"ps", "dxf",
"wmf", "emf",
"emf", "eps",
"png" "fxg",
] "gpl",
}, "hpgl",
to: { "html",
images: [ "odg",
"dxf", "pdf",
"emf", "png",
"eps", "pov",
"fxg", "ps",
"gpl", "sif",
"hpgl", "svg",
"html", "svgz",
"odg", "tex",
"pdf", "wmf",
"png", ],
"pov", },
"ps", };
"sif",
"svg",
"svgz",
"tex",
"wmf",
]
},
};
export function convert( export function convert(
filePath: string, filePath: string,
fileType: string, fileType: string,
convertTo: string, convertTo: string,
targetPath: string, targetPath: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown, options?: unknown,
): Promise<string> { ): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(`inkscape "${filePath}" -o "${targetPath}"`, (error, stdout, stderr) => { execFile(
"inkscape",
[filePath, "-o", targetPath],
(error, stdout, stderr) => {
if (error) { if (error) {
reject(`error: ${error}`); reject(`error: ${error}`);
} }
if (stdout) { if (stdout) {
console.log(`stdout: ${stdout}`); console.log(`stdout: ${stdout}`);
} }
if (stderr) { if (stderr) {
console.error(`stderr: ${stderr}`); console.error(`stderr: ${stderr}`);
} }
resolve("Done"); resolve("Done");
}); },
}); );
} });
}

52
src/converters/libheif.ts Normal file
View File

@@ -0,0 +1,52 @@
import { execFile } from "child_process";
export const properties = {
from: {
images: [
"avci",
"avcs",
"avif",
"h264",
"heic",
"heics",
"heif",
"heifs",
"mkv",
"mp4",
],
},
to: {
images: ["jpeg", "png", "y4m"],
},
};
export function convert(
filePath: string,
fileType: string,
convertTo: string,
targetPath: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown,
): Promise<string> {
return new Promise((resolve, reject) => {
execFile(
"heif-convert",
[filePath, targetPath],
(error, stdout, stderr) => {
if (error) {
reject(`error: ${error}`);
}
if (stdout) {
console.log(`stdout: ${stdout}`);
}
if (stderr) {
console.error(`stderr: ${stderr}`);
}
resolve("Done");
},
);
});
}

View File

@@ -1,71 +1,71 @@
import { exec } from "node:child_process"; import { execFile } from "node:child_process";
// declare possible conversions // declare possible conversions
export const properties = { export const properties = {
from: { from: {
jxl: ["jxl"], jxl: ["jxl"],
images: [ images: [
"apng", "apng",
"exr", "exr",
"gif", "gif",
"jpeg", "jpeg",
"pam", "pam",
"pfm", "pfm",
"pgm", "pgm",
"pgx", "pgx",
"png", "png",
"ppm", "ppm",
], ],
}, },
to: { to: {
jxl: [ jxl: [
"apng", "apng",
"exr", "exr",
"gif", "gif",
"jpeg", "jpeg",
"pam", "pam",
"pfm", "pfm",
"pgm", "pgm",
"pgx", "pgx",
"png", "png",
"ppm", "ppm",
], ],
images: ["jxl"], images: ["jxl"],
}, },
}; };
export function convert( export function convert(
filePath: string, filePath: string,
fileType: string, fileType: string,
convertTo: string, convertTo: string,
targetPath: string, targetPath: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown, options?: unknown,
): Promise<string> { ): Promise<string> {
let tool = ""; let tool = "";
if (fileType === "jxl") { if (fileType === "jxl") {
tool = "djxl"; tool = "djxl";
} }
if (convertTo === "jxl") { if (convertTo === "jxl") {
tool = "cjxl"; tool = "cjxl";
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(`${tool} "${filePath}" "${targetPath}"`, (error, stdout, stderr) => { execFile(tool, [filePath, targetPath], (error, stdout, stderr) => {
if (error) { if (error) {
reject(`error: ${error}`); reject(`error: ${error}`);
} }
if (stdout) { if (stdout) {
console.log(`stdout: ${stdout}`); console.log(`stdout: ${stdout}`);
} }
if (stderr) { if (stderr) {
console.error(`stderr: ${stderr}`); console.error(`stderr: ${stderr}`);
} }
resolve("Done"); resolve("Done");
}); });
}); });
} }

View File

@@ -9,6 +9,7 @@ import { convert as convertresvg, properties as propertiesresvg } from "./resvg"
import { convert as convertImage, properties as propertiesImage } from "./vips"; import { convert as convertImage, properties as propertiesImage } from "./vips";
import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex"; import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex";
import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre"; import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre";
import { convert as convertLibheif, properties as propertiesLibheif } from "./libheif";
// This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular // This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular
@@ -53,6 +54,10 @@ const properties: Record<
properties: propertiesImage, properties: propertiesImage,
converter: convertImage, converter: convertImage,
}, },
libheif: {
properties: propertiesLibheif,
converter: convertLibheif,
},
xelatex: { xelatex: {
properties: propertiesxelatex, properties: propertiesxelatex,
converter: convertxelatex, converter: convertxelatex,

View File

@@ -1,156 +1,162 @@
import { exec } from "node:child_process"; import { execFile } from "node:child_process";
export const properties = { export const properties = {
from: { from: {
text: [ text: [
"textile", "textile",
"tikiwiki", "tikiwiki",
"tsv", "tsv",
"twiki", "twiki",
"typst", "typst",
"vimwiki", "vimwiki",
"biblatex", "biblatex",
"bibtex", "bibtex",
"bits", "bits",
"commonmark", "commonmark",
"commonmark_x", "commonmark_x",
"creole", "creole",
"csljson", "csljson",
"csv", "csv",
"djot", "djot",
"docbook", "docbook",
"docx", "docx",
"dokuwiki", "dokuwiki",
"endnotexml", "endnotexml",
"epub", "epub",
"fb2", "fb2",
"gfm", "gfm",
"haddock", "haddock",
"html", "html",
"ipynb", "ipynb",
"jats", "jats",
"jira", "jira",
"json", "json",
"latex", "latex",
"man", "man",
"markdown", "markdown",
"markdown_mmd", "markdown_mmd",
"markdown_phpextra", "markdown_phpextra",
"markdown_strict", "markdown_strict",
"mediawiki", "mediawiki",
"muse", "muse",
"pandoc native", "pandoc native",
"opml", "opml",
"org", "org",
"ris", "ris",
"rst", "rst",
"rtf", "rtf",
"t2t", "t2t",
], ],
}, },
to: { to: {
text: [ text: [
"tei", "tei",
"texinfo", "texinfo",
"textile", "textile",
"typst", "typst",
"xwiki", "xwiki",
"zimwiki", "zimwiki",
"asciidoc", "asciidoc",
"asciidoc_legacy", "asciidoc_legacy",
"asciidoctor", "asciidoctor",
"beamer", "beamer",
"biblatex", "biblatex",
"bibtex", "bibtex",
"chunkedhtml", "chunkedhtml",
"commonmark", "commonmark",
"commonmark_x", "commonmark_x",
"context", "context",
"csljson", "csljson",
"djot", "djot",
"docbook", "docbook",
"docbook4", "docbook4",
"docbook5", "docbook5",
"docx", "docx",
"dokuwiki", "dokuwiki",
"dzslides", "dzslides",
"epub", "epub",
"epub2", "epub2",
"epub3", "epub3",
"fb2", "fb2",
"gfm", "gfm",
"haddock", "haddock",
"html", "html",
"html4", "html4",
"html5", "html5",
"icml", "icml",
"ipynb", "ipynb",
"jats", "jats",
"jats_archiving", "jats_archiving",
"jats_articleauthoring", "jats_articleauthoring",
"jats_publishing", "jats_publishing",
"jira", "jira",
"json", "json",
"latex", "latex",
"man", "man",
"markdown", "markdown",
"markdown_mmd", "markdown_mmd",
"markdown_phpextra", "markdown_phpextra",
"markdown_strict", "markdown_strict",
"markua", "markua",
"mediawiki", "mediawiki",
"ms", "ms",
"muse", "muse",
"pandoc native", "pandoc native",
"odt", "odt",
"opendocument", "opendocument",
"opml", "opml",
"org", "org",
"pdf", "pdf",
"plain", "plain",
"pptx", "pptx",
"revealjs", "revealjs",
"rst", "rst",
"rtf", "rtf",
"s5", "s5",
"slideous", "slideous",
"slidy", "slidy",
], ],
}, },
}; };
export function convert( export function convert(
filePath: string, filePath: string,
fileType: string, fileType: string,
convertTo: string, convertTo: string,
targetPath: string, targetPath: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown, options?: unknown,
): Promise<string> { ): Promise<string> {
// set xelatex here // set xelatex here
const xelatex = ["pdf", "latex"]; const xelatex = ["pdf", "latex"];
let option = "";
if (xelatex.includes(convertTo)) { // Build arguments array
option = "--pdf-engine=xelatex"; const args: string[] = [];
}
return new Promise((resolve, reject) => { if (xelatex.includes(convertTo)) {
exec( args.push("--pdf-engine=xelatex");
`pandoc ${option} "${filePath}" -f ${fileType} -t ${convertTo} -o "${targetPath}"`, }
(error, stdout, stderr) => {
if (error) { args.push(filePath);
reject(`error: ${error}`); args.push("-f", fileType);
} args.push("-t", convertTo);
args.push("-o", targetPath);
if (stdout) {
console.log(`stdout: ${stdout}`); return new Promise((resolve, reject) => {
} execFile("pandoc", args, (error, stdout, stderr) => {
if (error) {
if (stderr) { reject(`error: ${error}`);
console.error(`stderr: ${stderr}`); }
}
if (stdout) {
resolve("Done"); console.log(`stdout: ${stdout}`);
}, }
);
}); if (stderr) {
} console.error(`stderr: ${stderr}`);
}
resolve("Done");
});
});
}

View File

@@ -1,37 +1,37 @@
import { exec } from "node:child_process"; import { execFile } from "node:child_process";
export const properties = { export const properties = {
from: { from: {
images: ["svg"], images: ["svg"],
}, },
to: { to: {
images: ["png"], images: ["png"],
}, },
}; };
export function convert( export function convert(
filePath: string, filePath: string,
fileType: string, fileType: string,
convertTo: string, convertTo: string,
targetPath: string, targetPath: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown, options?: unknown,
): Promise<string> { ): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(`resvg "${filePath}" "${targetPath}"`, (error, stdout, stderr) => { execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => {
if (error) { if (error) {
reject(`error: ${error}`); reject(`error: ${error}`);
} }
if (stdout) { if (stdout) {
console.log(`stdout: ${stdout}`); console.log(`stdout: ${stdout}`);
} }
if (stderr) { if (stderr) {
console.error(`stderr: ${stderr}`); console.error(`stderr: ${stderr}`);
} }
resolve("Done"); resolve("Done");
}); });
}); });
} }

View File

@@ -1,142 +1,142 @@
import { exec } from "node:child_process"; import { execFile } from "node:child_process";
// declare possible conversions
// declare possible conversions export const properties = {
export const properties = { from: {
from: { images: [
images: [ "avif",
"avif", "bif",
"bif", "csv",
"csv", "exr",
"exr", "fits",
"fits", "gif",
"gif", "hdr.gz",
"hdr.gz", "hdr",
"hdr", "heic",
"heic", "heif",
"heif", "img.gz",
"img.gz", "img",
"img", "j2c",
"j2c", "j2k",
"j2k", "jp2",
"jp2", "jpeg",
"jpeg", "jpx",
"jpx", "jxl",
"jxl", "mat",
"mat", "mrxs",
"mrxs", "ndpi",
"ndpi", "nia.gz",
"nia.gz", "nia",
"nia", "nii.gz",
"nii.gz", "nii",
"nii", "pdf",
"pdf", "pfm",
"pfm", "pgm",
"pgm", "pic",
"pic", "png",
"png", "ppm",
"ppm", "raw",
"raw", "scn",
"scn", "svg",
"svg", "svs",
"svs", "svslide",
"svslide", "szi",
"szi", "tif",
"tif", "tiff",
"tiff", "v",
"v", "vips",
"vips", "vms",
"vms", "vmu",
"vmu", "webp",
"webp", "zip",
"zip", ],
], },
}, to: {
to: { images: [
images: [ "avif",
"avif", "dzi",
"dzi", "fits",
"fits", "gif",
"gif", "hdr.gz",
"hdr.gz", "heic",
"heic", "heif",
"heif", "img.gz",
"img.gz", "j2c",
"j2c", "j2k",
"j2k", "jp2",
"jp2", "jpeg",
"jpeg", "jpx",
"jpx", "jxl",
"jxl", "mat",
"mat", "nia.gz",
"nia.gz", "nia",
"nia", "nii.gz",
"nii.gz", "nii",
"nii", "png",
"png", "tiff",
"tiff", "vips",
"vips", "webp",
"webp", ],
], },
}, options: {
options: { svg: {
svg: { scale: {
scale: { description: "Scale the image up or down",
description: "Scale the image up or down", type: "number",
type: "number", default: 1,
default: 1, },
}, },
}, },
}, };
};
export function convert(
export function convert( filePath: string,
filePath: string, fileType: string,
fileType: string, convertTo: string,
convertTo: string, targetPath: string,
targetPath: string, // eslint-disable-next-line @typescript-eslint/no-unused-vars
// eslint-disable-next-line @typescript-eslint/no-unused-vars options?: unknown,
options?: unknown, ): Promise<string> {
): Promise<string> { // if (fileType === "svg") {
// if (fileType === "svg") { // const scale = options.scale || 1;
// const scale = options.scale || 1; // const metadata = await sharp(filePath).metadata();
// const metadata = await sharp(filePath).metadata();
// if (!metadata || !metadata.width || !metadata.height) {
// if (!metadata || !metadata.width || !metadata.height) { // throw new Error("Could not get metadata from image");
// throw new Error("Could not get metadata from image"); // }
// }
// const newWidth = Math.round(metadata.width * scale);
// const newWidth = Math.round(metadata.width * scale); // const newHeight = Math.round(metadata.height * scale);
// const newHeight = Math.round(metadata.height * scale);
// return await sharp(filePath)
// return await sharp(filePath) // .resize(newWidth, newHeight)
// .resize(newWidth, newHeight) // .toFormat(convertTo)
// .toFormat(convertTo) // .toFile(targetPath);
// .toFile(targetPath); // }
// } let action = "copy";
let action = "copy"; if (fileType === "pdf") {
if (fileType === "pdf") { action = "pdfload";
action = "pdfload"; }
}
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { execFile(
exec( "vips",
`vips ${action} "${filePath}" "${targetPath}"`, [action, filePath, targetPath],
(error, stdout, stderr) => { (error, stdout, stderr) => {
if (error) { if (error) {
reject(`error: ${error}`); reject(`error: ${error}`);
} }
if (stdout) { if (stdout) {
console.log(`stdout: ${stdout}`); console.log(`stdout: ${stdout}`);
} }
if (stderr) { if (stderr) {
console.error(`stderr: ${stderr}`); console.error(`stderr: ${stderr}`);
} }
resolve("Done"); resolve("Done");
}, },
); );
}); });
} }

View File

@@ -1,46 +1,53 @@
import { exec } from "node:child_process"; import { execFile } from "node:child_process";
export const properties = { export const properties = {
from: { from: {
text: ["tex", "latex"], text: ["tex", "latex"],
}, },
to: { to: {
text: ["pdf"], text: ["pdf"],
}, },
}; };
export function convert( export function convert(
filePath: string, filePath: string,
fileType: string, fileType: string,
convertTo: string, convertTo: string,
targetPath: string, targetPath: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown, options?: unknown,
): Promise<string> { ): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "") // const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
const outputPath = targetPath const outputPath = targetPath
.split("/") .split("/")
.slice(0, -1) .slice(0, -1)
.join("/") .join("/")
.replace("./", ""); .replace("./", "");
exec(
`latexmk -xelatex -interaction=nonstopmode -output-directory="${outputPath}" "${filePath}"`, execFile(
(error, stdout, stderr) => { "latexmk",
if (error) { [
reject(`error: ${error}`); "-xelatex",
} "-interaction=nonstopmode",
`-output-directory=${outputPath}`,
if (stdout) { filePath,
console.log(`stdout: ${stdout}`); ],
} (error, stdout, stderr) => {
if (error) {
if (stderr) { reject(`error: ${error}`);
console.error(`stderr: ${stderr}`); }
}
if (stdout) {
resolve("Done"); console.log(`stdout: ${stdout}`);
}, }
);
}); if (stderr) {
} console.error(`stderr: ${stderr}`);
}
resolve("Done");
},
);
});
}

View File

@@ -1,5 +1,6 @@
import { exec } from "node:child_process"; import { exec } from "node:child_process";
import { version } from "../../package.json"; import { version } from "../../package.json";
console.log(`ConvertX v${version}`); console.log(`ConvertX v${version}`);
if (process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === "production") {
@@ -113,6 +114,16 @@ if (process.env.NODE_ENV === "production") {
} }
}); });
exec("heif-info -v", (error, stdout) => {
if (error) {
console.error("libheif is not installed");
}
if (stdout) {
console.log(`libheif v${stdout.split("\n")[0]}`);
}
});
exec("bun -v", (error, stdout) => { exec("bun -v", (error, stdout) => {
if (error) { if (error) {
console.error("Bun is not installed. wait what"); console.error("Bun is not installed. wait what");

View File

@@ -7,6 +7,7 @@ import { jwt, type JWTPayloadSpec } from "@elysiajs/jwt";
import { staticPlugin } from "@elysiajs/static"; import { staticPlugin } from "@elysiajs/static";
import { Database } from "bun:sqlite"; import { Database } from "bun:sqlite";
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import sanitize from "sanitize-filename";
import { BaseHtml } from "./components/base"; import { BaseHtml } from "./components/base";
import { Header } from "./components/header"; import { Header } from "./components/header";
import { import {
@@ -153,7 +154,12 @@ const app = new Elysia({
return ( return (
<BaseHtml title="ConvertX | Setup" webroot={WEBROOT}> <BaseHtml title="ConvertX | Setup" webroot={WEBROOT}>
<main class="mx-auto w-full max-w-4xl px-4"> <main
class={`
mx-auto w-full max-w-4xl px-2
sm:px-4
`}
>
<h1 class="my-8 text-3xl">Welcome to ConvertX!</h1> <h1 class="my-8 text-3xl">Welcome to ConvertX!</h1>
<article class="article p-0"> <article class="article p-0">
<header class="w-full bg-neutral-800 p-4"> <header class="w-full bg-neutral-800 p-4">
@@ -217,7 +223,12 @@ const app = new Elysia({
accountRegistration={ACCOUNT_REGISTRATION} accountRegistration={ACCOUNT_REGISTRATION}
allowUnauthenticated={ALLOW_UNAUTHENTICATED} allowUnauthenticated={ALLOW_UNAUTHENTICATED}
/> />
<main class="w-full px-4"> <main
class={`
w-full px-2
sm:px-4
`}
>
<article class="article"> <article class="article">
<form method="post" class="flex flex-col gap-4"> <form method="post" class="flex flex-col gap-4">
<fieldset class="mb-4 flex flex-col gap-4"> <fieldset class="mb-4 flex flex-col gap-4">
@@ -343,7 +354,12 @@ const app = new Elysia({
accountRegistration={ACCOUNT_REGISTRATION} accountRegistration={ACCOUNT_REGISTRATION}
allowUnauthenticated={ALLOW_UNAUTHENTICATED} allowUnauthenticated={ALLOW_UNAUTHENTICATED}
/> />
<main class="w-full px-4"> <main
class={`
w-full px-2
sm:px-4
`}
>
<article class="article"> <article class="article">
<form method="post" class="flex flex-col gap-4"> <form method="post" class="flex flex-col gap-4">
<fieldset class="mb-4 flex flex-col gap-4"> <fieldset class="mb-4 flex flex-col gap-4">
@@ -556,7 +572,12 @@ const app = new Elysia({
allowUnauthenticated={ALLOW_UNAUTHENTICATED} allowUnauthenticated={ALLOW_UNAUTHENTICATED}
loggedIn loggedIn
/> />
<main class="w-full px-4"> <main
class={`
w-full px-2
sm:px-4
`}
>
<article class="article"> <article class="article">
<h1 class="mb-4 text-xl">Convert</h1> <h1 class="mb-4 text-xl">Convert</h1>
<div class="mb-4 max-h-[50vh] overflow-y-auto scrollbar-thin"> <div class="mb-4 max-h-[50vh] overflow-y-auto scrollbar-thin">
@@ -564,7 +585,7 @@ const app = new Elysia({
id="file-list" id="file-list"
class={` class={`
w-full table-auto rounded bg-neutral-900 w-full table-auto rounded bg-neutral-900
[&_td]:p-4 [&_td]:p-4 [&_td]:first:max-w-[30vw] [&_td]:first:truncate
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800 [&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
`} `}
/> />
@@ -866,6 +887,10 @@ const app = new Elysia({
const converterName = body.convert_to.split(",")[1]; const converterName = body.convert_to.split(",")[1];
const fileNames = JSON.parse(body.file_names) as string[]; const fileNames = JSON.parse(body.file_names) as string[];
for (let i = 0; i < fileNames.length; i++) {
fileNames[i] = sanitize(fileNames[i] || "");
}
if (!Array.isArray(fileNames) || fileNames.length === 0) { if (!Array.isArray(fileNames) || fileNames.length === 0) {
return redirect(`${WEBROOT}/`, 302); return redirect(`${WEBROOT}/`, 302);
} }
@@ -942,7 +967,8 @@ const app = new Elysia({
let userJobs = db let userJobs = db
.query("SELECT * FROM jobs WHERE user_id = ?") .query("SELECT * FROM jobs WHERE user_id = ?")
.as(Jobs) .as(Jobs)
.all(user.id); .all(user.id)
.reverse();
for (const job of userJobs) { for (const job of userJobs) {
const files = db const files = db
@@ -964,31 +990,75 @@ const app = new Elysia({
allowUnauthenticated={ALLOW_UNAUTHENTICATED} allowUnauthenticated={ALLOW_UNAUTHENTICATED}
loggedIn loggedIn
/> />
<main class="w-full px-4"> <main
class={`
w-full px-2
sm:px-4
`}
>
<article class="article"> <article class="article">
<h1 class="mb-4 text-xl">Results</h1> <h1 class="mb-4 text-xl">Results</h1>
<table <table
class={` class={`
w-full table-auto rounded bg-neutral-900 text-left w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
[&_td]:p-4 [&_td]:p-4
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800 [&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
`} `}
> >
<thead> <thead>
<tr> <tr>
<th class="px-4 py-2">Time</th> <th
<th class="px-4 py-2">Files</th> class={`
<th class="px-4 py-2">Files Done</th> px-2 py-2
<th class="px-4 py-2">Status</th> sm:px-4
<th class="px-4 py-2">View</th> `}
>
Time
</th>
<th
class={`
px-2 py-2
sm:px-4
`}
>
Files
</th>
<th
class={`
px-2 py-2
max-sm:hidden
sm:px-4
`}
>
Files Done
</th>
<th
class={`
px-2 py-2
sm:px-4
`}
>
Status
</th>
<th
class={`
px-2 py-2
sm:px-4
`}
>
View
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{userJobs.map((job) => ( {userJobs.map((job) => (
<tr> <tr>
<td safe>{job.date_created}</td> <td safe>
{job.date_created.split("T")[1]?.split(".")[0] ??
job.date_created}
</td>
<td>{job.num_files}</td> <td>{job.num_files}</td>
<td>{job.finished_files}</td> <td class="max-sm:hidden">{job.finished_files}</td>
<td safe>{job.status}</td> <td safe>{job.status}</td>
<td> <td>
<a <a
@@ -1055,7 +1125,12 @@ const app = new Elysia({
allowUnauthenticated={ALLOW_UNAUTHENTICATED} allowUnauthenticated={ALLOW_UNAUTHENTICATED}
loggedIn loggedIn
/> />
<main class="w-full px-4"> <main
class={`
w-full px-2
sm:px-4
`}
>
<article class="article"> <article class="article">
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<h1 class="text-xl">Results</h1> <h1 class="text-xl">Results</h1>
@@ -1095,16 +1170,46 @@ const app = new Elysia({
> >
<thead> <thead>
<tr> <tr>
<th class="px-4 py-2">Converted File Name</th> <th
<th class="px-4 py-2">Status</th> class={`
<th class="px-4 py-2">View</th> px-2 py-2
<th class="px-4 py-2">Download</th> sm:px-4
`}
>
Converted File Name
</th>
<th
class={`
px-2 py-2
sm:px-4
`}
>
Status
</th>
<th
class={`
px-2 py-2
sm:px-4
`}
>
View
</th>
<th
class={`
px-2 py-2
sm:px-4
`}
>
Download
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{files.map((file) => ( {files.map((file) => (
<tr> <tr>
<td safe>{file.output_file_name}</td> <td safe class="max-w-[20vw] truncate">
{file.output_file_name}
</td>
<td safe>{file.status}</td> <td safe>{file.status}</td>
<td> <td>
<a <a
@@ -1217,16 +1322,46 @@ const app = new Elysia({
> >
<thead> <thead>
<tr> <tr>
<th class="px-4 py-2">Converted File Name</th> <th
<th class="px-4 py-2">Status</th> class={`
<th class="px-4 py-2">View</th> px-2 py-2
<th class="px-4 py-2">Download</th> sm:px-4
`}
>
Converted File Name
</th>
<th
class={`
px-2 py-2
sm:px-4
`}
>
Status
</th>
<th
class={`
px-2 py-2
sm:px-4
`}
>
View
</th>
<th
class={`
px-2 py-2
sm:px-4
`}
>
Download
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{files.map((file) => ( {files.map((file) => (
<tr> <tr>
<td safe>{file.output_file_name}</td> <td safe class="max-w-[20vw] truncate">
{file.output_file_name}
</td>
<td safe>{file.status}</td> <td safe>{file.status}</td>
<td> <td>
<a <a
@@ -1281,7 +1416,7 @@ const app = new Elysia({
// parse from url encoded string // parse from url encoded string
const userId = decodeURIComponent(params.userId); const userId = decodeURIComponent(params.userId);
const jobId = decodeURIComponent(params.jobId); const jobId = decodeURIComponent(params.jobId);
const fileName = decodeURIComponent(params.fileName); const fileName = sanitize(decodeURIComponent(params.fileName));
const filePath = `${outputDir}${userId}/${jobId}/${fileName}`; const filePath = `${outputDir}${userId}/${jobId}/${fileName}`;
return Bun.file(filePath); return Bun.file(filePath);
@@ -1305,7 +1440,12 @@ const app = new Elysia({
allowUnauthenticated={ALLOW_UNAUTHENTICATED} allowUnauthenticated={ALLOW_UNAUTHENTICATED}
loggedIn loggedIn
/> />
<main class="w-full px-4"> <main
class={`
w-full px-2
sm:px-4
`}
>
<article class="article"> <article class="article">
<h1 class="mb-4 text-xl">Converters</h1> <h1 class="mb-4 text-xl">Converters</h1>
<table <table

View File

@@ -1,4 +1,4 @@
@import 'tailwindcss'; @import "tailwindcss";
@plugin 'tailwind-scrollbar'; @plugin 'tailwind-scrollbar';
@@ -37,42 +37,42 @@
} }
@utility article { @utility article {
@apply p-4 mb-4 bg-neutral-800/40 w-full mx-auto max-w-4xl rounded-sm; @apply px-2 sm:px-4 py-4 mb-4 bg-neutral-800/40 w-full mx-auto max-w-4xl rounded-sm;
} }
@utility btn-primary { @utility btn-primary {
@apply bg-accent-500 text-contrast rounded-sm p-4 hover:bg-accent-400 cursor-pointer transition-colors; @apply bg-accent-500 text-contrast rounded-sm p-2 sm:p-4 hover:bg-accent-400 cursor-pointer transition-colors;
} }
:root { :root {
--contrast: 255, 255, 255; --contrast: 255, 255, 255;
--neutral-900: 243, 244, 246; --neutral-900: 243, 244, 246;
--neutral-800: 229, 231, 235; --neutral-800: 229, 231, 235;
--neutral-700: 209, 213, 219; --neutral-700: 209, 213, 219;
--neutral-600: 156, 163, 175; --neutral-600: 156, 163, 175;
--neutral-500: 180, 180, 180; --neutral-500: 180, 180, 180;
--neutral-400: 75, 85, 99; --neutral-400: 75, 85, 99;
--neutral-300: 55, 65, 81; --neutral-300: 55, 65, 81;
--neutral-200: 31, 41, 55; --neutral-200: 31, 41, 55;
--neutral-100: 17, 24, 39; --neutral-100: 17, 24, 39;
--accent-400: 132, 204, 22; --accent-400: 132, 204, 22;
--accent-500: 101, 163, 13; --accent-500: 101, 163, 13;
--accent-600: 77, 124, 15; --accent-600: 77, 124, 15;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--contrast: 0, 0, 0; --contrast: 0, 0, 0;
--neutral-900: 17, 24, 39; --neutral-900: 17, 24, 39;
--neutral-800: 31, 41, 55; --neutral-800: 31, 41, 55;
--neutral-700: 55, 65, 81; --neutral-700: 55, 65, 81;
--neutral-600: 75, 85, 99; --neutral-600: 75, 85, 99;
--neutral-500: 107, 114, 128; --neutral-500: 107, 114, 128;
--neutral-300: 209, 213, 219; --neutral-300: 209, 213, 219;
--neutral-400: 156, 163, 175; --neutral-400: 156, 163, 175;
--neutral-200: 229, 231, 235; --neutral-200: 229, 231, 235;
--accent-600: 101, 163, 13; --accent-600: 101, 163, 13;
--accent-500: 132, 204, 22; --accent-500: 132, 204, 22;
--accent-400: 163, 230, 53; --accent-400: 163, 230, 53;
} }
} }