43 Commits

Author SHA1 Message Date
Emrik Östling
5b6e70eb3a Merge pull request #246 from C4illin/release-please--branches--main--components--convertx-frontend
chore(main): release 0.12.1
2025-04-01 14:15:28 +02:00
Emrik Östling
cdae798fcf chore: rollback to 1.2.2
issue: #235
2025-03-20 11:05:24 +01:00
Emrik Östling
bcc827a81b chore(main): release 0.12.1 2025-03-20 09:40:15 +01:00
Emrik Östling
84274b9c55 chore: revert to bun 1.2.3
issue: #235
2025-03-20 09:39:36 +01:00
Emrik Östling
20c6f8249e Merge pull request #245 from C4illin/fix/#235/change-to-canary-bun
fix: change to canary bun
2025-03-19 21:19:45 +01:00
C4illin
8f0ea2a592 fix: change to canary bun
issue: #235
2025-03-19 20:30:50 +01:00
Emrik Östling
a29e4a930a Merge pull request #242 from C4illin/downgrade-bun-to-1.2.2
chore: downgrade bun to 1.2.2
2025-03-10 13:12:31 +01:00
Emrik Östling
4549c96ae3 chore: remove old labels 2025-03-10 12:38:17 +01:00
Emrik Östling
bc64094c04 chore: downgrade bun to 1.2.2
issue: #235
2025-03-10 12:34:47 +01:00
Emrik Östling
fa58827ad5 Merge pull request #240 from C4illin/donwgrade-bun-to-1.2.3
chore: downgrade bun to 1.2.3
2025-03-09 22:43:02 +01:00
C4illin
8f27be0e3d chore: downgrade bun 2025-03-09 21:10:59 +01:00
C4illin
df43df1178 Merge branch 'main' of https://github.com/C4illin/ConvertX 2025-03-09 21:09:58 +01:00
C4illin
c2beb4a227 chore: add default full opacity 2025-03-09 21:09:53 +01:00
Emrik Östling
9277c27a50 chore: change security url 2025-03-09 21:07:04 +01:00
Emrik Östling
171ecd6884 chore: Create SECURITY.md 2025-03-09 21:04:18 +01:00
Emrik Östling
dca29f7e5a Merge pull request #239 from C4illin/remove-slim
build: remove slim for tailwind
2025-03-09 16:40:20 +01:00
C4illin
318acc20bd build: remove slim for tailwind 2025-03-08 01:08:00 +01:00
C4illin
f433493d57 chore: remove @elysiajs/cookie 2025-03-08 00:28:27 +01:00
C4illin
19970fc132 chore: fix lint 2025-03-06 21:09:02 +01:00
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
21 changed files with 1270 additions and 1140 deletions

View File

@@ -1,5 +1,31 @@
# Changelog # Changelog
## [0.12.1](https://github.com/C4illin/ConvertX/compare/v0.12.0...v0.12.1) (2025-03-20)
### Bug Fixes
* change to canary bun ([20c6f82](https://github.com/C4illin/ConvertX/commit/20c6f8249e03e03f4517e5b8b0d421cb6291bc1a))
* change to canary bun ([8f0ea2a](https://github.com/C4illin/ConvertX/commit/8f0ea2a592f01af176caa745020f0a3514945357)), closes [#235](https://github.com/C4illin/ConvertX/issues/235)
## [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) ## [0.11.1](https://github.com/C4illin/ConvertX/compare/v0.11.0...v0.11.1) (2025-02-07)

View File

@@ -20,10 +20,7 @@ ENV PATH=/root/.cargo/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
RUN cargo install resvg RUN cargo install resvg
# copy node_modules from temp directory FROM base AS prerelease
# then copy all (non-ignored) project files into the image
# will switch to alpine again when it works
FROM oven/bun:1.2.2-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 . .
@@ -33,9 +30,8 @@ RUN bun run build
# copy production dependencies and source code into final image # copy production dependencies and source code into final image
FROM base AS release FROM base AS release
LABEL maintainer="Emrik Östling (C4illin)"
LABEL description="ConvertX: self-hosted online file converter supporting 700+ file formats." RUN apk --no-cache add qt6-qtbase-private-dev libheif-tools --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/
LABEL repo="https://github.com/C4illin/ConvertX"
# install additional dependencies # install additional dependencies
RUN apk --no-cache add \ RUN apk --no-cache add \
@@ -49,6 +45,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 +55,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.
@@ -67,8 +63,6 @@ RUN apk --no-cache add calibre --repository=http://dl-cdn.alpinelinux.org/alpine
COPY --from=install /temp/prod/node_modules node_modules COPY --from=install /temp/prod/node_modules node_modules
COPY --from=builder /root/.cargo/bin/resvg /usr/local/bin/resvg COPY --from=builder /root/.cargo/bin/resvg /usr/local/bin/resvg
COPY --from=prerelease /app/public/generated.css /app/public/ COPY --from=prerelease /app/public/generated.css /app/public/
# COPY --from=prerelease /app/src/index.tsx /app/src/
# COPY --from=prerelease /app/package.json .
COPY . . COPY . .
EXPOSE 3000/tcp EXPOSE 3000/tcp

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 |

9
SECURITY.md Normal file
View File

@@ -0,0 +1,9 @@
# Security Policy
## Supported Versions
Only the latest release is supported
## Reporting a Vulnerability
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/C4illin/ConvertX/security/advisories/new) tab.

View File

@@ -4,40 +4,39 @@
"": { "": {
"name": "convertx-frontend", "name": "convertx-frontend",
"dependencies": { "dependencies": {
"@elysiajs/cookie": "^0.8.0",
"@elysiajs/html": "^1.2.0", "@elysiajs/html": "^1.2.0",
"@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/node": "^22.13.1",
"@types/node": "^22.10.10",
"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.4",
"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",
}, },
}, },
}, },
@@ -60,8 +59,6 @@
"@babel/types": ["@babel/types@7.26.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg=="], "@babel/types": ["@babel/types@7.26.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg=="],
"@elysiajs/cookie": ["@elysiajs/cookie@0.8.0", "", { "dependencies": { "@types/cookie": "^0.5.1", "@types/cookie-signature": "^1.1.0", "cookie": "^0.5.0", "cookie-signature": "^1.2.1" }, "peerDependencies": { "elysia": ">= 0.8.0" } }, "sha512-CUtDwdYEoN0BcQ3SgZrB4x5nrbM4ih0sMhMuKKdMlEvqLtmRQDfq9KBCrMJW6L/Q0tPH0JLRqwjEbVb6rJufCw=="],
"@elysiajs/html": ["@elysiajs/html@1.2.0", "", { "dependencies": { "@kitajs/html": "^4.1.0", "@kitajs/ts-html-plugin": "^4.0.1" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-j/exB9TLn2/KOMWRfPcJ7MNOsmr85uHRjC+kKPLyb1lRtNDWzgab4deUQrxtIv4gt4PDSKmywNnhyD+GrdnH3w=="], "@elysiajs/html": ["@elysiajs/html@1.2.0", "", { "dependencies": { "@kitajs/html": "^4.1.0", "@kitajs/ts-html-plugin": "^4.0.1" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-j/exB9TLn2/KOMWRfPcJ7MNOsmr85uHRjC+kKPLyb1lRtNDWzgab4deUQrxtIv4gt4PDSKmywNnhyD+GrdnH3w=="],
"@elysiajs/jwt": ["@elysiajs/jwt@1.2.0", "", { "dependencies": { "jose": "^4.14.4" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-5iMoZucIKNAqPKW3n6RBIyCnDWG3kOcqA4WZKtqEff+IjV6AN3dlMSE2XsS0xjIvusLD0UBXS8cxQ9NwIcj6ew=="], "@elysiajs/jwt": ["@elysiajs/jwt@1.2.0", "", { "dependencies": { "jose": "^4.14.4" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-5iMoZucIKNAqPKW3n6RBIyCnDWG3kOcqA4WZKtqEff+IjV6AN3dlMSE2XsS0xjIvusLD0UBXS8cxQ9NwIcj6ew=="],
@@ -190,16 +187,10 @@
"@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="], "@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="],
"@types/cookie": ["@types/cookie@0.5.4", "", {}, "sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA=="],
"@types/cookie-signature": ["@types/cookie-signature@1.1.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-2OhrZV2LVnUAXklUFwuYUTokalh/dUb8rqt70OW6ByMSxYpauPZ+kfNLknX3aJyjY5iu8i3cUyoLZP9Fn37tTg=="],
"@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="], "@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="],
"@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/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=="],
@@ -290,9 +281,7 @@
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"cookie": ["cookie@0.5.0", "", {}, "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="], "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
@@ -356,7 +345,7 @@
"eslint": ["eslint@9.19.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.19.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA=="], "eslint": ["eslint@9.19.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.19.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA=="],
"eslint-plugin-readable-tailwind": ["eslint-plugin-readable-tailwind@2.0.0-beta.1", "", { "dependencies": { "enhanced-resolve": "^5.18.0", "postcss": "^8.5.1", "postcss-import": "^16.1.0", "synckit": "^0.9.2" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", "tailwindcss": "^3.3.0 || ^4.0.0" } }, "sha512-zZefMqO6cvN/6UsbRWaOPDSBQmjpCOx5ZVlA0kNhemQzfaBC1Z6tIGJGyv728ktzcSMvUvIJE+miunI0IGLZMg=="], "eslint-plugin-readable-tailwind": ["eslint-plugin-readable-tailwind@2.0.0-beta.4", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "postcss": "^8.5.3", "postcss-import": "^16.1.0", "synckit": "^0.9.2" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", "tailwindcss": "^3.3.0 || ^4.0.0" } }, "sha512-lBCT6YtH4pqi3VHRYjGE4uMrtkRx1PrArRdFcpK+a5E8jrkGxOZDgjgOf3/k2k2E50yFKG+T9sxnS/4rFe0TWg=="],
"eslint-plugin-simple-import-sort": ["eslint-plugin-simple-import-sort@12.1.1", "", { "peerDependencies": { "eslint": ">=5.0.0" } }, "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA=="], "eslint-plugin-simple-import-sort": ["eslint-plugin-simple-import-sort@12.1.1", "", { "peerDependencies": { "eslint": ">=5.0.0" } }, "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA=="],
@@ -414,7 +403,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 +675,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=="],
@@ -730,6 +721,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=="],
@@ -752,6 +745,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,8 +785,6 @@
"@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=="],
"@types/cookie-signature/@types/node": ["@types/node@22.13.0", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA=="],
"@types/ws/@types/node": ["@types/node@22.13.0", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA=="], "@types/ws/@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/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/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=="],
@@ -836,7 +829,9 @@
"dir-glob/path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], "dir-glob/path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"elysia/cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], "eslint-plugin-readable-tailwind/enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
"eslint-plugin-readable-tailwind/postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
"eslint-plugin-tailwindcss/eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], "eslint-plugin-tailwindcss/eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],

View File

@@ -1,6 +1,6 @@
{ {
"name": "convertx-frontend", "name": "convertx-frontend",
"version": "0.11.1", "version": "0.12.1",
"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",
@@ -12,12 +12,12 @@
"lint:eslint": "eslint ." "lint:eslint": "eslint ."
}, },
"dependencies": { "dependencies": {
"@elysiajs/cookie": "^0.8.0",
"@elysiajs/html": "^1.2.0", "@elysiajs/html": "^1.2.0",
"@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.12" "elysia": "^1.2.12",
"sanitize-filename": "^1.6.3"
}, },
"module": "src/index.tsx", "module": "src/index.tsx",
"type": "module", "type": "module",
@@ -33,15 +33,14 @@
"@total-typescript/ts-reset": "^0.6.1", "@total-typescript/ts-reset": "^0.6.1",
"@types/bun": "^1.2.2", "@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/node": "^22.13.1", "@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.4",
"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.6", "knip": "^5.43.6",
"npm-run-all2": "^7.0.2", "npm-run-all2": "^7.0.2",
"postcss": "^8.5.1", "postcss": "^8.5.1",

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 = () => {
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

@@ -1,12 +1,12 @@
import { randomInt, randomUUID } from "node:crypto"; import { randomInt, randomUUID } from "node:crypto";
import { rmSync } from "node:fs"; import { rmSync } from "node:fs";
import { mkdir, unlink } from "node:fs/promises"; import { mkdir, unlink } from "node:fs/promises";
import cookie from "@elysiajs/cookie";
import { html, Html } from "@elysiajs/html"; import { html, Html } from "@elysiajs/html";
import { jwt, type JWTPayloadSpec } from "@elysiajs/jwt"; 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 {
@@ -116,7 +116,6 @@ const app = new Elysia({
}, },
prefix: WEBROOT, prefix: WEBROOT,
}) })
.use(cookie())
.use(html()) .use(html())
.use( .use(
jwt({ jwt({
@@ -692,7 +691,7 @@ const app = new Elysia({
</article> </article>
<input <input
class={` class={`
btn-primary w-full btn-primary w-full opacity-100
disabled:cursor-not-allowed disabled:opacity-50 disabled:cursor-not-allowed disabled:opacity-50
`} `}
type="submit" type="submit"
@@ -886,6 +885,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);
} }
@@ -1411,7 +1414,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);