mirror of
				https://github.com/C4illin/ConvertX.git
				synced 2025-10-31 12:03:31 +00:00 
			
		
		
		
	Compare commits
	
		
			62 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | bba420386f | ||
|  | 74df47531c | ||
|  | 1adac8c31c | ||
|  | 0579f1852b | ||
|  | 52d4cc0d03 | ||
|  | 2c68016ca6 | ||
|  | 7914194856 | ||
|  | 2dac7f1362 | ||
|  | a17e5fd614 | ||
|  | 21994fb6a2 | ||
|  | a5eaaa422a | ||
|  | ff2ef74135 | ||
|  | 70705c1850 | ||
|  | fd9c151e01 | ||
|  | 4f0573963f | ||
|  | 6bb6bce8a4 | ||
|  | 448557bece | ||
|  | bdbd4a122c | ||
|  | cb9d0ec680 | ||
|  | fb60ef66f5 | ||
|  | c1ae43075f | ||
|  | 377f69ae8d | ||
|  | cb131cd0a0 | ||
|  | fcc83c5ea8 | ||
|  | 96d4717d13 | ||
|  | 4d73bf9760 | ||
|  | 725a94bc95 | ||
|  | 0a366b447a | ||
|  | 4a27a7bc03 | ||
|  | 3ca5803bda | ||
|  | 239041294c | ||
|  | 31fdd8f214 | ||
|  | c3319c09eb | ||
|  | d460e94d52 | ||
|  | 4b5c732380 | ||
|  | f42665ca40 | ||
|  | bed52cef17 | ||
|  | 9d1c93155c | ||
|  | 794cc7c474 | ||
|  | d7d584e497 | ||
|  | f5320df86e | ||
|  | 056fd4ba93 | ||
|  | 5b6e70eb3a | ||
|  | f437a8e7e2 | ||
|  | cdae798fcf | ||
|  | bcc827a81b | ||
|  | 84274b9c55 | ||
|  | 20c6f8249e | ||
|  | 8f0ea2a592 | ||
|  | a29e4a930a | ||
|  | 4549c96ae3 | ||
|  | bc64094c04 | ||
|  | fa58827ad5 | ||
|  | 8f27be0e3d | ||
|  | df43df1178 | ||
|  | c2beb4a227 | ||
|  | 9277c27a50 | ||
|  | 171ecd6884 | ||
|  | dca29f7e5a | ||
|  | 318acc20bd | ||
|  | f433493d57 | ||
|  | 19970fc132 | 
							
								
								
									
										15
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| # These are supported funding model platforms | ||||
|  | ||||
| github: [C4illin] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] | ||||
| patreon: # Replace with a single Patreon username | ||||
| open_collective: # Replace with a single Open Collective username | ||||
| ko_fi: # Replace with a single Ko-fi username | ||||
| tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | ||||
| community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | ||||
| liberapay: # Replace with a single Liberapay username | ||||
| issuehunt: # Replace with a single IssueHunt username | ||||
| lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry | ||||
| polar: # Replace with a single Polar username | ||||
| buy_me_a_coffee: # Replace with a single Buy Me a Coffee username | ||||
| thanks_dev: # Replace with a single thanks.dev username | ||||
| custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] | ||||
							
								
								
									
										22
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,27 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## [0.13.0](https://github.com/C4illin/ConvertX/compare/v0.12.1...v0.13.0) (2025-05-14) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * add HIDE_HISTORY option to control visibility of history page ([9d1c931](https://github.com/C4illin/ConvertX/commit/9d1c93155cc33ed6c83f9e5122afff8f28d0e4bf)) | ||||
| * add potrace converter ([bdbd4a1](https://github.com/C4illin/ConvertX/commit/bdbd4a122c09559b089b985ea12c5f3e085107da)) | ||||
| * Add support for .HIF files ([70705c1](https://github.com/C4illin/ConvertX/commit/70705c1850d470296df85958c02a01fb5bc3a25f)) | ||||
| * add support for drag/drop of images ([ff2ef74](https://github.com/C4illin/ConvertX/commit/ff2ef7413542cf10ba7a6e246763bcecd6829ec1)) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * add timezone support ([4b5c732](https://github.com/C4illin/ConvertX/commit/4b5c732380bc844dccf340ea1eb4f8bfe3bb44a5)), closes [#258](https://github.com/C4illin/ConvertX/issues/258) | ||||
|  | ||||
| ## [0.12.1](https://github.com/C4illin/ConvertX/compare/v0.12.0...v0.12.1) (2025-03-20) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * rollback to bun 1.2.2 ([cdae798](https://github.com/C4illin/ConvertX/commit/cdae798fcf5879e4adea87386a38748b9a1e1ddc)) | ||||
|  | ||||
| ## [0.12.0](https://github.com/C4illin/ConvertX/compare/v0.11.1...v0.12.0) (2025-03-06) | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										19
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| FROM oven/bun:1.2.4-alpine AS base | ||||
| FROM oven/bun:1.2.2-alpine AS base | ||||
| LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX" | ||||
| WORKDIR /app | ||||
|  | ||||
| @@ -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 cargo install resvg | ||||
|  | ||||
| # copy node_modules from temp directory | ||||
| # then copy all (non-ignored) project files into the image | ||||
| # will switch to alpine again when it works | ||||
| FROM oven/bun:1.2.4-slim AS prerelease | ||||
| FROM base AS prerelease | ||||
| WORKDIR /app | ||||
| COPY --from=install /temp/dev/node_modules node_modules | ||||
| COPY . . | ||||
| @@ -33,11 +30,8 @@ RUN bun run build | ||||
|  | ||||
| # copy production dependencies and source code into final image | ||||
| FROM base AS release | ||||
| LABEL maintainer="Emrik Östling (C4illin)" | ||||
| LABEL description="ConvertX: self-hosted online file converter supporting 700+ file formats." | ||||
| 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/ | ||||
| RUN apk --no-cache add libheif-tools --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/ | ||||
|  | ||||
| # install additional dependencies | ||||
| RUN apk --no-cache add  \ | ||||
| @@ -59,9 +53,10 @@ RUN apk --no-cache add  \ | ||||
|   poppler-utils \ | ||||
|   gcompat \ | ||||
|   libva-utils \ | ||||
|   py3-numpy | ||||
|   py3-numpy \ | ||||
|   potrace | ||||
|  | ||||
| RUN apk --no-cache add calibre --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/ | ||||
| # RUN apk --no-cache add calibre@testing --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main/ | ||||
|  | ||||
| # this might be needed for some latex use cases, will add it if needed. | ||||
| #   texmf-dist-fontsextra \ | ||||
| @@ -69,8 +64,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=builder /root/.cargo/bin/resvg /usr/local/bin/resvg | ||||
| 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 . . | ||||
|  | ||||
| EXPOSE 3000/tcp | ||||
|   | ||||
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								README.md
									
									
									
									
									
								
							| @@ -29,12 +29,14 @@ A self-hosted online file converter. Supports over a thousand different formats. | ||||
| | [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           | | ||||
| | [Calibre](https://calibre-ebook.com/)                                        | E-books       | 26            | 19          | | ||||
| | [Pandoc](https://pandoc.org/)                                                | Documents     | 43            | 65          | | ||||
| | [GraphicsMagick](http://www.graphicsmagick.org/)                             | Images        | 167           | 130         | | ||||
| | [Inkscape](https://inkscape.org/)                                            | Vector images | 7             | 17          | | ||||
| | [Assimp](https://github.com/assimp/assimp)                                   | 3D Assets     | 77            | 23          | | ||||
| | [FFmpeg](https://ffmpeg.org/)                                                | Video         | ~472          | ~199        | | ||||
| | [Potrace](https://potrace.sourceforge.net/)                                  | Raster to vector | 4          | 11          | | ||||
|  | ||||
| <!-- | [Calibre](https://calibre-ebook.com/)                                        | E-books       | 26            | 19          | --> | ||||
|  | ||||
| <!-- many ffmpeg fileformats are duplicates --> | ||||
|  | ||||
| @@ -42,6 +44,9 @@ Any missing converter? Open an issue or pull request! | ||||
|  | ||||
| ## Deployment | ||||
|  | ||||
| > [!WARNING] | ||||
| > If you can't login, make sure you are accessing the service over localhost or https otherwise set HTTP_ALLOWED=true | ||||
|  | ||||
| ```yml | ||||
| # docker-compose.yml | ||||
| services: | ||||
| @@ -80,9 +85,7 @@ All are optional, JWT_SECRET is recommended to be set. | ||||
| | AUTO_DELETE_EVERY_N_HOURS | 24 | Checks every n hours for files older then n hours and deletes them, set to 0 to disable | | ||||
| | WEBROOT                   |  | The address to the root path setting this to "/convert" will serve the website on "example.com/convert/" | | ||||
| | FFMPEG_ARGS               |  | Arguments to pass to ffmpeg, e.g. `-preset veryfast` | | ||||
|  | ||||
| > [!WARNING] | ||||
| > If you can't login, make sure you are accessing the service over https or set HTTP_ALLOWED=true | ||||
| | HIDE_HISTORY             | false | Hide the history page | | ||||
|  | ||||
| ### Docker images | ||||
|  | ||||
| @@ -97,10 +100,15 @@ The image is available on [GitHub Container Registry](https://github.com/C4illin | ||||
| | `image: c4illin/convertx` | The latest release on docker hub | | ||||
| | `image: c4illin/convertx:main` | The latest commit on docker hub | | ||||
|  | ||||
|  | ||||
|  | ||||
| <!-- Dockerhub was introduced in 0.9.0 and older releases --> | ||||
|  | ||||
| ### Tutorial | ||||
|  | ||||
| > [!NOTE] | ||||
| > These are written by other people, and may be outdated, incorrect or wrong. | ||||
|  | ||||
| Tutorial in french: <https://belginux.com/installer-convertx-avec-docker/> | ||||
|  | ||||
| Tutorial in chinese: <https://xzllll.com/24092901/> | ||||
|   | ||||
							
								
								
									
										9
									
								
								SECURITY.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								SECURITY.md
									
									
									
									
									
										Normal 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. | ||||
| @@ -13,5 +13,6 @@ services: | ||||
|       - AUTO_DELETE_EVERY_N_HOURS=1 # checks every n hours for files older then n hours and deletes them, set to 0 to disable | ||||
|       # - FFMPEG_ARGS=-hwaccel vulkan # additional arguments to pass to ffmpeg | ||||
|       # - WEBROOT=/convertx # the root path of the web interface, leave empty to disable | ||||
|       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false | ||||
|     ports: | ||||
|       - 3000:3000 | ||||
|   | ||||
							
								
								
									
										61
									
								
								flake.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								flake.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| { | ||||
|   "nodes": { | ||||
|     "flake-utils": { | ||||
|       "inputs": { | ||||
|         "systems": "systems" | ||||
|       }, | ||||
|       "locked": { | ||||
|         "lastModified": 1731533236, | ||||
|         "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", | ||||
|         "owner": "numtide", | ||||
|         "repo": "flake-utils", | ||||
|         "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|         "owner": "numtide", | ||||
|         "repo": "flake-utils", | ||||
|         "type": "github" | ||||
|       } | ||||
|     }, | ||||
|     "nixpkgs": { | ||||
|       "locked": { | ||||
|         "lastModified": 1747179050, | ||||
|         "narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=", | ||||
|         "owner": "NixOS", | ||||
|         "repo": "nixpkgs", | ||||
|         "rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|         "owner": "NixOS", | ||||
|         "ref": "nixos-unstable", | ||||
|         "repo": "nixpkgs", | ||||
|         "type": "github" | ||||
|       } | ||||
|     }, | ||||
|     "root": { | ||||
|       "inputs": { | ||||
|         "flake-utils": "flake-utils", | ||||
|         "nixpkgs": "nixpkgs" | ||||
|       } | ||||
|     }, | ||||
|     "systems": { | ||||
|       "locked": { | ||||
|         "lastModified": 1681028828, | ||||
|         "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", | ||||
|         "owner": "nix-systems", | ||||
|         "repo": "default", | ||||
|         "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|         "owner": "nix-systems", | ||||
|         "repo": "default", | ||||
|         "type": "github" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "root": "root", | ||||
|   "version": 7 | ||||
| } | ||||
							
								
								
									
										60
									
								
								flake.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								flake.nix
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| { | ||||
|   description = "ConvertX"; | ||||
|  | ||||
|   inputs = { | ||||
|     nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; | ||||
|     flake-utils.url = "github:numtide/flake-utils"; | ||||
|   }; | ||||
|  | ||||
|   outputs = { self, nixpkgs, flake-utils, ... }: | ||||
|     flake-utils.lib.eachDefaultSystem (system: | ||||
|       let pkgs = import nixpkgs { inherit system; }; | ||||
|       appSrc = ./.; | ||||
|  | ||||
|       app = pkgs.dockerTools.buildLayeredImage { | ||||
|         name = "convertx"; | ||||
|         tag = "latest"; | ||||
|  | ||||
|         contents = [ | ||||
|           pkgs.bun | ||||
|           pkgs.resvg | ||||
|           pkgs.ffmpeg | ||||
|           pkgs.graphicsmagick | ||||
|           pkgs.ghostscript | ||||
|           pkgs.vips | ||||
|           pkgs.pandoc | ||||
|           pkgs.texlive.combined.scheme-full | ||||
|           pkgs.calibre | ||||
|           pkgs.inkscape | ||||
|           pkgs.poppler_utils | ||||
|           pkgs.assimp | ||||
|           pkgs.jxrlib | ||||
|           pkgs.libheif | ||||
|           pkgs.libjxl | ||||
|           pkgs.python3Packages.numpy | ||||
|         ]; | ||||
|  | ||||
|         config = { | ||||
|           Env = [ | ||||
|             "NODE_ENV=production" | ||||
|             "PATH=/bin:/usr/bin" | ||||
|           ]; | ||||
|           WorkingDir = "/app"; | ||||
|           Cmd = [ "bun" "run" "./src/index.tsx" ]; | ||||
|           ExposedPorts = { | ||||
|             "3000/tcp" = {}; | ||||
|           }; | ||||
|         }; | ||||
|  | ||||
|         extraCommands = '' | ||||
|           export PATH=${pkgs.bun}/bin:$PATH | ||||
|           mkdir -p /app | ||||
|           cp -r ${./dist}/* /app/ | ||||
|         ''; | ||||
|       }; | ||||
|  | ||||
|       in { | ||||
|         packages.default = app; | ||||
|       } | ||||
|     ); | ||||
| } | ||||
							
								
								
									
										53
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,9 +1,10 @@ | ||||
| { | ||||
|   "name": "convertx-frontend", | ||||
|   "version": "0.12.0", | ||||
|   "version": "0.13.0", | ||||
|   "scripts": { | ||||
|     "dev": "bun run --watch src/index.tsx", | ||||
|     "hot": "bun run --hot src/index.tsx", | ||||
|     "start": "bun run src/index.tsx", | ||||
|     "format": "eslint --fix .", | ||||
|     "build": "bunx @tailwindcss/cli -i ./src/main.css -o ./public/generated.css", | ||||
|     "lint": "run-p 'lint:*'", | ||||
| @@ -12,12 +13,11 @@ | ||||
|     "lint:eslint": "eslint ." | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@elysiajs/cookie": "^0.8.0", | ||||
|     "@elysiajs/html": "^1.2.0", | ||||
|     "@elysiajs/jwt": "^1.2.0", | ||||
|     "@elysiajs/static": "^1.2.0", | ||||
|     "@kitajs/html": "^4.2.7", | ||||
|     "elysia": "^1.2.12", | ||||
|     "@elysiajs/html": "^1.3.0", | ||||
|     "@elysiajs/jwt": "^1.3.0", | ||||
|     "@elysiajs/static": "^1.3.0", | ||||
|     "@kitajs/html": "^4.2.9", | ||||
|     "elysia": "^1.3.1", | ||||
|     "sanitize-filename": "^1.6.3" | ||||
|   }, | ||||
|   "module": "src/index.tsx", | ||||
| @@ -26,31 +26,30 @@ | ||||
|     "start": "bun run src/index.tsx" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@eslint/js": "^9.19.0", | ||||
|     "@eslint/js": "^9.26.0", | ||||
|     "@ianvs/prettier-plugin-sort-imports": "^4.4.1", | ||||
|     "@kitajs/ts-html-plugin": "^4.1.1", | ||||
|     "@tailwindcss/cli": "^4.0.4", | ||||
|     "@tailwindcss/postcss": "^4.0.4", | ||||
|     "@tailwindcss/cli": "^4.1.6", | ||||
|     "@tailwindcss/postcss": "^4.1.6", | ||||
|     "@total-typescript/ts-reset": "^0.6.1", | ||||
|     "@types/bun": "^1.2.2", | ||||
|     "@types/bun": "^1.2.13", | ||||
|     "@types/eslint-plugin-tailwindcss": "^3.17.0", | ||||
|     "@types/eslint__js": "^9.0.0", | ||||
|     "@types/node": "^22.13.1", | ||||
|     "autoprefixer": "^10.4.20", | ||||
|     "cssnano": "^7.0.6", | ||||
|     "eslint": "^9.19.0", | ||||
|     "eslint-plugin-readable-tailwind": "^2.0.0-beta.1", | ||||
|     "@types/node": "^22.15.17", | ||||
|     "autoprefixer": "^10.4.21", | ||||
|     "cssnano": "^7.0.7", | ||||
|     "eslint": "^9.26.0", | ||||
|     "eslint-plugin-readable-tailwind": "^2.1.1", | ||||
|     "eslint-plugin-simple-import-sort": "^12.1.1", | ||||
|     "eslint-plugin-tailwindcss": "4.0.0-alpha.0", | ||||
|     "globals": "^16.0.0", | ||||
|     "knip": "^5.43.6", | ||||
|     "npm-run-all2": "^7.0.2", | ||||
|     "postcss": "^8.5.1", | ||||
|     "postcss-cli": "^11.0.0", | ||||
|     "prettier": "^3.4.2", | ||||
|     "tailwind-scrollbar": "^4.0.0", | ||||
|     "tailwindcss": "^4.0.4", | ||||
|     "typescript": "^5.7.3", | ||||
|     "typescript-eslint": "^8.23.0" | ||||
|     "globals": "^16.1.0", | ||||
|     "knip": "^5.55.1", | ||||
|     "npm-run-all2": "^8.0.1", | ||||
|     "postcss": "^8.5.3", | ||||
|     "postcss-cli": "^11.0.1", | ||||
|     "prettier": "^3.5.3", | ||||
|     "tailwind-scrollbar": "^4.0.2", | ||||
|     "tailwindcss": "^4.1.6", | ||||
|     "typescript": "^5.8.3", | ||||
|     "typescript-eslint": "^8.32.0" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										113
									
								
								public/script.js
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								public/script.js
									
									
									
									
									
								
							| @@ -7,7 +7,8 @@ let fileType; | ||||
| let pendingFiles = 0; | ||||
| let formatSelected = false; | ||||
|  | ||||
| dropZone.addEventListener("dragover", () => { | ||||
| dropZone.addEventListener("dragover", (e) => { | ||||
|   e.preventDefault(); | ||||
|   dropZone.classList.add("dragover"); | ||||
| }); | ||||
|  | ||||
| @@ -15,10 +16,59 @@ dropZone.addEventListener("dragleave", () => { | ||||
|   dropZone.classList.remove("dragover"); | ||||
| }); | ||||
|  | ||||
| dropZone.addEventListener("drop", () => { | ||||
|   dropZone.classList.remove("dragover"); | ||||
| dropZone.addEventListener("drop", (e) => { | ||||
|     e.preventDefault(); | ||||
|     dropZone.classList.remove("dragover"); | ||||
|    | ||||
|     const files = e.dataTransfer.files; | ||||
|    | ||||
|     if (files.length === 0) { | ||||
|       console.warn("No files dropped — likely a URL or unsupported source."); | ||||
|       return; | ||||
|     } | ||||
|    | ||||
|     for (const file of files) { | ||||
|       console.log("Handling dropped file:", file.name); | ||||
|       handleFile(file); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| // Extracted handleFile function for reusability in drag-and-drop and file input | ||||
| function handleFile(file) { | ||||
|   const fileList = document.querySelector("#file-list"); | ||||
|  | ||||
|   const row = document.createElement("tr"); | ||||
|   row.innerHTML = ` | ||||
|     <td>${file.name}</td> | ||||
|     <td><progress max="100"></progress></td> | ||||
|     <td>${(file.size / 1024).toFixed(2)} kB</td> | ||||
|     <td><a onclick="deleteRow(this)">Remove</a></td> | ||||
|   `; | ||||
|  | ||||
|   if (!fileType) { | ||||
|     fileType = file.name.split(".").pop(); | ||||
|     fileInput.setAttribute("accept", `.${fileType}`); | ||||
|     setTitle(); | ||||
|  | ||||
|     fetch(`${webroot}/conversions`, { | ||||
|       method: "POST", | ||||
|       body: JSON.stringify({ fileType }), | ||||
|       headers: { "Content-Type": "application/json" }, | ||||
|     }) | ||||
|       .then((res) => res.text()) | ||||
|       .then((html) => { | ||||
|         selectContainer.innerHTML = html; | ||||
|         updateSearchBar(); | ||||
|       }) | ||||
|       .catch(console.error); | ||||
|   } | ||||
|  | ||||
|   fileList.appendChild(row); | ||||
|   file.htmlRow = row; | ||||
|   fileNames.push(file.name); | ||||
|   uploadFile(file); | ||||
| } | ||||
|  | ||||
| const selectContainer = document.querySelector("form .select_container"); | ||||
|  | ||||
| const updateSearchBar = () => { | ||||
| @@ -106,62 +156,9 @@ const updateSearchBar = () => { | ||||
|  | ||||
| // Add a 'change' event listener to the file input element | ||||
| fileInput.addEventListener("change", (e) => { | ||||
|   // Get the selected files from the event target | ||||
|   const files = e.target.files; | ||||
|  | ||||
|   // Select the file-list table | ||||
|   const fileList = document.querySelector("#file-list"); | ||||
|  | ||||
|   // Loop through the selected files | ||||
|   for (const file of files) { | ||||
|     // Create a new table row for each file | ||||
|     const row = document.createElement("tr"); | ||||
|     row.innerHTML = ` | ||||
|       <td>${file.name}</td> | ||||
|       <td><progress max="100"></progress></td> | ||||
|       <td>${(file.size / 1024).toFixed(2)} kB</td> | ||||
|       <td><a onclick="deleteRow(this)">Remove</a></td> | ||||
|     `; | ||||
|  | ||||
|     if (!fileType) { | ||||
|       fileType = file.name.split(".").pop(); | ||||
|       fileInput.setAttribute("accept", `.${fileType}`); | ||||
|       setTitle(); | ||||
|  | ||||
|       // choose the option that matches the file type | ||||
|       // for (const option of convertFromSelect.children) { | ||||
|       //   console.log(option.value); | ||||
|       //   if (option.value === fileType) { | ||||
|       //     option.selected = true; | ||||
|       //   } | ||||
|       // } | ||||
|  | ||||
|       fetch(`${webroot}/conversions`, { | ||||
|         method: "POST", | ||||
|         body: JSON.stringify({ fileType: fileType }), | ||||
|         headers: { | ||||
|           "Content-Type": "application/json", | ||||
|         }, | ||||
|       }) | ||||
|         .then((res) => res.text()) | ||||
|         .then((html) => { | ||||
|           selectContainer.innerHTML = html; | ||||
|           updateSearchBar(); | ||||
|         }) | ||||
|         .catch((err) => console.log(err)); | ||||
|     } | ||||
|  | ||||
|     // Append the row to the file-list table | ||||
|     fileList.appendChild(row); | ||||
|  | ||||
|     //Imbed row into the file to reference later | ||||
|     file.htmlRow = row; | ||||
|  | ||||
|  | ||||
|     // Append the file to the hidden input | ||||
|     fileNames.push(file.name); | ||||
|  | ||||
|     uploadFile(file); | ||||
|     handleFile(file); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| @@ -214,7 +211,7 @@ const uploadFile = (file) => { | ||||
|  | ||||
|   xhr.open("POST", `${webroot}/upload`, true); | ||||
|  | ||||
|   xhr.onload = (e) => { | ||||
|   xhr.onload = () => { | ||||
|     let data = JSON.parse(xhr.responseText); | ||||
|  | ||||
|     pendingFiles -= 1; | ||||
|   | ||||
| @@ -4,28 +4,32 @@ export const Header = ({ | ||||
|   loggedIn, | ||||
|   accountRegistration, | ||||
|   allowUnauthenticated, | ||||
|   hideHistory, | ||||
|   webroot = "", | ||||
| }: { | ||||
|   loggedIn?: boolean; | ||||
|   accountRegistration?: boolean; | ||||
|   allowUnauthenticated?: boolean; | ||||
|   hideHistory?: boolean; | ||||
|   webroot?: string; | ||||
| }) => { | ||||
|   let rightNav: JSX.Element; | ||||
|   if (loggedIn) { | ||||
|     rightNav = ( | ||||
|       <ul class="flex gap-4"> | ||||
|         <li> | ||||
|           <a | ||||
|             class={` | ||||
|               text-accent-600 transition-all | ||||
|               hover:text-accent-500 hover:underline | ||||
|             `} | ||||
|             href={`${webroot}/history`} | ||||
|           > | ||||
|             History | ||||
|           </a> | ||||
|         </li> | ||||
|         {!hideHistory && ( | ||||
|           <li> | ||||
|             <a | ||||
|               class={` | ||||
|                 text-accent-600 transition-all | ||||
|                 hover:text-accent-500 hover:underline | ||||
|               `} | ||||
|               href={`${webroot}/history`} | ||||
|             > | ||||
|               History | ||||
|             </a> | ||||
|           </li> | ||||
|         )} | ||||
|         {!allowUnauthenticated ? ( | ||||
|           <li> | ||||
|             <a | ||||
|   | ||||
| @@ -11,6 +11,7 @@ export const properties = { | ||||
|       "heics", | ||||
|       "heif", | ||||
|       "heifs", | ||||
|       "hif", | ||||
|       "mkv", | ||||
|       "mp4", | ||||
|     ], | ||||
|   | ||||
| @@ -8,8 +8,9 @@ import { convert as convertPandoc, properties as propertiesPandoc } from "./pand | ||||
| import { convert as convertresvg, properties as propertiesresvg } from "./resvg"; | ||||
| import { convert as convertImage, properties as propertiesImage } from "./vips"; | ||||
| 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"; | ||||
| import { convert as convertpotrace, properties as propertiespotrace } from "./potrace"; | ||||
|  | ||||
|  | ||||
| // This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular | ||||
| @@ -62,10 +63,10 @@ const properties: Record< | ||||
|     properties: propertiesxelatex, | ||||
|     converter: convertxelatex, | ||||
|   }, | ||||
|   calibre: { | ||||
|     properties: propertiesCalibre, | ||||
|     converter: convertCalibre, | ||||
|   }, | ||||
|   // calibre: { | ||||
|   //   properties: propertiesCalibre, | ||||
|   //   converter: convertCalibre, | ||||
|   // }, | ||||
|   pandoc: { | ||||
|     properties: propertiesPandoc, | ||||
|     converter: convertPandoc, | ||||
| @@ -86,6 +87,10 @@ const properties: Record< | ||||
|     properties: propertiesFFmpeg, | ||||
|     converter: convertFFmpeg, | ||||
|   }, | ||||
|   potrace: { | ||||
|     properties: propertiespotrace, | ||||
|     converter: convertpotrace, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export async function mainConverter( | ||||
|   | ||||
							
								
								
									
										37
									
								
								src/converters/potrace.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/converters/potrace.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     images: ["pnm", "pbm", "pgm", "bmp"], | ||||
|   }, | ||||
|   to: { | ||||
|     images: ["svg", "pdf", "pdfpage", "eps", "postscript", "ps", "dxf", "geojson", "pgm", "gimppath", "xfig"], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| 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("potrace", [filePath, "-o", targetPath, "-b", convertTo], (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|         } | ||||
|    | ||||
|         if (stdout) { | ||||
|           console.log(`stdout: ${stdout}`); | ||||
|         } | ||||
|    | ||||
|         if (stderr) { | ||||
|           console.error(`stderr: ${stderr}`); | ||||
|         } | ||||
|    | ||||
|         resolve("Done"); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| @@ -124,6 +124,16 @@ if (process.env.NODE_ENV === "production") { | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("potrace -v", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("potrace is not installed"); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("bun -v", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Bun is not installed. wait what"); | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import { randomInt, randomUUID } from "node:crypto"; | ||||
| import { rmSync } from "node:fs"; | ||||
| import { mkdir, unlink } from "node:fs/promises"; | ||||
| import cookie from "@elysiajs/cookie"; | ||||
| import { html, Html } from "@elysiajs/html"; | ||||
| import { jwt, type JWTPayloadSpec } from "@elysiajs/jwt"; | ||||
| import { staticPlugin } from "@elysiajs/static"; | ||||
| @@ -37,6 +36,8 @@ const ALLOW_UNAUTHENTICATED = | ||||
| const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS | ||||
|   ? Number(process.env.AUTO_DELETE_EVERY_N_HOURS) | ||||
|   : 24; | ||||
| const HIDE_HISTORY = | ||||
|   process.env.HIDE_HISTORY?.toLowerCase() === "true" || false; | ||||
|  | ||||
| const WEBROOT = process.env.WEBROOT ?? ""; | ||||
|  | ||||
| @@ -117,7 +118,6 @@ const app = new Elysia({ | ||||
|   }, | ||||
|   prefix: WEBROOT, | ||||
| }) | ||||
|   .use(cookie()) | ||||
|   .use(html()) | ||||
|   .use( | ||||
|     jwt({ | ||||
| @@ -353,6 +353,7 @@ const app = new Elysia({ | ||||
|             webroot={WEBROOT} | ||||
|             accountRegistration={ACCOUNT_REGISTRATION} | ||||
|             allowUnauthenticated={ALLOW_UNAUTHENTICATED} | ||||
|             hideHistory={HIDE_HISTORY} | ||||
|           /> | ||||
|           <main | ||||
|             class={` | ||||
| @@ -580,7 +581,7 @@ const app = new Elysia({ | ||||
|           > | ||||
|             <article class="article"> | ||||
|               <h1 class="mb-4 text-xl">Convert</h1> | ||||
|               <div class="mb-4 max-h-[50vh] overflow-y-auto scrollbar-thin"> | ||||
|               <div class="scrollbar-thin mb-4 max-h-[50vh] overflow-y-auto"> | ||||
|                 <table | ||||
|                   id="file-list" | ||||
|                   class={` | ||||
| @@ -595,8 +596,8 @@ const app = new Elysia({ | ||||
|                 class={` | ||||
|                   relative flex h-48 w-full items-center justify-center rounded border border-dashed | ||||
|                   border-neutral-700 transition-all | ||||
|                   [&.dragover]:border-4 [&.dragover]:border-neutral-500 | ||||
|                   hover:border-neutral-600 | ||||
|                   [&.dragover]:border-4 [&.dragover]:border-neutral-500 | ||||
|                 `} | ||||
|               > | ||||
|                 <span> | ||||
| @@ -693,7 +694,7 @@ const app = new Elysia({ | ||||
|               </article> | ||||
|               <input | ||||
|                 class={` | ||||
|                   btn-primary w-full | ||||
|                   btn-primary w-full opacity-100 | ||||
|                   disabled:cursor-not-allowed disabled:opacity-50 | ||||
|                 `} | ||||
|                 type="submit" | ||||
| @@ -955,6 +956,10 @@ const app = new Elysia({ | ||||
|     }, | ||||
|   ) | ||||
|   .get("/history", async ({ jwt, redirect, cookie: { auth } }) => { | ||||
|     if (HIDE_HISTORY) { | ||||
|       return redirect(`${WEBROOT}/`, 302); | ||||
|     } | ||||
|  | ||||
|     if (!auth?.value) { | ||||
|       return redirect(`${WEBROOT}/login`, 302); | ||||
|     } | ||||
| @@ -988,6 +993,7 @@ const app = new Elysia({ | ||||
|           <Header | ||||
|             webroot={WEBROOT} | ||||
|             allowUnauthenticated={ALLOW_UNAUTHENTICATED} | ||||
|             hideHistory={HIDE_HISTORY} | ||||
|             loggedIn | ||||
|           /> | ||||
|           <main | ||||
| @@ -1054,8 +1060,7 @@ const app = new Elysia({ | ||||
|                   {userJobs.map((job) => ( | ||||
|                     <tr> | ||||
|                       <td safe> | ||||
|                         {job.date_created.split("T")[1]?.split(".")[0] ?? | ||||
|                           job.date_created} | ||||
|                         {new Date(job.date_created).toLocaleTimeString()} | ||||
|                       </td> | ||||
|                       <td>{job.num_files}</td> | ||||
|                       <td class="max-sm:hidden">{job.finished_files}</td> | ||||
| @@ -1153,12 +1158,12 @@ const app = new Elysia({ | ||||
|                   max={job.num_files} | ||||
|                   value={files.length} | ||||
|                   class={` | ||||
|                     mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full | ||||
|                     border-0 bg-neutral-700 bg-none text-accent-500 accent-accent-500 | ||||
|                     [&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full | ||||
|                     [&::-webkit-progress-value]:[background:none] | ||||
|                     text-accent-500 accent-accent-500 mb-4 inline-block h-2 w-full appearance-none | ||||
|                     overflow-hidden rounded-full border-0 bg-neutral-700 bg-none | ||||
|                     [&[value]::-webkit-progress-value]:bg-accent-500 | ||||
|                     [&[value]::-webkit-progress-value]:transition-[inline-size] | ||||
|                     [&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full | ||||
|                     [&::-webkit-progress-value]:[background:none] | ||||
|                   `} | ||||
|                 /> | ||||
|                 <table | ||||
| @@ -1305,12 +1310,12 @@ const app = new Elysia({ | ||||
|             max={job.num_files} | ||||
|             value={files.length} | ||||
|             class={` | ||||
|               mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full border-0 | ||||
|               bg-neutral-700 bg-none text-accent-500 accent-accent-500 | ||||
|               [&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full | ||||
|               [&::-webkit-progress-value]:[background:none] | ||||
|               text-accent-500 accent-accent-500 mb-4 inline-block h-2 w-full appearance-none | ||||
|               overflow-hidden rounded-full border-0 bg-neutral-700 bg-none | ||||
|               [&[value]::-webkit-progress-value]:bg-accent-500 | ||||
|               [&[value]::-webkit-progress-value]:transition-[inline-size] | ||||
|               [&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full | ||||
|               [&::-webkit-progress-value]:[background:none] | ||||
|             `} | ||||
|           /> | ||||
|           <table | ||||
|   | ||||
		Reference in New Issue
	
	Block a user