Compare commits
	
		
			419 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 | ||
|  | 24394ca3c5 | ||
|  | 10ff0b464a | ||
|  | 9263d17609 | ||
|  | c1b75a13fd | ||
|  | a8ed60d48f | ||
|  | dc82a438d4 | ||
|  | cc54bdcbe7 | ||
|  | ae4bbc8baa | ||
|  | ad98499da0 | ||
|  | db60f355b2 | ||
|  | eb91d8b298 | ||
|  | b8312be4b7 | ||
|  | 326a8e3404 | ||
|  | f017e13ac1 | ||
|  | 67a5fe353e | ||
|  | 51d49d7ff3 | ||
|  | d42b820b36 | ||
|  | 07d32776d3 | ||
|  | ef027e81b5 | ||
|  | a75e4b495d | ||
|  | fba5e212e8 | ||
|  | 62f44fb052 | ||
|  | 6b9254047c | ||
|  | decfea5dc9 | ||
|  | eacded6848 | ||
|  | 279ca72c64 | ||
|  | b8fc9383ca | ||
|  | bec58ac59f | ||
|  | 83d7126820 | ||
|  | f0e9c6d794 | ||
|  | 0e61051fc6 | ||
|  | 480ba77ebe | ||
|  | 16f27c13bb | ||
|  | afe5c50d66 | ||
|  | 72ea859ebb | ||
|  | 8edf3834c4 | ||
|  | e595014fcd | ||
|  | 8bebf7e569 | ||
|  | c825ec06e2 | ||
|  | 8c75f273fb | ||
|  | 0ba776c129 | ||
|  | 2bbbd03554 | ||
|  | 0a5d0487b1 | ||
|  | 583cd2dd3b | ||
|  | e1f7fc1ecb | ||
|  | 961a55cbe5 | ||
|  | cdf9bad903 | ||
|  | 6769fa2f83 | ||
|  | 3b7ea88b73 | ||
|  | 59310c095d | ||
|  | c47f0c12fe | ||
|  | ca71a40485 | ||
|  | d4e8f376c1 | ||
|  | 14c6ea1e6b | ||
|  | d6e4d8fbd6 | ||
|  | 460bda62d5 | ||
|  | d2702ab673 | ||
|  | e2581f42f5 | ||
|  | f0fcfc159f | ||
|  | 538c5b60c9 | ||
|  | 2fabb7bbb2 | ||
|  | e7c34a9c94 | ||
|  | 618f9fce5a | ||
|  | 95dbc9f678 | ||
|  | aa87bc5c51 | ||
|  | 815de531ed | ||
|  | cf2b026dc4 | ||
|  | 9ce46aefba | ||
|  | 98b2db7818 | ||
|  | 0229851bf9 | ||
|  | 9e15114fe8 | ||
|  | 7f66a76bb0 | ||
|  | e9cc8392bb | ||
|  | d0b89ce74f | ||
|  | f537c81db7 | ||
|  | 03d3edfff6 | ||
|  | 447b4c5e5c | ||
|  | cb143209ae | ||
|  | 9c24fe73b5 | ||
|  | 19ae85424b | ||
|  | 22fad99552 | ||
|  | 8144bbef74 | ||
|  | aad6da0ae8 | ||
|  | c5f8162a22 | ||
|  | f0f30224b5 | ||
|  | d0d888e356 | ||
|  | 2c64122224 | ||
|  | 3b2eee96a9 | ||
|  | 465aacbf9b | ||
|  | d1a2a66170 | ||
|  | 4c05fd72bb | ||
|  | f04fe760e3 | ||
|  | 834d19bcc6 | ||
|  | 6808c4642c | ||
|  | d0ce307f94 | ||
|  | f3740e9ded | ||
|  | b485bc9445 | ||
|  | 2d14c1bb26 | ||
|  | 1a442d6e69 | ||
|  | 2386543e5c | ||
|  | 58e220e82d | ||
|  | 24bea6e4d2 | ||
|  | 43497ad8d1 | ||
|  | f22b61fe4c | ||
|  | 5b08f4cd19 | ||
|  | 1589f8d24e | ||
|  | 7d1db72cf5 | ||
|  | 53a8f66414 | ||
|  | 36cb6cc589 | ||
|  | f3a4aece46 | ||
|  | 580a6a869a | ||
|  | 008eaac493 | ||
|  | b450623bb4 | ||
|  | 8ac2ecb673 | ||
|  | 0a10a56ae3 | ||
|  | 9378ba9208 | ||
|  | 0c586e324b | ||
|  | 91c4a64284 | ||
|  | c599e98d9d | ||
|  | d2cd6706c9 | ||
|  | e8ed10dde8 | ||
|  | 5fe0b79802 | ||
|  | 34a6722a68 | ||
|  | 5b0d769c63 | ||
|  | 718401a28b | ||
|  | 3112cd57f6 | ||
|  | 410fc777a7 | ||
|  | 8eed99e732 | ||
|  | 663b1d4171 | ||
|  | c3067ca12d | ||
|  | 4561ca3760 | ||
|  | 698cce58ce | ||
|  | 339b79f786 | ||
|  | 4f98f778f0 | ||
|  | 8479b33a47 | ||
|  | 78844d7bd5 | ||
|  | 64e4a271e1 | ||
|  | 5fb8c3575b | ||
|  | a6b8bcecae | ||
|  | bc9c820820 | ||
|  | ee9207a7f4 | ||
|  | a34e215202 | ||
|  | b4e53dbb8e | ||
|  | b5e8d82bfa | ||
|  | 5d9000bb33 | ||
|  | ccb065ef0f | ||
|  | 883fad806b | ||
|  | feacd1b816 | ||
|  | 094e7a0d1c | ||
|  | 72636c5059 | ||
|  | 291cfc80c6 | ||
|  | ae1dfafc9d | ||
|  | 6caa583c35 | ||
|  | 2057167576 | ||
|  | 1c9e67fc32 | ||
|  | d3af9688c6 | ||
|  | 7d0cbb9844 | ||
|  | 88173891ba | ||
|  | 2b4b8f9551 | ||
|  | 63a4328d4a | ||
|  | 413f5dc7b4 | ||
|  | ebccdf9169 | ||
|  | 47139a550b | ||
|  | fa5446c446 | ||
|  | 8772e582b0 | ||
|  | 45922ed3a3 | ||
|  | 4c747e8908 | ||
|  | e573997aa9 | ||
|  | c57b69991c | ||
|  | eee983a56a | ||
|  | 22f823c535 | ||
|  | ed59cd7aa4 | ||
|  | b28977ffe2 | ||
|  | a47bb682a5 | ||
|  | a17eca0a09 | ||
|  | ea9250543e | ||
|  | 317c932c2a | ||
|  | 5b1703db68 | ||
|  | 60ba7c93fb | ||
|  | 22227130dd | ||
|  | 5daf66f5d0 | ||
|  | aee1962607 | ||
|  | 0d42762b36 | ||
|  | b97b12b449 | ||
|  | bdf651df82 | ||
|  | 267ef14789 | ||
|  | 905adc5e1c | ||
|  | 52ed7274e9 | ||
|  | a29238c265 | ||
|  | 48c6fb79fc | ||
|  | 8358396656 | ||
|  | b30e5800c3 | ||
|  | 21a1b50ed8 | ||
|  | e6a94fb21d | ||
|  | bef1710e33 | ||
|  | 16b322d4e6 | ||
|  | 9bf64e42d5 | ||
|  | 5988fe8212 | ||
|  | 5df9c0b751 | ||
|  | 136a8b2d74 | ||
|  | ccfb574d5d | ||
|  | ad6eedea69 | ||
|  | c3082db8f7 | ||
|  | a1f8cbae66 | ||
|  | bb34bdee87 | ||
|  | 11fcbc3f96 | ||
|  | f7344e4c65 | ||
|  | 781310f3dc | ||
|  | 3f063644f2 | ||
|  | 081634b610 | ||
|  | cf3da08c73 | ||
|  | 5f7234d6c1 | ||
|  | 6597c1d7ca | ||
|  | ecb2c75008 | ||
|  | d5eeef9f68 | ||
|  | 7456174022 | ||
|  | bc4ad49285 | ||
|  | f0d0e43929 | ||
|  | 8ca4f1587d | ||
|  | 1535377bfe | ||
|  | 83bf78fd57 | ||
|  | 4d9c4d64aa | ||
|  | 53fff594fc | ||
|  | fe4aeaff03 | ||
|  | 2078cb0ee0 | ||
|  | 86a61d35d7 | ||
|  | 96fa7e2f55 | ||
|  | 7d2af46b0b | ||
|  | 57e2999866 | ||
|  | 6fb8ca4d82 | ||
|  | c295e546bd | ||
|  | f7abb9389c | ||
|  | d7de154eda | ||
|  | 20bd111765 | ||
|  | eadd0da291 | ||
|  | 52294465fb | ||
|  | 049e9163ce | ||
|  | d466d2dbbc | ||
|  | 3f79ccaa2a | ||
|  | 1e9bde18c7 | ||
|  | 9af23346bf | ||
|  | d310341fca | ||
|  | d88a755c13 | ||
|  | 7c6085c685 | ||
|  | 7ed1ad21f2 | ||
|  | 8a2237fbd9 | ||
|  | 0e363f0731 | ||
|  | 4074647b67 | ||
|  | c84968be50 | ||
|  | 0e53a99d43 | ||
|  | bdd0cf556f | ||
|  | 2483274388 | ||
|  | 4c5129910a | ||
|  | fe13a1b736 | ||
|  | f1ac71b397 | ||
|  | 1b1067a03f | ||
|  | 8674557e42 | ||
|  | 87052ce105 | ||
|  | 98ee26f6e2 | ||
|  | 96e2c88465 | ||
|  | d55ba218ff | ||
|  | ae2455e73e | ||
|  | b9fe32053c | ||
|  | 5cf3d74e03 | ||
|  | 2b92778f37 | ||
|  | 27d4da8941 | ||
|  | 2384e22c22 | ||
|  | 6690caeb1e | ||
|  | c714ade3e2 | ||
|  | e9e95c61e9 | ||
|  | b1e0e68d9c | ||
|  | 5ce3706550 | ||
|  | 57e47e95c0 | ||
|  | 6d6bc6cfdd | ||
|  | b44eb22e77 | ||
|  | 6edfbaa27d | ||
|  | d669baeff4 | ||
|  | ec1a7bc015 | ||
|  | 0805241a19 | ||
|  | 83f041daa2 | ||
|  | 55331a4496 | ||
|  | b53f07e7a7 | ||
|  | 0eb89ae712 | ||
|  | 7dd153b02c | ||
|  | 6ccafeb3b0 | ||
|  | b703903b22 | ||
|  | 9e66eab0a2 | ||
|  | b272bf9504 | ||
|  | 56632f3500 | ||
|  | 2d9d8f8b4f | ||
|  | 65d4e0fbbe | ||
|  | 8182d12ea0 | ||
|  | 1c241d4cad | ||
|  | 874ff6ee00 | ||
|  | e9f1219ad9 | ||
|  | 4811452aec | ||
|  | 382ebad35a | ||
|  | 85945256e7 | ||
|  | c504692569 | ||
|  | 64a16036be | ||
|  | b9f038386f | ||
|  | 945775e52b | ||
|  | e7f3466736 | ||
|  | ee80eeb18d | ||
|  | 34c7e0bd25 | ||
|  | 492dbd5617 | ||
|  | 0935bf66ce | ||
|  | 7389e0a059 | ||
|  | c512b45f91 | ||
|  | 3ae2db5d9b | ||
|  | 0945b40a9c | ||
|  | 20b958e547 | ||
|  | e7e146c6c9 | ||
|  | 005ad2d66b | ||
|  | e5c3a8acc4 | ||
|  | 87ecbabd1f | ||
|  | 991c4e4ba8 | ||
|  | 87ccd8b44c | ||
|  | 83e6699ca6 | ||
|  | c91523c038 | ||
|  | 1f73f036b2 | ||
|  | 1223fabfca | ||
|  | 8a42a39e69 | ||
|  | 22023bad25 | ||
|  | db2f2d8f0a | ||
|  | d0fa9ac408 | ||
|  | 776a97289b | ||
|  | 95340dd0eb | ||
|  | 7dcd74cc5f | ||
|  | c5efac9423 | ||
|  | cceca9a924 | ||
|  | 4d4c13a8d8 | ||
|  | f8f90ebd69 | ||
|  | 648d5070e2 | ||
|  | 801cf28d1e | ||
|  | fae2ba9c54 | ||
|  | 10d20a8786 | ||
|  | c9bc1e237e | ||
|  | 48a76a46b3 | ||
|  | 4dcb796e1b | ||
|  | 5952103bcd | ||
|  | fd05ee5cd5 | ||
|  | 7e7d238c7a | ||
|  | 755a4170f2 | ||
|  | 263ba415f5 | ||
|  | 317260098a | ||
|  | 5304e94b4e | ||
|  | aab2b311cf | ||
|  | baa7ea40e6 | ||
|  | 481a11b610 | ||
|  | c09fe296b1 | ||
|  | f023aae753 | ||
|  | 5cd9544b55 | ||
|  | 97c23ba65c | ||
|  | 0ffda40ac8 | ||
|  | cb639907ee | ||
|  | 0166842b78 | 
| @@ -1,16 +1,19 @@ | ||||
| node_modules | ||||
| Dockerfile* | ||||
| docker-compose* | ||||
| .dockerignore | ||||
| .git | ||||
| .gitignore | ||||
| README.md | ||||
| LICENSE | ||||
| .vscode | ||||
| Makefile | ||||
| helm-charts | ||||
| .env | ||||
| .editorconfig | ||||
| .idea | ||||
| coverage* | ||||
| data | ||||
| .dockerignore | ||||
| .editorconfig | ||||
| .env | ||||
| .git | ||||
| .idea | ||||
| .vscode | ||||
| CHANGELOG.md | ||||
| coverage* | ||||
| data | ||||
| docker-compose* | ||||
| Dockerfile* | ||||
| eslint.config.js | ||||
| helm-charts | ||||
| LICENSE | ||||
| Makefile | ||||
| node_modules | ||||
| prettier.config.js | ||||
| README.md | ||||
| renovate.json | ||||
| @@ -1,55 +0,0 @@ | ||||
| /** @type {import("eslint").Linter.Config} */ | ||||
| const config = { | ||||
|   root: true, | ||||
|   parser: "@typescript-eslint/parser", | ||||
|   plugins: ["isaacscript", "import"], | ||||
|   extends: [ | ||||
|     "plugin:@typescript-eslint/recommended-type-checked", | ||||
|     "plugin:@typescript-eslint/stylistic-type-checked", | ||||
|     "plugin:prettier/recommended", | ||||
|   ], | ||||
|   parserOptions: { | ||||
|     ecmaVersion: "latest", | ||||
|     sourceType: "module", | ||||
|     tsconfigRootDir: __dirname, | ||||
|     project: [ | ||||
|       "./tsconfig.json", | ||||
|       "./cli/tsconfig.eslint.json", // separate eslint config for the CLI since we want to lint and typecheck differently due to template files | ||||
|       "./upgrade/tsconfig.json", | ||||
|       "./www/tsconfig.json", | ||||
|     ], | ||||
|   }, | ||||
|   overrides: [ | ||||
|     // Template files don't have reliable type information | ||||
|     { | ||||
|       files: ["./cli/template/**/*.{ts,tsx}"], | ||||
|       extends: ["plugin:@typescript-eslint/disable-type-checked"], | ||||
|     }, | ||||
|   ], | ||||
|   rules: { | ||||
|     // These off/not-configured-the-way-we-want lint rules we like & opt into | ||||
|     "@typescript-eslint/no-explicit-any": "error", | ||||
|     "@typescript-eslint/no-unused-vars": [ | ||||
|       "error", | ||||
|       { argsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_" }, | ||||
|     ], | ||||
|     "@typescript-eslint/consistent-type-imports": [ | ||||
|       "error", | ||||
|       { prefer: "type-imports", fixStyle: "inline-type-imports" }, | ||||
|     ], | ||||
|     "import/consistent-type-specifier-style": ["error", "prefer-inline"], | ||||
|  | ||||
|     // For educational purposes we format our comments/jsdoc nicely | ||||
|     "isaacscript/complete-sentences-jsdoc": "warn", | ||||
|     "isaacscript/format-jsdoc-comments": "warn", | ||||
|  | ||||
|     // These lint rules don't make sense for us but are enabled in the preset configs | ||||
|     "@typescript-eslint/no-confusing-void-expression": "off", | ||||
|     "@typescript-eslint/restrict-template-expressions": "off", | ||||
|  | ||||
|     // This rule doesn't seem to be working properly | ||||
|     "@typescript-eslint/prefer-nullish-coalescing": "off", | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| module.exports = config; | ||||
							
								
								
									
										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'] | ||||
							
								
								
									
										23
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,23 +0,0 @@ | ||||
| # To get started with Dependabot version updates, you'll need to specify which | ||||
| # package ecosystems to update and where the package manifests are located. | ||||
| # Please see the documentation for all configuration options: | ||||
| # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates | ||||
|  | ||||
| version: 2 | ||||
| updates: | ||||
| - package-ecosystem: npm | ||||
|   versioning-strategy: increase | ||||
|   directory: "/" | ||||
|   schedule: | ||||
|     interval: daily | ||||
|   commit-message: | ||||
|     prefix: "build" | ||||
|     include: "scope" | ||||
|   open-pull-requests-limit: 10 | ||||
| - package-ecosystem: github-actions | ||||
|   directory: "/" | ||||
|   schedule: | ||||
|     interval: weekly | ||||
|   commit-message: | ||||
|     prefix: "build" | ||||
|     include: "scope" | ||||
							
								
								
									
										28
									
								
								.github/workflows/bun-dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,28 +0,0 @@ | ||||
| name: 'Dependabot: Update bun.lockb' | ||||
|  | ||||
| on: | ||||
|   pull_request: | ||||
|     paths: | ||||
|       - "package.json" | ||||
|  | ||||
| permissions: | ||||
|   contents: write | ||||
|  | ||||
| jobs: | ||||
|   update-bun-lockb: | ||||
|     name: "Update bun.lockb" | ||||
|     if: github.actor == 'dependabot[bot]' | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: oven-sh/setup-bun@v1 | ||||
|       - uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           ref: ${{ github.event.pull_request.head.ref }} | ||||
|       - run: | | ||||
|           bun install | ||||
|           git add bun.lockb | ||||
|           git config --global user.name 'dependabot[bot]' | ||||
|           git config --global user.email 'dependabot[bot]@users.noreply.github.com' | ||||
|           git commit --amend --no-edit  | ||||
|           git push --force | ||||
							
								
								
									
										148
									
								
								.github/workflows/docker-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,68 +1,80 @@ | ||||
| name: Docker | ||||
|  | ||||
| # This workflow uses actions that are not certified by GitHub. | ||||
| # They are provided by a third-party and are governed by | ||||
| # separate terms of service, privacy policy, and support | ||||
| # documentation. | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [ "main" ] | ||||
|     # Publish semver tags as releases. | ||||
|     tags: [ 'v*.*.*' ] | ||||
|   pull_request: | ||||
|     branches: [ "main" ] | ||||
|   workflow_dispatch: | ||||
|  | ||||
| env: | ||||
|   # Use docker.io for Docker Hub if empty | ||||
|   REGISTRY: ghcr.io | ||||
|   # github.repository as <account>/<repo> | ||||
|   IMAGE_NAME: ${{ github.repository }} | ||||
|  | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
|  | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v4 | ||||
|  | ||||
|       # Workaround: https://github.com/docker/build-push-action/issues/461 | ||||
|       - name: Setup Docker buildx | ||||
|         uses: docker/setup-buildx-action@v3 | ||||
|  | ||||
|       # Login against a Docker registry except on PR | ||||
|       # https://github.com/docker/login-action | ||||
|       - name: Log into registry ${{ env.REGISTRY }} | ||||
|         if: github.event_name != 'pull_request' | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           registry: ${{ env.REGISTRY }} | ||||
|           username: ${{ github.actor }} | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
|  | ||||
|       # Extract metadata (tags, labels) for Docker | ||||
|       # https://github.com/docker/metadata-action | ||||
|       - name: Extract Docker metadata | ||||
|         id: meta | ||||
|         uses: docker/metadata-action@v5 | ||||
|         with: | ||||
|           images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||||
|  | ||||
|       # Build and push Docker image with Buildx (don't push on PR) | ||||
|       # https://github.com/docker/build-push-action | ||||
|       - name: Build and push Docker image | ||||
|         id: build-and-push | ||||
|         uses: docker/build-push-action@v5 | ||||
|         with: | ||||
|           context: . | ||||
|           push: ${{ github.event_name != 'pull_request' }} | ||||
|           tags: ${{ steps.meta.outputs.tags }} | ||||
|           labels: ${{ steps.meta.outputs.labels }} | ||||
|           cache-from: type=gha | ||||
|           cache-to: type=gha,mode=max | ||||
| name: Docker | ||||
|  | ||||
| # This workflow uses actions that are not certified by GitHub. | ||||
| # They are provided by a third-party and are governed by | ||||
| # separate terms of service, privacy policy, and support | ||||
| # documentation. | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [ "main" ] | ||||
|     # Publish semver tags as releases. | ||||
|     tags: [ 'v*.*.*' ] | ||||
|   pull_request: | ||||
|     branches: [ "main" ] | ||||
|   workflow_dispatch: | ||||
|  | ||||
| env: | ||||
|   # Use docker.io for Docker Hub if empty | ||||
|   REGISTRY: ghcr.io | ||||
|   # github.repository as <account>/<repo> | ||||
|   IMAGE_NAME: ${{ github.repository }} | ||||
|   DOCKERHUB_USERNAME: c4illin | ||||
|  | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
|  | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v4 | ||||
|  | ||||
|       # Workaround: https://github.com/docker/build-push-action/issues/461 | ||||
|       - name: Setup Docker buildx | ||||
|         uses: docker/setup-buildx-action@v3 | ||||
|  | ||||
|       # Login against a Docker registry except on PR | ||||
|       # https://github.com/docker/login-action | ||||
|       - name: Log into registry ${{ env.REGISTRY }} | ||||
|         if: github.event_name != 'pull_request' | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           registry: ${{ env.REGISTRY }} | ||||
|           username: ${{ github.actor }} | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
|        | ||||
|       - name: Login to Docker Hub | ||||
|         if: github.event_name != 'pull_request' | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           username: ${{ env.DOCKERHUB_USERNAME }} | ||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||
|  | ||||
|       # Extract metadata (tags, labels) for Docker | ||||
|       # https://github.com/docker/metadata-action | ||||
|       - name: Extract Docker metadata | ||||
|         id: meta | ||||
|         uses: docker/metadata-action@v5 | ||||
|         with: | ||||
|           images: | | ||||
|             ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||||
|             ${{ env.IMAGE_NAME }} | ||||
|  | ||||
|       # Build and push Docker image with Buildx (don't push on PR) | ||||
|       # https://github.com/docker/build-push-action | ||||
|       - name: Build and push Docker image | ||||
|         id: build-and-push | ||||
|         uses: docker/build-push-action@v6 | ||||
|         with: | ||||
|           context: . | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|           push: ${{ github.event_name != 'pull_request' }} | ||||
|           tags: ${{ steps.meta.outputs.tags }} | ||||
|           labels: ${{ steps.meta.outputs.labels }} | ||||
|           cache-from: type=gha | ||||
|           cache-to: type=gha,mode=max | ||||
|        | ||||
							
								
								
									
										27
									
								
								.github/workflows/dockerhub-description.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,27 @@ | ||||
| name: Update Docker Hub Description | ||||
|  | ||||
| env: | ||||
|   IMAGE_NAME: ${{ github.repository }} | ||||
|   DOCKERHUB_USERNAME: c4illin | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
|     paths: | ||||
|       - README.md | ||||
|       - .github/workflows/dockerhub-description.yml | ||||
| jobs: | ||||
|   dockerHubDescription: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|     - uses: actions/checkout@v4 | ||||
|  | ||||
|     - name: Docker Hub Description | ||||
|       uses: peter-evans/dockerhub-description@v4 | ||||
|       with: | ||||
|         username: ${{ env.DOCKERHUB_USERNAME }} | ||||
|         password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||
|         repository: ${{ env.IMAGE_NAME }} | ||||
|         short-description: ${{ github.event.repository.description }} | ||||
|         enable-url-completion: true | ||||
							
								
								
									
										4
									
								
								.github/workflows/release-please.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -18,8 +18,8 @@ jobs: | ||||
|           # this assumes that you have created a personal access token | ||||
|           # (PAT) and configured it as a GitHub action secret named | ||||
|           # `MY_RELEASE_PLEASE_TOKEN` (this secret name is not important). | ||||
|           # token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }} | ||||
|           token: ${{ secrets.GITHUB_TOKEN }} | ||||
|           token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }} | ||||
|           # token: ${{ secrets.GITHUB_TOKEN }} | ||||
|           # this is a built-in strategy in release-please, see "Action Inputs" | ||||
|           # for more options | ||||
|           release-type: node | ||||
|   | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -46,4 +46,6 @@ package-lock.json | ||||
| /output | ||||
| /db | ||||
| /data | ||||
| /Bruno | ||||
| /Bruno | ||||
| /tsconfig.tsbuildinfo | ||||
| /public/generated.css | ||||
|   | ||||
							
								
								
									
										4
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| { | ||||
|   "typescript.tsdk": "node_modules/typescript/lib", | ||||
|   "typescript.enablePromptUseWorkspaceTsdk": true | ||||
| } | ||||
							
								
								
									
										209
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						| @@ -1,5 +1,214 @@ | ||||
| # 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) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * added progress bar for file upload ([db60f35](https://github.com/C4illin/ConvertX/commit/db60f355b2973f43f8e5990e6fe4e351b959b659)) | ||||
| * made every upload file independent ([cc54bdc](https://github.com/C4illin/ConvertX/commit/cc54bdcbe764c41cc3273485d072fd3178ad2dca)) | ||||
| * replace exec with execFile ([9263d17](https://github.com/C4illin/ConvertX/commit/9263d17609dc4b2b367eb7fee67b3182e283b3a3)) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * add libheif ([6b92540](https://github.com/C4illin/ConvertX/commit/6b9254047c0598963aee1d99e20ba1650a0368bf)) | ||||
| * add libheif ([decfea5](https://github.com/C4illin/ConvertX/commit/decfea5dc9627b216bb276a9e1578c32cfa1deb6)), closes [#202](https://github.com/C4illin/ConvertX/issues/202) | ||||
| * added onerror log ([ae4bbc8](https://github.com/C4illin/ConvertX/commit/ae4bbc8baacbaf67763c62ea44140bb21cc17230)) | ||||
| * refactored uploadFile to only accept a single file instead of multiple ([dc82a43](https://github.com/C4illin/ConvertX/commit/dc82a438d4104b79ff423d502a6779a43928968a)) | ||||
| * update libheif to 1.19.5 ([fba5e21](https://github.com/C4illin/ConvertX/commit/fba5e212e8d0eaba8971e239e35aeb521f3cd813)), closes [#202](https://github.com/C4illin/ConvertX/issues/202) | ||||
|  | ||||
| ## [0.11.1](https://github.com/C4illin/ConvertX/compare/v0.11.0...v0.11.1) (2025-02-07) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * mobile view overflow ([bec58ac](https://github.com/C4illin/ConvertX/commit/bec58ac59f9600e35385b9e21d174f3ab1b42b1d)) | ||||
|  | ||||
| ## [0.11.0](https://github.com/C4illin/ConvertX/compare/v0.10.1...v0.11.0) (2025-02-05) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * add deps for vaapi ([2bbbd03](https://github.com/C4illin/ConvertX/commit/2bbbd03554d384a4488143f29e5fc863cfdf333b)), closes [#192](https://github.com/C4illin/ConvertX/issues/192) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * don't crash if file is not found ([16f27c1](https://github.com/C4illin/ConvertX/commit/16f27c13bbc1c0e5fa2316f3db11d0918524053b)) | ||||
| * install numpy for inkscape ([0e61051](https://github.com/C4illin/ConvertX/commit/0e61051fc6be188164c3865b4fb579c140859fdc)) | ||||
|  | ||||
| ## [0.10.1](https://github.com/C4illin/ConvertX/compare/v0.10.0...v0.10.1) (2025-01-21) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * ffmpeg works without ffmpeg_args ([3b7ea88](https://github.com/C4illin/ConvertX/commit/3b7ea88b7382f7c21b120bdc9bda5bb10547f55d)), closes [#212](https://github.com/C4illin/ConvertX/issues/212) | ||||
|  | ||||
| ## [0.10.0](https://github.com/C4illin/ConvertX/compare/v0.9.0...v0.10.0) (2025-01-18) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * add calibre ([03d3edf](https://github.com/C4illin/ConvertX/commit/03d3edfff65c252dd4b8922fc98257c089c1ff74)), closes [#191](https://github.com/C4illin/ConvertX/issues/191) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * add FFMPEG_ARGS env variable ([f537c81](https://github.com/C4illin/ConvertX/commit/f537c81db7815df8017f834e3162291197e1c40f)), closes [#190](https://github.com/C4illin/ConvertX/issues/190) | ||||
| * add qt6-qtbase-private-dev from community repo ([95dbc9f](https://github.com/C4illin/ConvertX/commit/95dbc9f678bec7e6e2c03587e1473fb8ff708ea3)) | ||||
| * skip account setup when ALLOW_UNAUTHENTICATED is true ([538c5b6](https://github.com/C4illin/ConvertX/commit/538c5b60c9e27a8184740305475245da79bae143)) | ||||
|  | ||||
| ## [0.9.0](https://github.com/C4illin/ConvertX/compare/v0.8.1...v0.9.0) (2024-11-21) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * add inkscape for vector images ([f3740e9](https://github.com/C4illin/ConvertX/commit/f3740e9ded100b8500f3613517960248bbd3c210)) | ||||
| * Allow to chose webroot ([36cb6cc](https://github.com/C4illin/ConvertX/commit/36cb6cc589d80d0a87fa8dbe605db71a9a2570f9)), closes [#180](https://github.com/C4illin/ConvertX/issues/180) | ||||
| * disable convert when uploading ([58e220e](https://github.com/C4illin/ConvertX/commit/58e220e82d7f9c163d6ea4dc31092c08a3e254f4)), closes [#177](https://github.com/C4illin/ConvertX/issues/177) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * treat unknown as m4a ([1a442d6](https://github.com/C4illin/ConvertX/commit/1a442d6e69606afef63b1e7df36aa83d111fa23d)), closes [#178](https://github.com/C4illin/ConvertX/issues/178) | ||||
| * wait for both upload and selection ([4c05fd7](https://github.com/C4illin/ConvertX/commit/4c05fd72bbbf91ee02327f6fcbf749b78272376b)), closes [#177](https://github.com/C4illin/ConvertX/issues/177) | ||||
|  | ||||
| ## [0.8.1](https://github.com/C4illin/ConvertX/compare/v0.8.0...v0.8.1) (2024-10-05) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * disable convert button when input is empty ([78844d7](https://github.com/C4illin/ConvertX/commit/78844d7bd55990789ed07c81e49043e688cbe656)), closes [#151](https://github.com/C4illin/ConvertX/issues/151) | ||||
| * resize to fit for ico ([b4e53db](https://github.com/C4illin/ConvertX/commit/b4e53dbb8e70b3a95b44e5b756759d16117a87e1)), closes [#157](https://github.com/C4illin/ConvertX/issues/157) | ||||
| * treat jfif as jpeg ([339b79f](https://github.com/C4illin/ConvertX/commit/339b79f786131deb93f0d5683e03178fdcab1ef5)), closes [#163](https://github.com/C4illin/ConvertX/issues/163) | ||||
|  | ||||
| ## [0.8.0](https://github.com/C4illin/ConvertX/compare/v0.7.0...v0.8.0) (2024-09-30) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * add light theme, fixes [#156](https://github.com/C4illin/ConvertX/issues/156) ([72636c5](https://github.com/C4illin/ConvertX/commit/72636c5059ebf09c8fece2e268293650b2f8ccf6)) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * add support for usd for assimp, [#144](https://github.com/C4illin/ConvertX/issues/144) ([2057167](https://github.com/C4illin/ConvertX/commit/20571675766209ad1251f07e687d29a6791afc8b)) | ||||
| * cleanup formats and add opus, fixes [#159](https://github.com/C4illin/ConvertX/issues/159) ([ae1dfaf](https://github.com/C4illin/ConvertX/commit/ae1dfafc9d9116a57b08c2f7fc326990e00824b0)) | ||||
| * support .awb and clean up, fixes [#153](https://github.com/C4illin/ConvertX/issues/153), [#92](https://github.com/C4illin/ConvertX/issues/92) ([1c9e67f](https://github.com/C4illin/ConvertX/commit/1c9e67fc3201e0e5dee91e8981adf34daaabf33a)) | ||||
|  | ||||
| ## [0.7.0](https://github.com/C4illin/ConvertX/compare/v0.6.0...v0.7.0) (2024-09-26) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * Add support for 3d assets through assimp converter ([63a4328](https://github.com/C4illin/ConvertX/commit/63a4328d4a1e01df3e0ec4a877bad8c8ffe71129)) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * wrong layout on search with few options ([8817389](https://github.com/C4illin/ConvertX/commit/88173891ba2d69da46eda46f3f598a9b54f26f96)) | ||||
|  | ||||
| ## [0.6.0](https://github.com/C4illin/ConvertX/compare/v0.5.0...v0.6.0) (2024-09-25) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * ui remake with tailwind ([22f823c](https://github.com/C4illin/ConvertX/commit/22f823c535b20382981f86a13616b830a1f3392f)) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * rename css file to force update cache, fixes [#141](https://github.com/C4illin/ConvertX/issues/141) ([47139a5](https://github.com/C4illin/ConvertX/commit/47139a550bd3d847da288c61bf8f88953b79c673)) | ||||
|  | ||||
| ## [0.5.0](https://github.com/C4illin/ConvertX/compare/v0.4.1...v0.5.0) (2024-09-20) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * add option to customize how often files are automatically deleted ([317c932](https://github.com/C4illin/ConvertX/commit/317c932c2a26280bf37ed3d3bf9b879413590f5a)) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * improve file name replacement logic ([60ba7c9](https://github.com/C4illin/ConvertX/commit/60ba7c93fbdc961f3569882fade7cc13dee7a7a5)) | ||||
|  | ||||
| ## [0.4.1](https://github.com/C4illin/ConvertX/compare/v0.4.0...v0.4.1) (2024-09-15) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * allow non lowercase true and false values, fixes [#122](https://github.com/C4illin/ConvertX/issues/122) ([bef1710](https://github.com/C4illin/ConvertX/commit/bef1710e3376baa7e25c107ded20a40d18b8c6b0)) | ||||
|  | ||||
| ## [0.4.0](https://github.com/C4illin/ConvertX/compare/v0.3.3...v0.4.0) (2024-08-26) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * add option for unauthenticated file conversions [#114](https://github.com/C4illin/ConvertX/issues/114) ([f0d0e43](https://github.com/C4illin/ConvertX/commit/f0d0e4392983c3e4c530304ea88e023fda9bcac0)) | ||||
| * add resvg converter ([d5eeef9](https://github.com/C4illin/ConvertX/commit/d5eeef9f6884b2bb878508bed97ea9ceaa662995)) | ||||
| * add robots.txt ([6597c1d](https://github.com/C4illin/ConvertX/commit/6597c1d7caeb4dfb6bc47b442e4dfc9840ad12b7)) | ||||
| * Add search bar for formats ([53fff59](https://github.com/C4illin/ConvertX/commit/53fff594fc4d69306abcb2a5cad890fcd0953a58)) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * keep unauthenticated user logged in if allowed [#114](https://github.com/C4illin/ConvertX/issues/114) ([bc4ad49](https://github.com/C4illin/ConvertX/commit/bc4ad492852fad8cb832a0c03485cccdd7f7b117)) | ||||
| * pdf support in vips ([8ca4f15](https://github.com/C4illin/ConvertX/commit/8ca4f1587df7f358893941c656d78d75f04dac93)) | ||||
| * Slow click on conversion popup does not work ([4d9c4d6](https://github.com/C4illin/ConvertX/commit/4d9c4d64aa0266f3928935ada68d91ac81f638aa)) | ||||
|  | ||||
| ## [0.3.3](https://github.com/C4illin/ConvertX/compare/v0.3.2...v0.3.3) (2024-07-30) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * downgrade @elysiajs/html dependency to version 1.0.2 ([c714ade](https://github.com/C4illin/ConvertX/commit/c714ade3e23865ba6cfaf76c9e7259df1cda222c)) | ||||
|  | ||||
| ## [0.3.2](https://github.com/C4illin/ConvertX/compare/v0.3.1...v0.3.2) (2024-07-09) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * increase max request body to support large uploads ([3ae2db5](https://github.com/C4illin/ConvertX/commit/3ae2db5d9b36fe3dcd4372ddcd32aa573ea59aa6)), closes [#64](https://github.com/C4illin/ConvertX/issues/64) | ||||
|  | ||||
| ## [0.3.1](https://github.com/C4illin/ConvertX/compare/v0.3.0...v0.3.1) (2024-06-27) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * release releases ([4d4c13a](https://github.com/C4illin/ConvertX/commit/4d4c13a8d85ec7c9209ad41cdbea7d4380b0edbf)) | ||||
|  | ||||
| ## [0.3.0](https://github.com/C4illin/ConvertX/compare/v0.2.0...v0.3.0) (2024-06-27) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * add version number to log ([4dcb796](https://github.com/C4illin/ConvertX/commit/4dcb796e1bd27badc078d0638076cd9f1e81c4a4)), closes [#44](https://github.com/C4illin/ConvertX/issues/44) | ||||
| * change to xelatex ([fae2ba9](https://github.com/C4illin/ConvertX/commit/fae2ba9c54461dccdccd1bfb5e76398540d11d0b)) | ||||
| * print version of installed converters to log ([801cf28](https://github.com/C4illin/ConvertX/commit/801cf28d1e5edac9353b0b16be75a4fb48470b8a)) | ||||
|  | ||||
| ## [0.2.0](https://github.com/C4illin/ConvertX/compare/v0.1.2...v0.2.0) (2024-06-20) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,63 +0,0 @@ | ||||
| FROM oven/bun:1-debian as base | ||||
| WORKDIR /app | ||||
|  | ||||
| # install dependencies into temp directory | ||||
| # this will cache them and speed up future builds | ||||
| FROM base AS install | ||||
| RUN mkdir -p /temp/dev | ||||
| COPY package.json bun.lockb /temp/dev/ | ||||
| RUN cd /temp/dev && bun install --frozen-lockfile | ||||
|  | ||||
| # install with --production (exclude devDependencies) | ||||
| RUN mkdir -p /temp/prod | ||||
| COPY package.json bun.lockb /temp/prod/ | ||||
| RUN cd /temp/prod && bun install --frozen-lockfile --production | ||||
|  | ||||
| # FROM base AS install-libjxl-tools | ||||
| # download | ||||
|  | ||||
|  | ||||
|  | ||||
| # copy node_modules from temp directory | ||||
| # then copy all (non-ignored) project files into the image | ||||
| # FROM base AS prerelease | ||||
| # COPY --from=install /temp/dev/node_modules node_modules | ||||
| # COPY . . | ||||
|  | ||||
| # # [optional] tests & build | ||||
| # ENV NODE_ENV=production | ||||
| # RUN bun test | ||||
| # 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" | ||||
|  | ||||
| # install additional dependencies | ||||
| RUN rm -rf /var/lib/apt/lists/partial && apt-get update -o Acquire::CompressionTypes::Order::=gz \ | ||||
|   && apt-get install -y \ | ||||
|   pandoc \ | ||||
|   texlive-latex-recommended \ | ||||
|   texlive-fonts-recommended \ | ||||
|   texlive-latex-extra \ | ||||
|   ffmpeg \ | ||||
|   graphicsmagick \ | ||||
|   ghostscript \ | ||||
|   libvips-tools | ||||
|  | ||||
| # # libjxl is not available in the official debian repositories | ||||
| # RUN wget https://github.com/libjxl/libjxl/releases/download/v0.10.2/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz -O /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz \ | ||||
| #   && mkdir -p /tmp/libjxl \ | ||||
| #   && tar -xvf /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz -C /tmp/libjxl \ | ||||
| #   && dpkg -i /tmp/libjxl/libjxl_0.10.2_amd64.deb /tmp/libjxl/jxl_0.10.2_amd64.deb \ | ||||
| #   && rm -rf /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz /tmp/libjxl | ||||
|  | ||||
| COPY --from=install /temp/prod/node_modules node_modules | ||||
| # COPY --from=prerelease /app/src/index.tsx /app/src/ | ||||
| # COPY --from=prerelease /app/package.json . | ||||
| COPY . . | ||||
|  | ||||
| EXPOSE 3000/tcp | ||||
| ENTRYPOINT [ "bun", "run", "./src/index.tsx" ] | ||||
							
								
								
									
										52
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						| @@ -1,53 +1,71 @@ | ||||
| FROM oven/bun:1-alpine as base | ||||
| FROM oven/bun:1.2.2-alpine AS base | ||||
| LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX" | ||||
| WORKDIR /app | ||||
|  | ||||
| # install dependencies into temp directory | ||||
| # this will cache them and speed up future builds | ||||
| FROM base AS install | ||||
| RUN mkdir -p /temp/dev | ||||
| COPY package.json bun.lockb /temp/dev/ | ||||
| COPY package.json bun.lock /temp/dev/ | ||||
| RUN cd /temp/dev && bun install --frozen-lockfile | ||||
|  | ||||
| # install with --production (exclude devDependencies) | ||||
| RUN mkdir -p /temp/prod | ||||
| COPY package.json bun.lockb /temp/prod/ | ||||
| COPY package.json bun.lock /temp/prod/ | ||||
| RUN cd /temp/prod && bun install --frozen-lockfile --production | ||||
|  | ||||
| # copy node_modules from temp directory | ||||
| # then copy all (non-ignored) project files into the image | ||||
| # FROM base AS prerelease | ||||
| # COPY --from=install /temp/dev/node_modules node_modules | ||||
| # COPY . . | ||||
| FROM base AS builder | ||||
| RUN apk --no-cache add curl gcc | ||||
| ENV PATH=/root/.cargo/bin:$PATH | ||||
| RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y | ||||
| RUN cargo install resvg | ||||
|  | ||||
| FROM base AS prerelease | ||||
| WORKDIR /app | ||||
| COPY --from=install /temp/dev/node_modules node_modules | ||||
| COPY . . | ||||
|  | ||||
| # # [optional] tests & build | ||||
| # ENV NODE_ENV=production | ||||
| # RUN bun test | ||||
| # RUN bun run build | ||||
| 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 libheif-tools --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/ | ||||
|  | ||||
| # install additional dependencies | ||||
| RUN apk --no-cache add  \ | ||||
|   pandoc \ | ||||
|   texlive \ | ||||
|   texlive-xetex \ | ||||
|   texmf-dist-latexextra \ | ||||
|   ffmpeg \ | ||||
|   graphicsmagick \ | ||||
|   ghostscript \ | ||||
|   vips-tools \ | ||||
|   libjxl-tools | ||||
|   vips-poppler \ | ||||
|   vips-jxl \ | ||||
|   vips-heif \ | ||||
|   vips-magick \ | ||||
|   libjxl-tools \ | ||||
|   assimp \ | ||||
|   inkscape \ | ||||
|   poppler-utils \ | ||||
|   gcompat \ | ||||
|   libva-utils \ | ||||
|   py3-numpy \ | ||||
|   potrace | ||||
|  | ||||
| # 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 \ | ||||
|  | ||||
| COPY --from=install /temp/prod/node_modules node_modules | ||||
| # COPY --from=prerelease /app/src/index.tsx /app/src/ | ||||
| # COPY --from=prerelease /app/package.json . | ||||
| COPY --from=builder /root/.cargo/bin/resvg /usr/local/bin/resvg | ||||
| COPY --from=prerelease /app/public/generated.css /app/public/ | ||||
| COPY . . | ||||
|  | ||||
| EXPOSE 3000/tcp | ||||
| ENV NODE_ENV=production | ||||
| ENTRYPOINT [ "bun", "run", "./src/index.tsx" ] | ||||
							
								
								
									
										242
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,85 +1,157 @@ | ||||
|  | ||||
| # ConvertX | ||||
| [](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml) | ||||
| [](https://github.com/C4illin/ConvertX/pkgs/container/convertx) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| A self-hosted online file converter. Supports 831 different formats. Written with TypeScript, Bun and Elysia. | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| - Convert files to different formats | ||||
| - Password protection | ||||
| - Multiple accounts | ||||
|  | ||||
| ## Converters supported | ||||
|  | ||||
| | Converter                                                                    | Use case      | Converts from | Converts to | | ||||
| |------------------------------------------------------------------------------|---------------|---------------|-------------| | ||||
| | [libjxl](https://github.com/libjxl/libjxl)                                   | JPEG XL       | 11            | 11          | | ||||
| | [Vips](https://github.com/libvips/libvips)                                   | Images        | 45            | 23          | | ||||
| | [PDFLaTeX](https://www.math.rug.nl/~trentelman/jacob/pdflatex/pdflatex.html) | Documents     | 1             | 1           | | ||||
| | [Pandoc](https://pandoc.org/)                                                | Documents     | 43            | 65          | | ||||
| | [GraphicsMagick](http://www.graphicsmagick.org/)                             | Images        | 166           | 133         | | ||||
| | [FFmpeg](https://ffmpeg.org/)                                                | Video         | ~473          | ~280        | | ||||
|  | ||||
| <!-- many ffmpeg fileformats are duplicates --> | ||||
|  | ||||
| ## Deployment | ||||
|  | ||||
| ```yml | ||||
| # docker-compose.yml | ||||
| services: | ||||
|   convertx:  | ||||
|     image: ghcr.io/c4illin/convertx | ||||
|     ports: | ||||
|       - "3000:3000" | ||||
|     environment: # Defaults are listed below. All are optional. | ||||
|       - ACCOUNT_REGISTRATION=false # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account) | ||||
|       - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default | ||||
|       - HTTP_ALLOWED=false # setting this to true is unsafe, only set this to true locally | ||||
|     volumes: | ||||
|       - convertx:/app/data | ||||
| ``` | ||||
|  | ||||
| <!-- or | ||||
|  | ||||
| ```bash | ||||
| docker run ghcr.io/c4illin/convertx:master -p 3000:3000 -e ACCOUNT_REGISTRATION=false -v /path/you/want:/app/data | ||||
| ``` --> | ||||
|  | ||||
| Then visit `http://localhost:3000` in your browser and create your account. Don't leave it unconfigured and open, as anyone can register the first account. | ||||
|  | ||||
| If you get unable to open database file run `chown -R $USER:$USER path` on the path you choose. | ||||
|  | ||||
| ### Tutorial | ||||
|  | ||||
| Tutorial in french: https://belginux.com/installer-convertx-avec-docker/ | ||||
|  | ||||
| ## Todo | ||||
| - [x] Add messages for errors in converters | ||||
| - [ ] Add options for converters | ||||
| - [ ] Add more converters | ||||
| - [ ] Divide index.tsx into smaller components | ||||
| - [ ] Add tests | ||||
| - [ ] Add searchable list of formats | ||||
| - [ ] Make the upload button nicer and more easy to drop files on. Support copy paste as well if possible. | ||||
|  | ||||
| ## Contributors | ||||
|  | ||||
| <a href="https://github.com/C4illin/ConvertX/graphs/contributors"> | ||||
|   <img src="https://contrib.rocks/image?repo=C4illin/ConvertX" /> | ||||
| </a> | ||||
|  | ||||
| ## Star History | ||||
|  | ||||
| <a href="https://github.com/C4illin/ConvertX/stargazers"> | ||||
|  <picture> | ||||
|    <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date&theme=dark" /> | ||||
|    <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" /> | ||||
|    <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" /> | ||||
|  </picture> | ||||
| </a> | ||||
|  | ||||
|  | ||||
| # ConvertX | ||||
|  | ||||
| [](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml) | ||||
| [](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX) | ||||
| [](https://hub.docker.com/r/c4illin/convertx) | ||||
| [](https://github.com/C4illin/ConvertX/pkgs/container/convertx) | ||||
|  | ||||
|  | ||||
|  | ||||
| <!--  --> | ||||
|  | ||||
| A self-hosted online file converter. Supports over a thousand different formats. Written with TypeScript, Bun and Elysia. | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| - Convert files to different formats | ||||
| - Process multiple files at once | ||||
| - Password protection | ||||
| - Multiple accounts | ||||
|  | ||||
| ## Converters supported | ||||
|  | ||||
| | Converter                                                                    | Use case      | Converts from | Converts to | | ||||
| |------------------------------------------------------------------------------|---------------|---------------|-------------| | ||||
| | [libjxl](https://github.com/libjxl/libjxl)                                   | JPEG XL       | 11            | 11          | | ||||
| | [resvg](https://github.com/RazrFalcon/resvg)                                 | SVG           | 1             | 1           | | ||||
| | [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           | | ||||
| | [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 --> | ||||
|  | ||||
| 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: | ||||
|   convertx:  | ||||
|     image: ghcr.io/c4illin/convertx | ||||
|     container_name: convertx | ||||
|     restart: unless-stopped | ||||
|     ports: | ||||
|       - "3000:3000" | ||||
|     environment: | ||||
|       - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() if unset | ||||
|     volumes: | ||||
|       - ./data:/app/data | ||||
| ``` | ||||
|  | ||||
| or | ||||
|  | ||||
| ```bash | ||||
| docker run -p 3000:3000 -v ./data:/app/data ghcr.io/c4illin/convertx | ||||
| ``` | ||||
|  | ||||
| Then visit `http://localhost:3000` in your browser and create your account. Don't leave it unconfigured and open, as anyone can register the first account. | ||||
|  | ||||
| If you get unable to open database file run `chown -R $USER:$USER path` on the path you choose. | ||||
|  | ||||
| ### Environment variables | ||||
|  | ||||
| All are optional, JWT_SECRET is recommended to be set. | ||||
|  | ||||
| | Name                      | Default | Description | | ||||
| |---------------------------|---------|-------------| | ||||
| | JWT_SECRET                | when unset it will use the value from randomUUID() | A long and secret string used to sign the JSON Web Token | | ||||
| | ACCOUNT_REGISTRATION      | false | Allow users to register accounts | | ||||
| | HTTP_ALLOWED              | false | Allow HTTP connections, only set this to true locally | | ||||
| | ALLOW_UNAUTHENTICATED     | false | Allow unauthenticated users to use the service, only set this to true locally | | ||||
| | 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` | | ||||
| | HIDE_HISTORY             | false | Hide the history page | | ||||
|  | ||||
| ### Docker images | ||||
|  | ||||
| There is a `:latest` tag that is updated with every release and a `:main` tag that is updated with every push to the main branch. `:latest` is recommended for normal use. | ||||
|  | ||||
| The image is available on [GitHub Container Registry](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX) and [Docker Hub](https://hub.docker.com/r/c4illin/convertx). | ||||
|  | ||||
| | Image | What it is | | ||||
| |-------|------------| | ||||
| | `image: ghcr.io/c4illin/convertx` | The latest release on ghcr | | ||||
| | `image: ghcr.io/c4illin/convertx:main` | The latest commit on ghcr | | ||||
| | `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/> | ||||
|  | ||||
| ## Screenshots | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Development | ||||
|  | ||||
| 0. Install [Bun](https://bun.sh/) and Git | ||||
| 1. Clone the repository | ||||
| 2. `bun install` | ||||
| 3. `bun run dev` | ||||
|  | ||||
| Pull requests are welcome! See below and open issues for the list of todos. | ||||
|  | ||||
| ## Todo | ||||
|  | ||||
| - [x] Add messages for errors in converters | ||||
| - [x] Add searchable list of formats | ||||
| - [ ] Add options for converters | ||||
| - [ ] Divide index.tsx into smaller components | ||||
| - [ ] Add tests | ||||
| - [ ] Make the upload button nicer and more easy to drop files on. Support copy paste as well if possible. | ||||
| - [ ] Make errors logs visible from the web ui | ||||
| - [ ] Add more converters: | ||||
|   - [ ] [deark](https://github.com/jsummers/deark) | ||||
|   - [ ] LibreOffice | ||||
|   - [ ] [dvisvgm](https://github.com/mgieseki/dvisvgm) | ||||
|  | ||||
| ## Contributors | ||||
|  | ||||
| <a href="https://github.com/C4illin/ConvertX/graphs/contributors"> | ||||
|   <img src="https://contrib.rocks/image?repo=C4illin/ConvertX" alt="Image with all contributors"/> | ||||
| </a> | ||||
|  | ||||
| ## Star History | ||||
|  | ||||
| <a href="https://github.com/C4illin/ConvertX/stargazers"> | ||||
|  <picture> | ||||
|    <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date&theme=dark" /> | ||||
|    <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" /> | ||||
|    <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" /> | ||||
|  </picture> | ||||
| </a> | ||||
|   | ||||
							
								
								
									
										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. | ||||
							
								
								
									
										23
									
								
								biome.json
									
									
									
									
									
								
							
							
						
						| @@ -1,5 +1,5 @@ | ||||
| { | ||||
|   "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", | ||||
|   "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", | ||||
|   "formatter": { | ||||
|     "enabled": true, | ||||
|     "formatWithErrors": true, | ||||
| @@ -9,7 +9,15 @@ | ||||
|     "lineWidth": 80, | ||||
|     "attributePosition": "auto" | ||||
|   }, | ||||
|   "organizeImports": { "enabled": true }, | ||||
|   "files": { | ||||
|     "ignore": [ | ||||
|       "**/node_modules/**", | ||||
|       "**/pico.lime.min.css" | ||||
|     ] | ||||
|   }, | ||||
|   "organizeImports": { | ||||
|     "enabled": true | ||||
|   }, | ||||
|   "linter": { | ||||
|     "enabled": true, | ||||
|     "rules": { | ||||
| @@ -22,7 +30,11 @@ | ||||
|         "useLiteralKeys": "error", | ||||
|         "useOptionalChain": "error" | ||||
|       }, | ||||
|       "correctness": { "noPrecisionLoss": "error", "noUnusedVariables": "off" }, | ||||
|       "correctness": { | ||||
|         "noPrecisionLoss": "error", | ||||
|         "noUnusedVariables": "off", | ||||
|         "useJsxKeyInIterable": "off" | ||||
|       }, | ||||
|       "style": { | ||||
|         "noInferrableTypes": "error", | ||||
|         "noNamespace": "error", | ||||
| @@ -42,6 +54,9 @@ | ||||
|         "noUnsafeDeclarationMerging": "error", | ||||
|         "useAwait": "error", | ||||
|         "useNamespaceKeyword": "error" | ||||
|       }, | ||||
|       "nursery": { | ||||
|         "useSortedClasses": "error" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
| @@ -57,4 +72,4 @@ | ||||
|       "attributePosition": "auto" | ||||
|     } | ||||
|   } | ||||
| } | ||||
| } | ||||
							
								
								
									
										30
									
								
								compose.yaml
									
									
									
									
									
								
							
							
						
						| @@ -1,12 +1,18 @@ | ||||
| services: | ||||
|   convertx: | ||||
|     build: | ||||
|       context: . | ||||
|       # dockerfile: Debian.Dockerfile | ||||
|     volumes: | ||||
|       - ./data:/app/data | ||||
|     environment: | ||||
|       - ACCOUNT_REGISTRATION=true | ||||
|       - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 | ||||
|     ports: | ||||
|       - 3000:3000 | ||||
| services: | ||||
|   convertx: | ||||
|     build: | ||||
|       context: . | ||||
|       # dockerfile: Debian.Dockerfile | ||||
|     volumes: | ||||
|       - ./data:/app/data | ||||
|     environment: # Defaults are listed below. All are optional. | ||||
|       - ACCOUNT_REGISTRATION=true # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account) | ||||
|       - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default | ||||
|       - HTTP_ALLOWED=true # setting this to true is unsafe, only set this to true locally | ||||
|       - ALLOW_UNAUTHENTICATED=true # allows anyone to use the service without logging in, only set this to true locally | ||||
|       - 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 | ||||
|   | ||||
							
								
								
									
										57
									
								
								eslint.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,57 @@ | ||||
| import js from "@eslint/js"; | ||||
| import eslintParserTypeScript from "@typescript-eslint/parser"; | ||||
| import type { Linter } from "eslint"; | ||||
| import eslintPluginReadableTailwind from "eslint-plugin-readable-tailwind"; | ||||
| import simpleImportSortPlugin from "eslint-plugin-simple-import-sort"; | ||||
| import globals from "globals"; | ||||
| import tseslint from "typescript-eslint"; | ||||
|  | ||||
| export default [ | ||||
|   js.configs.recommended, | ||||
|   ...tseslint.configs.recommended, | ||||
|   // ...tailwind.configs["flat/recommended"], | ||||
|   { | ||||
|     plugins: { | ||||
|       "simple-import-sort": simpleImportSortPlugin, | ||||
|       "readable-tailwind": eslintPluginReadableTailwind, | ||||
|     }, | ||||
|     ignores: ["**/node_modules/**"], | ||||
|     languageOptions: { | ||||
|       parser: eslintParserTypeScript, | ||||
|       parserOptions: { | ||||
|         project: true, | ||||
|         ecmaFeatures: { | ||||
|           jsx: true, | ||||
|         }, | ||||
|       }, | ||||
|       globals: { | ||||
|         ...globals.node, | ||||
|         ...globals.browser, | ||||
|       }, | ||||
|     }, | ||||
|     files: ["**/*.{js,mjs,cjs,jsx,tsx,ts}"], | ||||
|     rules: { | ||||
|       ...eslintPluginReadableTailwind.configs.warning.rules, | ||||
|       // "tailwindcss/classnames-order": "off", | ||||
|       "readable-tailwind/multiline": [ | ||||
|         "warn", | ||||
|         { | ||||
|           group: "newLine", | ||||
|           printWidth: 100, | ||||
|         }, | ||||
|       ], | ||||
|       // "tailwindcss/no-custom-classname": [ | ||||
|       //   "warn", | ||||
|       //   { | ||||
|       //     whitelist: [ | ||||
|       //       "select_container", | ||||
|       //       "convert_to_popup", | ||||
|       //       "convert_to_group", | ||||
|       //       "target", | ||||
|       //       "convert_to_target", | ||||
|       //     ], | ||||
|       //   }, | ||||
|       // ], | ||||
|     }, | ||||
|   }, | ||||
| ] as Linter.Config[]; | ||||
							
								
								
									
										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
									
								
							
							
						
						| @@ -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; | ||||
|       } | ||||
|     ); | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								images/preview.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 53 KiB | 
							
								
								
									
										69
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,42 +1,55 @@ | ||||
| { | ||||
|   "name": "convertx-frontend", | ||||
|   "version": "0.2.0", | ||||
|   "version": "0.13.0", | ||||
|   "scripts": { | ||||
|     "dev": "bun run --watch src/index.tsx", | ||||
|     "hot": "bun run --hot src/index.tsx", | ||||
|     "format": "biome format --write ./src", | ||||
|     "css": "cpy 'node_modules/@picocss/pico/css/pico.lime.min.css' 'src/public/' --flat" | ||||
|     "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:*'", | ||||
|     "lint:tsc": "tsc --noEmit", | ||||
|     "lint:knip": "knip", | ||||
|     "lint:eslint": "eslint ." | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@elysiajs/cookie": "^0.8.0", | ||||
|     "@elysiajs/html": "^1.0.2", | ||||
|     "@elysiajs/jwt": "^1.0.2", | ||||
|     "@elysiajs/static": "^1.0.3", | ||||
|     "elysia": "^1.0.24" | ||||
|     "@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", | ||||
|   "type": "module", | ||||
|   "bun-create": { | ||||
|     "start": "bun run src/index.tsx" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@biomejs/biome": "1.8.1", | ||||
|     "@ianvs/prettier-plugin-sort-imports": "^4.2.1", | ||||
|     "@kitajs/ts-html-plugin": "^4.0.1", | ||||
|     "@picocss/pico": "^2.0.6", | ||||
|     "@total-typescript/ts-reset": "^0.5.1", | ||||
|     "@types/bun": "^1.1.5", | ||||
|     "@types/eslint": "^8.56.10", | ||||
|     "@types/node": "^20.14.6", | ||||
|     "@types/ws": "^8.5.10", | ||||
|     "@typescript-eslint/eslint-plugin": "^7.13.1", | ||||
|     "@typescript-eslint/parser": "^7.13.1", | ||||
|     "cpy-cli": "^5.0.0", | ||||
|     "eslint-config-prettier": "^9.1.0", | ||||
|     "eslint-plugin-prettier": "^5.1.3", | ||||
|     "prettier": "^3.3.2", | ||||
|     "typescript": "^5.5.2" | ||||
|   }, | ||||
|   "trustedDependencies": [ | ||||
|     "@biomejs/biome" | ||||
|   ] | ||||
|     "@eslint/js": "^9.26.0", | ||||
|     "@ianvs/prettier-plugin-sort-imports": "^4.4.1", | ||||
|     "@kitajs/ts-html-plugin": "^4.1.1", | ||||
|     "@tailwindcss/cli": "^4.1.6", | ||||
|     "@tailwindcss/postcss": "^4.1.6", | ||||
|     "@total-typescript/ts-reset": "^0.6.1", | ||||
|     "@types/bun": "^1.2.13", | ||||
|     "@types/eslint-plugin-tailwindcss": "^3.17.0", | ||||
|     "@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.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" | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										5
									
								
								postcss.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| export default { | ||||
|   plugins: { | ||||
|     "@tailwindcss/postcss": {}, | ||||
|   }, | ||||
| }; | ||||
| Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB | 
| Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB | 
| Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB | 
| Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 476 B | 
| Before Width: | Height: | Size: 960 B After Width: | Height: | Size: 960 B | 
| Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB | 
| @@ -1,3 +1,5 @@ | ||||
| const webroot = document.querySelector("meta[name='webroot']").content; | ||||
| 
 | ||||
| window.downloadAll = function () { | ||||
|   // Get all download links
 | ||||
|   const downloadLinks = document.querySelectorAll("a[download]"); | ||||
| @@ -18,7 +20,7 @@ let progressElem = document.querySelector("progress"); | ||||
| const refreshData = () => { | ||||
|   // console.log("Refreshing data...", progressElem.value, progressElem.max);
 | ||||
|   if (progressElem.value !== progressElem.max) { | ||||
|     fetch(`/progress/${jobId}`, { | ||||
|     fetch(`${webroot}/progress/${jobId}`, { | ||||
|       method: "POST", | ||||
|     }) | ||||
|       .then((res) => res.text()) | ||||
							
								
								
									
										2
									
								
								public/robots.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | ||||
| User-agent: * | ||||
| Disallow: / | ||||
							
								
								
									
										254
									
								
								public/script.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,254 @@ | ||||
| const webroot = document.querySelector("meta[name='webroot']").content; | ||||
| const fileInput = document.querySelector('input[type="file"]'); | ||||
| const dropZone = document.getElementById("dropzone"); | ||||
| const convertButton = document.querySelector("input[type='submit']"); | ||||
| const fileNames = []; | ||||
| let fileType; | ||||
| let pendingFiles = 0; | ||||
| let formatSelected = false; | ||||
|  | ||||
| dropZone.addEventListener("dragover", (e) => { | ||||
|   e.preventDefault(); | ||||
|   dropZone.classList.add("dragover"); | ||||
| }); | ||||
|  | ||||
| dropZone.addEventListener("dragleave", () => { | ||||
|   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 = () => { | ||||
|   const convertToInput = document.querySelector( | ||||
|     "input[name='convert_to_search']", | ||||
|   ); | ||||
|   const convertToPopup = document.querySelector(".convert_to_popup"); | ||||
|   const convertToGroupElements = document.querySelectorAll(".convert_to_group"); | ||||
|   const convertToGroups = {}; | ||||
|   const convertToElement = document.querySelector("select[name='convert_to']"); | ||||
|  | ||||
|   const showMatching = (search) => { | ||||
|     for (const [targets, groupElement] of Object.values(convertToGroups)) { | ||||
|       let matchingTargetsFound = 0; | ||||
|       for (const target of targets) { | ||||
|         if (target.dataset.target.includes(search)) { | ||||
|           matchingTargetsFound++; | ||||
|           target.classList.remove("hidden"); | ||||
|           target.classList.add("flex"); | ||||
|         } else { | ||||
|           target.classList.add("hidden"); | ||||
|           target.classList.remove("flex"); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (matchingTargetsFound === 0) { | ||||
|         groupElement.classList.add("hidden"); | ||||
|         groupElement.classList.remove("flex"); | ||||
|       } else { | ||||
|         groupElement.classList.remove("hidden"); | ||||
|         groupElement.classList.add("flex"); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   for (const groupElement of convertToGroupElements) { | ||||
|     const groupName = groupElement.dataset.converter; | ||||
|  | ||||
|     const targetElements = groupElement.querySelectorAll(".target"); | ||||
|     const targets = Array.from(targetElements); | ||||
|  | ||||
|     for (const target of targets) { | ||||
|       target.onmousedown = () => { | ||||
|         convertToElement.value = target.dataset.value; | ||||
|         convertToInput.value = `${target.dataset.target} using ${target.dataset.converter}`; | ||||
|         formatSelected = true; | ||||
|         if (pendingFiles === 0 && fileNames.length > 0) { | ||||
|           convertButton.disabled = false; | ||||
|         } | ||||
|         showMatching(""); | ||||
|       }; | ||||
|     } | ||||
|  | ||||
|     convertToGroups[groupName] = [targets, groupElement]; | ||||
|   } | ||||
|  | ||||
|   convertToInput.addEventListener("input", (e) => { | ||||
|     showMatching(e.target.value.toLowerCase()); | ||||
|   }); | ||||
|  | ||||
|   convertToInput.addEventListener("search", () => { | ||||
|     // when the user clears the search bar using the 'x' button | ||||
|     convertButton.disabled = true; | ||||
|     formatSelected = false; | ||||
|   }); | ||||
|  | ||||
|   convertToInput.addEventListener("blur", (e) => { | ||||
|     // Keep the popup open even when clicking on a target button | ||||
|     // for a split second to allow the click to go through | ||||
|     if (e?.relatedTarget?.classList?.contains("target")) { | ||||
|       convertToPopup.classList.add("hidden"); | ||||
|       convertToPopup.classList.remove("flex"); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     convertToPopup.classList.add("hidden"); | ||||
|     convertToPopup.classList.remove("flex"); | ||||
|   }); | ||||
|  | ||||
|   convertToInput.addEventListener("focus", () => { | ||||
|     convertToPopup.classList.remove("hidden"); | ||||
|     convertToPopup.classList.add("flex"); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // Add a 'change' event listener to the file input element | ||||
| fileInput.addEventListener("change", (e) => { | ||||
|   const files = e.target.files; | ||||
|   for (const file of files) { | ||||
|     handleFile(file); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| const setTitle = () => { | ||||
|   const title = document.querySelector("h1"); | ||||
|   title.textContent = `Convert ${fileType ? `.${fileType}` : ""}`; | ||||
| }; | ||||
|  | ||||
| // Add a onclick for the delete button | ||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
| const deleteRow = (target) => { | ||||
|   const filename = target.parentElement.parentElement.children[0].textContent; | ||||
|   const row = target.parentElement.parentElement; | ||||
|   row.remove(); | ||||
|  | ||||
|   // remove from fileNames | ||||
|   const index = fileNames.indexOf(filename); | ||||
|   fileNames.splice(index, 1); | ||||
|  | ||||
|   // reset fileInput | ||||
|   fileInput.value = ""; | ||||
|  | ||||
|   // if fileNames is empty, reset fileType | ||||
|   if (fileNames.length === 0) { | ||||
|     fileType = null; | ||||
|     fileInput.removeAttribute("accept"); | ||||
|     convertButton.disabled = true; | ||||
|     setTitle(); | ||||
|   } | ||||
|  | ||||
|   fetch(`${webroot}/delete`, { | ||||
|     method: "POST", | ||||
|     body: JSON.stringify({ filename: filename }), | ||||
|     headers: { | ||||
|       "Content-Type": "application/json", | ||||
|     }, | ||||
|   }) | ||||
|     .catch((err) => console.log(err)); | ||||
| }; | ||||
|  | ||||
| const uploadFile = (file) => { | ||||
|   convertButton.disabled = true; | ||||
|   convertButton.textContent = "Uploading..."; | ||||
|   pendingFiles += 1; | ||||
|  | ||||
|   const formData = new FormData(); | ||||
|   formData.append("file", file, file.name); | ||||
|  | ||||
|   let xhr = new XMLHttpRequest();  | ||||
|  | ||||
|   xhr.open("POST", `${webroot}/upload`, true); | ||||
|  | ||||
|   xhr.onload = () => { | ||||
|     let data = JSON.parse(xhr.responseText); | ||||
|  | ||||
|     pendingFiles -= 1; | ||||
|     if (pendingFiles === 0) { | ||||
|       if (formatSelected) { | ||||
|         convertButton.disabled = false; | ||||
|       } | ||||
|       convertButton.textContent = "Convert"; | ||||
|     } | ||||
|  | ||||
|     //Remove the progress bar when upload is done | ||||
|     let progressbar = file.htmlRow.getElementsByTagName("progress"); | ||||
|     progressbar[0].parentElement.remove(); | ||||
|     console.log(data); | ||||
|   }; | ||||
|  | ||||
|   xhr.upload.onprogress = (e) => { | ||||
|     let sent = e.loaded; | ||||
|     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(); | ||||
							
								
								
									
										11
									
								
								renovate.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | ||||
| { | ||||
|   "$schema": "https://docs.renovatebot.com/renovate-schema.json", | ||||
|   "extends": [ | ||||
|     "config:recommended", | ||||
|     ":disableDependencyDashboard" | ||||
|   ], | ||||
|   "lockFileMaintenance": { | ||||
|     "enabled": true, | ||||
|     "automerge": true | ||||
|   } | ||||
| } | ||||
| @@ -1,30 +1,40 @@ | ||||
| export const BaseHtml = ({ children, title = "ConvertX" }) => ( | ||||
|   <html lang="en"> | ||||
|     <head> | ||||
|       <meta charset="UTF-8" /> | ||||
|       <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|       <title safe>{title}</title> | ||||
|       <link rel="stylesheet" href="/pico.lime.min.css" /> | ||||
|       <link rel="stylesheet" href="/style.css" /> | ||||
|       <link | ||||
|         rel="apple-touch-icon" | ||||
|         sizes="180x180" | ||||
|         href="/apple-touch-icon.png" | ||||
|       /> | ||||
|       <link | ||||
|         rel="icon" | ||||
|         type="image/png" | ||||
|         sizes="32x32" | ||||
|         href="/favicon-32x32.png" | ||||
|       /> | ||||
|       <link | ||||
|         rel="icon" | ||||
|         type="image/png" | ||||
|         sizes="16x16" | ||||
|         href="/favicon-16x16.png" | ||||
|       /> | ||||
|       <link rel="manifest" href="/site.webmanifest" /> | ||||
|     </head> | ||||
|     <body>{children}</body> | ||||
|   </html> | ||||
| ); | ||||
| import { Html } from "@elysiajs/html"; | ||||
|  | ||||
| export const BaseHtml = ({ | ||||
|   children, | ||||
|   title = "ConvertX", | ||||
|   webroot = "", | ||||
| }: { | ||||
|   children: JSX.Element; | ||||
|   title?: string; | ||||
|   webroot?: string; | ||||
| }) => ( | ||||
|   <html lang="en"> | ||||
|     <head> | ||||
|       <meta charset="UTF-8" /> | ||||
|       <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|       <meta name="webroot" content={webroot} /> | ||||
|       <title safe>{title}</title> | ||||
|       <link rel="stylesheet" href={`${webroot}/generated.css`} /> | ||||
|       <link | ||||
|         rel="apple-touch-icon" | ||||
|         sizes="180x180" | ||||
|         href={`${webroot}/apple-touch-icon.png`} | ||||
|       /> | ||||
|       <link | ||||
|         rel="icon" | ||||
|         type="image/png" | ||||
|         sizes="32x32" | ||||
|         href={`${webroot}/favicon-32x32.png`} | ||||
|       /> | ||||
|       <link | ||||
|         rel="icon" | ||||
|         type="image/png" | ||||
|         sizes="16x16" | ||||
|         href={`${webroot}/favicon-16x16.png`} | ||||
|       /> | ||||
|       <link rel="manifest" href={`${webroot}/site.webmanifest`} /> | ||||
|     </head> | ||||
|     <body class="w-full bg-neutral-900 text-neutral-200">{children}</body> | ||||
|   </html> | ||||
| ); | ||||
|   | ||||
| @@ -1,48 +1,88 @@ | ||||
| import { Html } from "@kitajs/html"; | ||||
|  | ||||
| export const Header = ({ | ||||
|   loggedIn, | ||||
|   accountRegistration, | ||||
| }: { loggedIn?: boolean; accountRegistration?: boolean }) => { | ||||
|   allowUnauthenticated, | ||||
|   hideHistory, | ||||
|   webroot = "", | ||||
| }: { | ||||
|   loggedIn?: boolean; | ||||
|   accountRegistration?: boolean; | ||||
|   allowUnauthenticated?: boolean; | ||||
|   hideHistory?: boolean; | ||||
|   webroot?: string; | ||||
| }) => { | ||||
|   let rightNav: JSX.Element; | ||||
|   if (loggedIn) { | ||||
|     rightNav = ( | ||||
|       <ul> | ||||
|         <li> | ||||
|           <a href="/history">History</a> | ||||
|         </li> | ||||
|         <li> | ||||
|           <a href="/logoff">Logout</a> | ||||
|         </li> | ||||
|       <ul class="flex gap-4"> | ||||
|         {!hideHistory && ( | ||||
|           <li> | ||||
|             <a | ||||
|               class={` | ||||
|                 text-accent-600 transition-all | ||||
|                 hover:text-accent-500 hover:underline | ||||
|               `} | ||||
|               href={`${webroot}/history`} | ||||
|             > | ||||
|               History | ||||
|             </a> | ||||
|           </li> | ||||
|         )} | ||||
|         {!allowUnauthenticated ? ( | ||||
|           <li> | ||||
|             <a | ||||
|               class={` | ||||
|                 text-accent-600 transition-all | ||||
|                 hover:text-accent-500 hover:underline | ||||
|               `} | ||||
|               href={`${webroot}/logoff`} | ||||
|             > | ||||
|               Logout | ||||
|             </a> | ||||
|           </li> | ||||
|         ) : null} | ||||
|       </ul> | ||||
|     ); | ||||
|   } else { | ||||
|     rightNav = ( | ||||
|       <ul> | ||||
|       <ul class="flex gap-4"> | ||||
|         <li> | ||||
|           <a href="/login">Login</a> | ||||
|           <a | ||||
|             class={` | ||||
|               text-accent-600 transition-all | ||||
|               hover:text-accent-500 hover:underline | ||||
|             `} | ||||
|             href={`${webroot}/login`} | ||||
|           > | ||||
|             Login | ||||
|           </a> | ||||
|         </li> | ||||
|         {accountRegistration && ( | ||||
|         {accountRegistration ? ( | ||||
|           <li> | ||||
|             <a href="/register">Register</a> | ||||
|             <a | ||||
|               class={` | ||||
|                 text-accent-600 transition-all | ||||
|                 hover:text-accent-500 hover:underline | ||||
|               `} | ||||
|               href={`${webroot}/register`} | ||||
|             > | ||||
|               Register | ||||
|             </a> | ||||
|           </li> | ||||
|         )} | ||||
|         ) : null} | ||||
|       </ul> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <header className="container"> | ||||
|       <nav> | ||||
|     <header class="w-full p-4"> | ||||
|       <nav class="mx-auto flex max-w-4xl justify-between rounded-sm bg-neutral-900 p-4"> | ||||
|         <ul> | ||||
|           <li> | ||||
|             <strong> | ||||
|               <a | ||||
|                 href="/" | ||||
|                 style={{ | ||||
|                   textDecoration: "none", | ||||
|                   color: "inherit", | ||||
|                 }}> | ||||
|                 ConvertX | ||||
|               </a> | ||||
|               <a href={`${webroot}/`}>ConvertX</a> | ||||
|             </strong> | ||||
|           </li> | ||||
|         </ul> | ||||
|   | ||||
							
								
								
									
										139
									
								
								src/converters/assimp.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,139 @@ | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     object: [ | ||||
|       "3d", | ||||
|       "3ds", | ||||
|       "3mf", | ||||
|       "ac", | ||||
|       "ac3d", | ||||
|       "acc", | ||||
|       "amf", | ||||
|       "amj", | ||||
|       "ase", | ||||
|       "ask", | ||||
|       "assbin", | ||||
|       "b3d", | ||||
|       "blend", | ||||
|       "bsp", | ||||
|       "bvh", | ||||
|       "cob", | ||||
|       "csm", | ||||
|       "dae", | ||||
|       "dxf", | ||||
|       "enff", | ||||
|       "fbx", | ||||
|       "glb", | ||||
|       "gltf", | ||||
|       "hmb", | ||||
|       "hmp", | ||||
|       "ifc", | ||||
|       "ifczip", | ||||
|       "iqm", | ||||
|       "irr", | ||||
|       "irrmesh", | ||||
|       "lwo", | ||||
|       "lws", | ||||
|       "lxo", | ||||
|       "m3d", | ||||
|       "md2", | ||||
|       "md3", | ||||
|       "md5anim", | ||||
|       "md5camera", | ||||
|       "md5mesh", | ||||
|       "mdc", | ||||
|       "mdl", | ||||
|       "mesh.xml", | ||||
|       "mesh", | ||||
|       "mot", | ||||
|       "ms3d", | ||||
|       "ndo", | ||||
|       "nff", | ||||
|       "obj", | ||||
|       "off", | ||||
|       "ogex", | ||||
|       "pk3", | ||||
|       "ply", | ||||
|       "pmx", | ||||
|       "prj", | ||||
|       "q3o", | ||||
|       "q3s", | ||||
|       "raw", | ||||
|       "scn", | ||||
|       "sib", | ||||
|       "smd", | ||||
|       "step", | ||||
|       "stl", | ||||
|       "stp", | ||||
|       "ter", | ||||
|       "uc", | ||||
|       "usd", | ||||
|       "usda", | ||||
|       "usdc", | ||||
|       "usdz", | ||||
|       "vta", | ||||
|       "x", | ||||
|       "x3d", | ||||
|       "x3db", | ||||
|       "xgl", | ||||
|       "xml", | ||||
|       "zae", | ||||
|       "zgl", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     object: [ | ||||
|       "3ds", | ||||
|       "3mf", | ||||
|       "assbin", | ||||
|       "assjson", | ||||
|       "assxml", | ||||
|       "collada", | ||||
|       "dae", | ||||
|       "fbx", | ||||
|       "fbxa", | ||||
|       "glb", | ||||
|       "glb2", | ||||
|       "gltf", | ||||
|       "gltf2", | ||||
|       "json", | ||||
|       "obj", | ||||
|       "objnomtl", | ||||
|       "pbrt", | ||||
|       "ply", | ||||
|       "plyb", | ||||
|       "stl", | ||||
|       "stlb", | ||||
|       "stp", | ||||
|       "x", | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export async 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("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|  | ||||
|       if (stdout) { | ||||
|         console.log(`stdout: ${stdout}`); | ||||
|       } | ||||
|  | ||||
|       if (stderr) { | ||||
|         console.error(`stderr: ${stderr}`); | ||||
|       } | ||||
|  | ||||
|       resolve("Done"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										84
									
								
								src/converters/calibre.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,84 @@ | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     document: [ | ||||
|       "azw4", | ||||
|       "chm", | ||||
|       "cbr", | ||||
|       "cbz", | ||||
|       "cbt", | ||||
|       "cba", | ||||
|       "cb7", | ||||
|       "djvu", | ||||
|       "docx", | ||||
|       "epub", | ||||
|       "fb2", | ||||
|       "htlz", | ||||
|       "html", | ||||
|       "lit", | ||||
|       "lrf", | ||||
|       "mobi", | ||||
|       "odt", | ||||
|       "pdb", | ||||
|       "pdf", | ||||
|       "pml", | ||||
|       "rb", | ||||
|       "rtf", | ||||
|       "recipe", | ||||
|       "snb", | ||||
|       "tcr", | ||||
|       "txt", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     document: [ | ||||
|       "azw3", | ||||
|       "docx", | ||||
|       "epub", | ||||
|       "fb2", | ||||
|       "html", | ||||
|       "htmlz", | ||||
|       "lit", | ||||
|       "lrf", | ||||
|       "mobi", | ||||
|       "oeb", | ||||
|       "pdb", | ||||
|       "pdf", | ||||
|       "pml", | ||||
|       "rb", | ||||
|       "rtf", | ||||
|       "snb", | ||||
|       "tcr", | ||||
|       "txt", | ||||
|       "txtz", | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export async 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("ebook-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"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| @@ -1,339 +1,340 @@ | ||||
| import { exec } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     image: [ | ||||
|       "3fr", | ||||
|       "8bim", | ||||
|       "8bimtext", | ||||
|       "8bimwtext", | ||||
|       "app1", | ||||
|       "app1jpeg", | ||||
|       "art", | ||||
|       "arw", | ||||
|       "avs", | ||||
|       "b", | ||||
|       "bie", | ||||
|       "bigtiff", | ||||
|       "bmp", | ||||
|       "c", | ||||
|       "cals", | ||||
|       "caption", | ||||
|       "cin", | ||||
|       "cmyk", | ||||
|       "cmyka", | ||||
|       "cr2", | ||||
|       "crw", | ||||
|       "cur", | ||||
|       "cut", | ||||
|       "dcm", | ||||
|       "dcr", | ||||
|       "dcx", | ||||
|       "dng", | ||||
|       "dpx", | ||||
|       "epdf", | ||||
|       "epi", | ||||
|       "eps", | ||||
|       "epsf", | ||||
|       "epsi", | ||||
|       "ept", | ||||
|       "ept2", | ||||
|       "ept3", | ||||
|       "erf", | ||||
|       "exif", | ||||
|       "fax", | ||||
|       "file", | ||||
|       "fits", | ||||
|       "fractal", | ||||
|       "ftp", | ||||
|       "g", | ||||
|       "gif", | ||||
|       "gif87", | ||||
|       "gradient", | ||||
|       "gray", | ||||
|       "graya", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "hrz", | ||||
|       "http", | ||||
|       "icb", | ||||
|       "icc", | ||||
|       "icm", | ||||
|       "ico", | ||||
|       "icon", | ||||
|       "identity", | ||||
|       "image", | ||||
|       "iptc", | ||||
|       "iptctext", | ||||
|       "iptcwtext", | ||||
|       "jbg", | ||||
|       "jbig", | ||||
|       "jng", | ||||
|       "jnx", | ||||
|       "jpeg", | ||||
|       "jpg", | ||||
|       "k", | ||||
|       "k25", | ||||
|       "kdc", | ||||
|       "label", | ||||
|       "m", | ||||
|       "mac", | ||||
|       "map", | ||||
|       "mat", | ||||
|       "mef", | ||||
|       "miff", | ||||
|       "mng", | ||||
|       "mono", | ||||
|       "mpc", | ||||
|       "mrw", | ||||
|       "msl", | ||||
|       "mtv", | ||||
|       "mvg", | ||||
|       "nef", | ||||
|       "null", | ||||
|       "o", | ||||
|       "orf", | ||||
|       "otb", | ||||
|       "p7", | ||||
|       "pal", | ||||
|       "palm", | ||||
|       "pam", | ||||
|       "pbm", | ||||
|       "pcd", | ||||
|       "pcds", | ||||
|       "pct", | ||||
|       "pcx", | ||||
|       "pdb", | ||||
|       "pdf", | ||||
|       "pef", | ||||
|       "pfa", | ||||
|       "pfb", | ||||
|       "pgm", | ||||
|       "picon", | ||||
|       "pict", | ||||
|       "pix", | ||||
|       "plasma", | ||||
|       "png", | ||||
|       "png00", | ||||
|       "png24", | ||||
|       "png32", | ||||
|       "png48", | ||||
|       "png64", | ||||
|       "png8", | ||||
|       "pnm", | ||||
|       "ppm", | ||||
|       "ps", | ||||
|       "ptif", | ||||
|       "pwp", | ||||
|       "r", | ||||
|       "raf", | ||||
|       "ras", | ||||
|       "rgb", | ||||
|       "rgba", | ||||
|       "rla", | ||||
|       "rle", | ||||
|       "sct", | ||||
|       "sfw", | ||||
|       "sgi", | ||||
|       "sr2", | ||||
|       "srf", | ||||
|       "stegano", | ||||
|       "sun", | ||||
|       "svg", | ||||
|       "svgz", | ||||
|       "text", | ||||
|       "tga", | ||||
|       "tiff", | ||||
|       "tile", | ||||
|       "tim", | ||||
|       "topol", | ||||
|       "ttf", | ||||
|       "txt", | ||||
|       "uyvy", | ||||
|       "vda", | ||||
|       "vicar", | ||||
|       "vid", | ||||
|       "viff", | ||||
|       "vst", | ||||
|       "wbmp", | ||||
|       "webp", | ||||
|       "wmf", | ||||
|       "wpg", | ||||
|       "x3f", | ||||
|       "xbm", | ||||
|       "xc", | ||||
|       "xcf", | ||||
|       "xmp", | ||||
|       "xpm", | ||||
|       "xv", | ||||
|       "xwd", | ||||
|       "y", | ||||
|       "yuv", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     image: [ | ||||
|       "8bim", | ||||
|       "8bimtext", | ||||
|       "8bimwtext", | ||||
|       "app1", | ||||
|       "app1jpeg", | ||||
|       "art", | ||||
|       "avs", | ||||
|       "b", | ||||
|       "bie", | ||||
|       "bigtiff", | ||||
|       "bmp", | ||||
|       "bmp2", | ||||
|       "bmp3", | ||||
|       "brf", | ||||
|       "c", | ||||
|       "cals", | ||||
|       "cin", | ||||
|       "cmyk", | ||||
|       "cmyka", | ||||
|       "dcx", | ||||
|       "dpx", | ||||
|       "epdf", | ||||
|       "epi", | ||||
|       "eps", | ||||
|       "eps2", | ||||
|       "eps3", | ||||
|       "epsf", | ||||
|       "epsi", | ||||
|       "ept", | ||||
|       "ept2", | ||||
|       "ept3", | ||||
|       "exif", | ||||
|       "fax", | ||||
|       "fits", | ||||
|       "g", | ||||
|       "gif", | ||||
|       "gif87", | ||||
|       "gray", | ||||
|       "graya", | ||||
|       "histogram", | ||||
|       "html", | ||||
|       "icb", | ||||
|       "icc", | ||||
|       "icm", | ||||
|       "info", | ||||
|       "iptc", | ||||
|       "iptctext", | ||||
|       "iptcwtext", | ||||
|       "isobrl", | ||||
|       "isobrl6", | ||||
|       "jbg", | ||||
|       "jbig", | ||||
|       "jng", | ||||
|       "jpeg", | ||||
|       "jpg", | ||||
|       "k", | ||||
|       "m", | ||||
|       "m2v", | ||||
|       "map", | ||||
|       "mat", | ||||
|       "matte", | ||||
|       "miff", | ||||
|       "mng", | ||||
|       "mono", | ||||
|       "mpc", | ||||
|       "mpeg", | ||||
|       "mpg", | ||||
|       "msl", | ||||
|       "mtv", | ||||
|       "mvg", | ||||
|       "null", | ||||
|       "o", | ||||
|       "otb", | ||||
|       "p7", | ||||
|       "pal", | ||||
|       "pam", | ||||
|       "pbm", | ||||
|       "pcd", | ||||
|       "pcds", | ||||
|       "pcl", | ||||
|       "pct", | ||||
|       "pcx", | ||||
|       "pdb", | ||||
|       "pdf", | ||||
|       "pgm", | ||||
|       "picon", | ||||
|       "pict", | ||||
|       "png", | ||||
|       "png00", | ||||
|       "png24", | ||||
|       "png32", | ||||
|       "png48", | ||||
|       "png64", | ||||
|       "png8", | ||||
|       "pnm", | ||||
|       "ppm", | ||||
|       "preview", | ||||
|       "ps", | ||||
|       "ps2", | ||||
|       "ps3", | ||||
|       "ptif", | ||||
|       "r", | ||||
|       "ras", | ||||
|       "rgb", | ||||
|       "rgba", | ||||
|       "sgi", | ||||
|       "shtml", | ||||
|       "sun", | ||||
|       "text", | ||||
|       "tga", | ||||
|       "tiff", | ||||
|       "txt", | ||||
|       "ubrl", | ||||
|       "ubrl6", | ||||
|       "uil", | ||||
|       "uyvy", | ||||
|       "vda", | ||||
|       "vicar", | ||||
|       "vid", | ||||
|       "viff", | ||||
|       "vst", | ||||
|       "wbmp", | ||||
|       "webp", | ||||
|       "x", | ||||
|       "xbm", | ||||
|       "xmp", | ||||
|       "xpm", | ||||
|       "xv", | ||||
|       "xwd", | ||||
|       "y", | ||||
|       "yuv", | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||||
|   options?: any, | ||||
| ): Promise<string> { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     exec( | ||||
|       `gm convert "${filePath}" "${targetPath}"`, | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|         } | ||||
|  | ||||
|         if (stdout) { | ||||
|           console.log(`stdout: ${stdout}`); | ||||
|         } | ||||
|  | ||||
|         if (stderr) { | ||||
|           console.error(`stderr: ${stderr}`); | ||||
|         } | ||||
|  | ||||
|         resolve("success"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     image: [ | ||||
|       "3fr", | ||||
|       "8bim", | ||||
|       "8bimtext", | ||||
|       "8bimwtext", | ||||
|       "app1", | ||||
|       "app1jpeg", | ||||
|       "art", | ||||
|       "arw", | ||||
|       "avs", | ||||
|       "b", | ||||
|       "bie", | ||||
|       "bigtiff", | ||||
|       "bmp", | ||||
|       "c", | ||||
|       "cals", | ||||
|       "caption", | ||||
|       "cin", | ||||
|       "cmyk", | ||||
|       "cmyka", | ||||
|       "cr2", | ||||
|       "crw", | ||||
|       "cur", | ||||
|       "cut", | ||||
|       "dcm", | ||||
|       "dcr", | ||||
|       "dcx", | ||||
|       "dng", | ||||
|       "dpx", | ||||
|       "epdf", | ||||
|       "epi", | ||||
|       "eps", | ||||
|       "epsf", | ||||
|       "epsi", | ||||
|       "ept", | ||||
|       "ept2", | ||||
|       "ept3", | ||||
|       "erf", | ||||
|       "exif", | ||||
|       "fax", | ||||
|       "file", | ||||
|       "fits", | ||||
|       "fractal", | ||||
|       "ftp", | ||||
|       "g", | ||||
|       "gif", | ||||
|       "gif87", | ||||
|       "gradient", | ||||
|       "gray", | ||||
|       "graya", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "hrz", | ||||
|       "http", | ||||
|       "icb", | ||||
|       "icc", | ||||
|       "icm", | ||||
|       "ico", | ||||
|       "icon", | ||||
|       "identity", | ||||
|       "image", | ||||
|       "iptc", | ||||
|       "iptctext", | ||||
|       "iptcwtext", | ||||
|       "jbg", | ||||
|       "jbig", | ||||
|       "jng", | ||||
|       "jnx", | ||||
|       "jpeg", | ||||
|       "jpg", | ||||
|       "k", | ||||
|       "k25", | ||||
|       "kdc", | ||||
|       "label", | ||||
|       "m", | ||||
|       "mac", | ||||
|       "map", | ||||
|       "mat", | ||||
|       "mef", | ||||
|       "miff", | ||||
|       "mng", | ||||
|       "mono", | ||||
|       "mpc", | ||||
|       "mrw", | ||||
|       "msl", | ||||
|       "mtv", | ||||
|       "mvg", | ||||
|       "nef", | ||||
|       "null", | ||||
|       "o", | ||||
|       "orf", | ||||
|       "otb", | ||||
|       "p7", | ||||
|       "pal", | ||||
|       "palm", | ||||
|       "pam", | ||||
|       "pbm", | ||||
|       "pcd", | ||||
|       "pcds", | ||||
|       "pct", | ||||
|       "pcx", | ||||
|       "pdb", | ||||
|       "pdf", | ||||
|       "pef", | ||||
|       "pfa", | ||||
|       "pfb", | ||||
|       "pgm", | ||||
|       "picon", | ||||
|       "pict", | ||||
|       "pix", | ||||
|       "plasma", | ||||
|       "png", | ||||
|       "png00", | ||||
|       "png24", | ||||
|       "png32", | ||||
|       "png48", | ||||
|       "png64", | ||||
|       "png8", | ||||
|       "pnm", | ||||
|       "ppm", | ||||
|       "ps", | ||||
|       "ptif", | ||||
|       "pwp", | ||||
|       "r", | ||||
|       "raf", | ||||
|       "ras", | ||||
|       "rgb", | ||||
|       "rgba", | ||||
|       "rla", | ||||
|       "rle", | ||||
|       "sct", | ||||
|       "sfw", | ||||
|       "sgi", | ||||
|       "sr2", | ||||
|       "srf", | ||||
|       "stegano", | ||||
|       "sun", | ||||
|       "svg", | ||||
|       "svgz", | ||||
|       "text", | ||||
|       "tga", | ||||
|       "tif", | ||||
|       "tiff", | ||||
|       "tile", | ||||
|       "tim", | ||||
|       "topol", | ||||
|       "ttf", | ||||
|       "txt", | ||||
|       "uyvy", | ||||
|       "vda", | ||||
|       "vicar", | ||||
|       "vid", | ||||
|       "viff", | ||||
|       "vst", | ||||
|       "wbmp", | ||||
|       "webp", | ||||
|       "wmf", | ||||
|       "wpg", | ||||
|       "x3f", | ||||
|       "xbm", | ||||
|       "xc", | ||||
|       "xcf", | ||||
|       "xmp", | ||||
|       "xpm", | ||||
|       "xv", | ||||
|       "xwd", | ||||
|       "y", | ||||
|       "yuv", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     image: [ | ||||
|       "8bim", | ||||
|       "8bimtext", | ||||
|       "8bimwtext", | ||||
|       "app1", | ||||
|       "app1jpeg", | ||||
|       "art", | ||||
|       "avs", | ||||
|       "b", | ||||
|       "bie", | ||||
|       "bigtiff", | ||||
|       "bmp", | ||||
|       "bmp2", | ||||
|       "bmp3", | ||||
|       "brf", | ||||
|       "c", | ||||
|       "cals", | ||||
|       "cin", | ||||
|       "cmyk", | ||||
|       "cmyka", | ||||
|       "dcx", | ||||
|       "dpx", | ||||
|       "epdf", | ||||
|       "epi", | ||||
|       "eps", | ||||
|       "eps2", | ||||
|       "eps3", | ||||
|       "epsf", | ||||
|       "epsi", | ||||
|       "ept", | ||||
|       "ept2", | ||||
|       "ept3", | ||||
|       "exif", | ||||
|       "fax", | ||||
|       "fits", | ||||
|       "g", | ||||
|       "gif", | ||||
|       "gif87", | ||||
|       "gray", | ||||
|       "graya", | ||||
|       "histogram", | ||||
|       "html", | ||||
|       "icb", | ||||
|       "icc", | ||||
|       "icm", | ||||
|       "info", | ||||
|       "iptc", | ||||
|       "iptctext", | ||||
|       "iptcwtext", | ||||
|       "isobrl", | ||||
|       "isobrl6", | ||||
|       "jbg", | ||||
|       "jbig", | ||||
|       "jng", | ||||
|       "jpeg", | ||||
|       "k", | ||||
|       "m", | ||||
|       "m2v", | ||||
|       "map", | ||||
|       "mat", | ||||
|       "matte", | ||||
|       "miff", | ||||
|       "mng", | ||||
|       "mono", | ||||
|       "mpc", | ||||
|       "mpeg", | ||||
|       "mpg", | ||||
|       "msl", | ||||
|       "mtv", | ||||
|       "mvg", | ||||
|       "null", | ||||
|       "o", | ||||
|       "otb", | ||||
|       "p7", | ||||
|       "pal", | ||||
|       "pam", | ||||
|       "pbm", | ||||
|       "pcd", | ||||
|       "pcds", | ||||
|       "pcl", | ||||
|       "pct", | ||||
|       "pcx", | ||||
|       "pdb", | ||||
|       "pdf", | ||||
|       "pgm", | ||||
|       "picon", | ||||
|       "pict", | ||||
|       "png", | ||||
|       "png00", | ||||
|       "png24", | ||||
|       "png32", | ||||
|       "png48", | ||||
|       "png64", | ||||
|       "png8", | ||||
|       "pnm", | ||||
|       "ppm", | ||||
|       "preview", | ||||
|       "ps", | ||||
|       "ps2", | ||||
|       "ps3", | ||||
|       "ptif", | ||||
|       "r", | ||||
|       "ras", | ||||
|       "rgb", | ||||
|       "rgba", | ||||
|       "sgi", | ||||
|       "shtml", | ||||
|       "sun", | ||||
|       "text", | ||||
|       "tga", | ||||
|       "tiff", | ||||
|       "txt", | ||||
|       "ubrl", | ||||
|       "ubrl6", | ||||
|       "uil", | ||||
|       "uyvy", | ||||
|       "vda", | ||||
|       "vicar", | ||||
|       "vid", | ||||
|       "viff", | ||||
|       "vst", | ||||
|       "wbmp", | ||||
|       "webp", | ||||
|       "x", | ||||
|       "xbm", | ||||
|       "xmp", | ||||
|       "xpm", | ||||
|       "xv", | ||||
|       "xwd", | ||||
|       "y", | ||||
|       "yuv", | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| 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( | ||||
|       "gm", | ||||
|       ["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"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
|   | ||||
							
								
								
									
										59
									
								
								src/converters/inkscape.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,59 @@ | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     images: ["svg", "pdf", "eps", "ps", "wmf", "emf", "png"], | ||||
|   }, | ||||
|   to: { | ||||
|     images: [ | ||||
|       "dxf", | ||||
|       "emf", | ||||
|       "eps", | ||||
|       "fxg", | ||||
|       "gpl", | ||||
|       "hpgl", | ||||
|       "html", | ||||
|       "odg", | ||||
|       "pdf", | ||||
|       "png", | ||||
|       "pov", | ||||
|       "ps", | ||||
|       "sif", | ||||
|       "svg", | ||||
|       "svgz", | ||||
|       "tex", | ||||
|       "wmf", | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| 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( | ||||
|       "inkscape", | ||||
|       [filePath, "-o", targetPath], | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|         } | ||||
|  | ||||
|         if (stdout) { | ||||
|           console.log(`stdout: ${stdout}`); | ||||
|         } | ||||
|  | ||||
|         if (stderr) { | ||||
|           console.error(`stderr: ${stderr}`); | ||||
|         } | ||||
|  | ||||
|         resolve("Done"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										53
									
								
								src/converters/libheif.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,53 @@ | ||||
| import { execFile } from "child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     images: [ | ||||
|       "avci", | ||||
|       "avcs", | ||||
|       "avif", | ||||
|       "h264", | ||||
|       "heic", | ||||
|       "heics", | ||||
|       "heif", | ||||
|       "heifs", | ||||
|       "hif", | ||||
|       "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"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
| @@ -1,71 +1,71 @@ | ||||
| import { exec } from "node:child_process"; | ||||
|  | ||||
| // declare possible conversions | ||||
| export const properties = { | ||||
|   from: { | ||||
|     jxl: ["jxl"], | ||||
|     images: [ | ||||
|       "apng", | ||||
|       "exr", | ||||
|       "gif", | ||||
|       "jpeg", | ||||
|       "pam", | ||||
|       "pfm", | ||||
|       "pgm", | ||||
|       "pgx", | ||||
|       "png", | ||||
|       "ppm", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     jxl: [ | ||||
|       "apng", | ||||
|       "exr", | ||||
|       "gif", | ||||
|       "jpeg", | ||||
|       "pam", | ||||
|       "pfm", | ||||
|       "pgm", | ||||
|       "pgx", | ||||
|       "png", | ||||
|       "ppm", | ||||
|     ], | ||||
|     images: ["jxl"], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||||
|   options?: any, | ||||
| ): Promise<string> { | ||||
|   let tool = ""; | ||||
|   if (fileType === "jxl") { | ||||
|     tool = "djxl"; | ||||
|   } | ||||
|  | ||||
|   if (convertTo === "jxl") { | ||||
|     tool = "cjxl"; | ||||
|   } | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     exec(`${tool} "${filePath}" "${targetPath}"`, (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|  | ||||
|       if (stdout) { | ||||
|         console.log(`stdout: ${stdout}`); | ||||
|       } | ||||
|  | ||||
|       if (stderr) { | ||||
|         console.error(`stderr: ${stderr}`); | ||||
|       } | ||||
|  | ||||
|       resolve("success"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| // declare possible conversions | ||||
| export const properties = { | ||||
|   from: { | ||||
|     jxl: ["jxl"], | ||||
|     images: [ | ||||
|       "apng", | ||||
|       "exr", | ||||
|       "gif", | ||||
|       "jpeg", | ||||
|       "pam", | ||||
|       "pfm", | ||||
|       "pgm", | ||||
|       "pgx", | ||||
|       "png", | ||||
|       "ppm", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     jxl: [ | ||||
|       "apng", | ||||
|       "exr", | ||||
|       "gif", | ||||
|       "jpeg", | ||||
|       "pam", | ||||
|       "pfm", | ||||
|       "pgm", | ||||
|       "pgx", | ||||
|       "png", | ||||
|       "ppm", | ||||
|     ], | ||||
|     images: ["jxl"], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   let tool = ""; | ||||
|   if (fileType === "jxl") { | ||||
|     tool = "djxl"; | ||||
|   } | ||||
|  | ||||
|   if (convertTo === "jxl") { | ||||
|     tool = "cjxl"; | ||||
|   } | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     execFile(tool, [filePath, targetPath], (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|  | ||||
|       if (stdout) { | ||||
|         console.log(`stdout: ${stdout}`); | ||||
|       } | ||||
|  | ||||
|       if (stderr) { | ||||
|         console.error(`stderr: ${stderr}`); | ||||
|       } | ||||
|  | ||||
|       resolve("Done"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -1,72 +1,72 @@ | ||||
| import { convert as convertImage, properties as propertiesImage } from "./vips"; | ||||
|  | ||||
| import { | ||||
|   convert as convertPandoc, | ||||
|   properties as propertiesPandoc, | ||||
| } from "./pandoc"; | ||||
|  | ||||
| import { | ||||
|   convert as convertFFmpeg, | ||||
|   properties as propertiesFFmpeg, | ||||
| } from "./ffmpeg"; | ||||
|  | ||||
| import { | ||||
|   convert as convertGraphicsmagick, | ||||
|   properties as propertiesGraphicsmagick, | ||||
| } from "./graphicsmagick"; | ||||
|  | ||||
| import { | ||||
|   convert as convertPdflatex, | ||||
|   properties as propertiesPdflatex, | ||||
| } from "./pdflatex"; | ||||
|  | ||||
| import { | ||||
|   convert as convertLibjxl, | ||||
|   properties as propertiesLibjxl, | ||||
| } from "./libjxl"; | ||||
|  | ||||
| import { normalizeFiletype } from "../helpers/normalizeFiletype"; | ||||
| import { convert as convertassimp, properties as propertiesassimp } from "./assimp"; | ||||
| import { convert as convertFFmpeg, properties as propertiesFFmpeg } from "./ffmpeg"; | ||||
| import { convert as convertGraphicsmagick, properties as propertiesGraphicsmagick } from "./graphicsmagick"; | ||||
| import { convert as convertInkscape, properties as propertiesInkscape } from "./inkscape"; | ||||
| import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libjxl"; | ||||
| import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc"; | ||||
| 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 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 | ||||
|  | ||||
| const properties: { | ||||
|   [key: string]: { | ||||
| const properties: Record< | ||||
|   string, | ||||
|   { | ||||
|     properties: { | ||||
|       from: { [key: string]: string[] }; | ||||
|       to: { [key: string]: string[] }; | ||||
|       options?: { | ||||
|         [key: string]: { | ||||
|           [key: string]: { | ||||
|       from: Record<string, string[]>; | ||||
|       to: Record<string, string[]>; | ||||
|       options?: Record< | ||||
|         string, | ||||
|         Record< | ||||
|           string, | ||||
|           { | ||||
|             description: string; | ||||
|             type: string; | ||||
|             default: number; | ||||
|           }; | ||||
|         }; | ||||
|       }; | ||||
|           } | ||||
|         > | ||||
|       >; | ||||
|     }; | ||||
|     converter: ( | ||||
|       filePath: string, | ||||
|       fileType: string, | ||||
|       convertTo: string, | ||||
|       targetPath: string, | ||||
|       // biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||||
|       options?: any, | ||||
|       // biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||||
|     ) => any; | ||||
|   }; | ||||
| } = { | ||||
|  | ||||
|       options?: unknown, | ||||
|     ) => unknown; | ||||
|   } | ||||
| > = { | ||||
|   libjxl: { | ||||
|     properties: propertiesLibjxl, | ||||
|     converter: convertLibjxl, | ||||
|   }, | ||||
|   resvg: { | ||||
|     properties: propertiesresvg, | ||||
|     converter: convertresvg, | ||||
|   }, | ||||
|   vips: { | ||||
|     properties: propertiesImage, | ||||
|     converter: convertImage, | ||||
|   }, | ||||
|   pdflatex: { | ||||
|     properties: propertiesPdflatex, | ||||
|     converter: convertPdflatex, | ||||
|   libheif: { | ||||
|     properties: propertiesLibheif, | ||||
|     converter: convertLibheif, | ||||
|   }, | ||||
|   xelatex: { | ||||
|     properties: propertiesxelatex, | ||||
|     converter: convertxelatex, | ||||
|   }, | ||||
|   // calibre: { | ||||
|   //   properties: propertiesCalibre, | ||||
|   //   converter: convertCalibre, | ||||
|   // }, | ||||
|   pandoc: { | ||||
|     properties: propertiesPandoc, | ||||
|     converter: convertPandoc, | ||||
| @@ -75,33 +75,40 @@ const properties: { | ||||
|     properties: propertiesGraphicsmagick, | ||||
|     converter: convertGraphicsmagick, | ||||
|   }, | ||||
|   inkscape: { | ||||
|     properties: propertiesInkscape, | ||||
|     converter: convertInkscape, | ||||
|   }, | ||||
|   assimp: { | ||||
|     properties: propertiesassimp, | ||||
|     converter: convertassimp, | ||||
|   }, | ||||
|   ffmpeg: { | ||||
|     properties: propertiesFFmpeg, | ||||
|     converter: convertFFmpeg, | ||||
|   }, | ||||
|   potrace: { | ||||
|     properties: propertiespotrace, | ||||
|     converter: convertpotrace, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export async function mainConverter( | ||||
|   inputFilePath: string, | ||||
|   fileTypeOriginal: string, | ||||
|   // biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||||
|   convertTo: any, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||||
|   options?: any, | ||||
|   options?: unknown, | ||||
|   converterName?: string, | ||||
| ) { | ||||
|   const fileType = normalizeFiletype(fileTypeOriginal); | ||||
|  | ||||
|   // biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||||
|   let converterFunc: any; | ||||
|   // let converterName = converterName; | ||||
|   let converterFunc: typeof properties["libjxl"]["converter"] | undefined; | ||||
|  | ||||
|   if (converterName) { | ||||
|     converterFunc = properties[converterName]?.converter; | ||||
|   } else { | ||||
|     // Iterate over each converter in properties | ||||
|     // biome-ignore lint/style/noParameterAssign: <explanation> | ||||
|     for (converterName in properties) { | ||||
|       const converterObj = properties[converterName]; | ||||
|  | ||||
| @@ -111,9 +118,8 @@ export async function mainConverter( | ||||
|  | ||||
|       for (const key in converterObj.properties.from) { | ||||
|         if ( | ||||
|           // HOW?? | ||||
|           converterObj.properties.from[key].includes(fileType) && | ||||
|           converterObj.properties.to[key].includes(convertTo) | ||||
|           converterObj?.properties?.from[key]?.includes(fileType) && | ||||
|           converterObj?.properties?.to[key]?.includes(convertTo) | ||||
|         ) { | ||||
|           converterFunc = converterObj.converter; | ||||
|           break; | ||||
| @@ -130,7 +136,7 @@ export async function mainConverter( | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     await converterFunc( | ||||
|     const result = await converterFunc( | ||||
|       inputFilePath, | ||||
|       fileType, | ||||
|       convertTo, | ||||
| @@ -140,7 +146,13 @@ export async function mainConverter( | ||||
|  | ||||
|     console.log( | ||||
|       `Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`, | ||||
|       result, | ||||
|     ); | ||||
|  | ||||
|     if (typeof result === "string") { | ||||
|       return result; | ||||
|     } | ||||
|  | ||||
|     return "Done"; | ||||
|   } catch (error) { | ||||
|     console.error( | ||||
| @@ -151,7 +163,7 @@ export async function mainConverter( | ||||
|   } | ||||
| } | ||||
|  | ||||
| const possibleTargets: { [key: string]: { [key: string]: string[] } } = {}; | ||||
| const possibleTargets: Record<string, Record<string, string[]>> = {}; | ||||
|  | ||||
| for (const converterName in properties) { | ||||
|   const converterProperties = properties[converterName]?.properties; | ||||
| @@ -176,9 +188,7 @@ for (const converterName in properties) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const getPossibleTargets = ( | ||||
|   from: string, | ||||
| ): { [key: string]: string[] } => { | ||||
| export const getPossibleTargets = (from: string): Record<string, string[]> => { | ||||
|   const fromClean = normalizeFiletype(from); | ||||
|  | ||||
|   return possibleTargets[fromClean] || {}; | ||||
| @@ -202,11 +212,12 @@ for (const converterName in properties) { | ||||
| } | ||||
| possibleInputs.sort(); | ||||
|  | ||||
| export const getPossibleInputs = () => { | ||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
| const getPossibleInputs = () => { | ||||
|   return possibleInputs; | ||||
| }; | ||||
|  | ||||
| const allTargets: { [key: string]: string[] } = {}; | ||||
| const allTargets: Record<string, string[]> = {}; | ||||
|  | ||||
| for (const converterName in properties) { | ||||
|   const converterProperties = properties[converterName]?.properties; | ||||
| @@ -217,9 +228,9 @@ for (const converterName in properties) { | ||||
|  | ||||
|   for (const key in converterProperties.to) { | ||||
|     if (allTargets[converterName]) { | ||||
|       allTargets[converterName].push(...converterProperties.to[key]); | ||||
|       allTargets[converterName].push(...(converterProperties.to[key] || [])); | ||||
|     } else { | ||||
|       allTargets[converterName] = converterProperties.to[key]; | ||||
|       allTargets[converterName] = converterProperties.to[key] || []; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -228,7 +239,7 @@ export const getAllTargets = () => { | ||||
|   return allTargets; | ||||
| }; | ||||
|  | ||||
| const allInputs: { [key: string]: string[] } = {}; | ||||
| const allInputs: Record<string, string[]> = {}; | ||||
| for (const converterName in properties) { | ||||
|   const converterProperties = properties[converterName]?.properties; | ||||
|  | ||||
| @@ -238,9 +249,9 @@ for (const converterName in properties) { | ||||
|  | ||||
|   for (const key in converterProperties.from) { | ||||
|     if (allInputs[converterName]) { | ||||
|       allInputs[converterName].push(...converterProperties.from[key]); | ||||
|       allInputs[converterName].push(...(converterProperties.from[key] || [])); | ||||
|     } else { | ||||
|       allInputs[converterName] = converterProperties.from[key]; | ||||
|       allInputs[converterName] = converterProperties.from[key] || []; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -273,4 +284,4 @@ export const getAllInputs = (converter: string) => { | ||||
| // } | ||||
|  | ||||
| // // print the number of unique Inputs and Outputs | ||||
| // console.log(`Unique Formats: ${uniqueFormats.size}`); | ||||
| // console.log(`Unique Formats: ${uniqueFormats.size}`); | ||||
| @@ -1,119 +0,0 @@ | ||||
| import sharp from "sharp"; | ||||
| import type { FormatEnum } from "sharp"; | ||||
|  | ||||
| // declare possible conversions | ||||
| export const properties = { | ||||
|   from: { | ||||
|     images: [ | ||||
|       "avif", | ||||
|       "bif", | ||||
|       "csv", | ||||
|       "exr", | ||||
|       "fits", | ||||
|       "gif", | ||||
|       "hdr.gz", | ||||
|       "hdr", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "img.gz", | ||||
|       "img", | ||||
|       "j2c", | ||||
|       "j2k", | ||||
|       "jp2", | ||||
|       "jpeg", | ||||
|       "jpx", | ||||
|       "jxl", | ||||
|       "mat", | ||||
|       "mrxs", | ||||
|       "ndpi", | ||||
|       "nia.gz", | ||||
|       "nia", | ||||
|       "nii.gz", | ||||
|       "nii", | ||||
|       "pdf", | ||||
|       "pfm", | ||||
|       "pgm", | ||||
|       "pic", | ||||
|       "png", | ||||
|       "ppm", | ||||
|       "raw", | ||||
|       "scn", | ||||
|       "svg", | ||||
|       "svs", | ||||
|       "svslide", | ||||
|       "szi", | ||||
|       "tif", | ||||
|       "tiff", | ||||
|       "v", | ||||
|       "vips", | ||||
|       "vms", | ||||
|       "vmu", | ||||
|       "webp", | ||||
|       "zip", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     images: [ | ||||
|       "avif", | ||||
|       "dzi", | ||||
|       "fits", | ||||
|       "gif", | ||||
|       "hdr.gz", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "img.gz", | ||||
|       "j2c", | ||||
|       "j2k", | ||||
|       "jp2", | ||||
|       "jpeg", | ||||
|       "jpx", | ||||
|       "jxl", | ||||
|       "mat", | ||||
|       "nia.gz", | ||||
|       "nia", | ||||
|       "nii.gz", | ||||
|       "nii", | ||||
|       "png", | ||||
|       "tiff", | ||||
|       "vips", | ||||
|       "webp", | ||||
|     ], | ||||
|   }, | ||||
|   options: { | ||||
|     svg: { | ||||
|       scale: { | ||||
|         description: "Scale the image up or down", | ||||
|         type: "number", | ||||
|         default: 1, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export async function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: keyof FormatEnum, | ||||
|   targetPath: string, | ||||
|   // biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||||
|   options?: any, | ||||
| ) { | ||||
|   if (fileType === "svg") { | ||||
|     const scale = options.scale || 1; | ||||
|     const metadata = await sharp(filePath).metadata(); | ||||
|  | ||||
|     if (!metadata || !metadata.width || !metadata.height) { | ||||
|       throw new Error("Could not get metadata from image"); | ||||
|     } | ||||
|  | ||||
|     const newWidth = Math.round(metadata.width * scale); | ||||
|     const newHeight = Math.round(metadata.height * scale); | ||||
|  | ||||
|     return await sharp(filePath) | ||||
|       .resize(newWidth, newHeight) | ||||
|       .toFormat(convertTo) | ||||
|       .toFile(targetPath); | ||||
|   } | ||||
|  | ||||
|   return await sharp(filePath).toFormat(convertTo).toFile(targetPath); | ||||
| } | ||||
| @@ -1,150 +1,162 @@ | ||||
| import { exec } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     text: [ | ||||
|       "textile", | ||||
|       "tikiwiki", | ||||
|       "tsv", | ||||
|       "twiki", | ||||
|       "typst", | ||||
|       "vimwiki", | ||||
|       "biblatex", | ||||
|       "bibtex", | ||||
|       "bits", | ||||
|       "commonmark", | ||||
|       "commonmark_x", | ||||
|       "creole", | ||||
|       "csljson", | ||||
|       "csv", | ||||
|       "djot", | ||||
|       "docbook", | ||||
|       "docx", | ||||
|       "dokuwiki", | ||||
|       "endnotexml", | ||||
|       "epub", | ||||
|       "fb2", | ||||
|       "gfm", | ||||
|       "haddock", | ||||
|       "html", | ||||
|       "ipynb", | ||||
|       "jats", | ||||
|       "jira", | ||||
|       "json", | ||||
|       "latex", | ||||
|       "man", | ||||
|       "markdown", | ||||
|       "markdown_mmd", | ||||
|       "markdown_phpextra", | ||||
|       "markdown_strict", | ||||
|       "mediawiki", | ||||
|       "muse", | ||||
|       "pandoc native", | ||||
|       "opml", | ||||
|       "org", | ||||
|       "ris", | ||||
|       "rst", | ||||
|       "rtf", | ||||
|       "t2t", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     text: [ | ||||
|       "tei", | ||||
|       "texinfo", | ||||
|       "textile", | ||||
|       "typst", | ||||
|       "xwiki", | ||||
|       "zimwiki", | ||||
|       "asciidoc", | ||||
|       "asciidoc_legacy", | ||||
|       "asciidoctor", | ||||
|       "beamer", | ||||
|       "biblatex", | ||||
|       "bibtex", | ||||
|       "chunkedhtml", | ||||
|       "commonmark", | ||||
|       "commonmark_x", | ||||
|       "context", | ||||
|       "csljson", | ||||
|       "djot", | ||||
|       "docbook", | ||||
|       "docbook4", | ||||
|       "docbook5", | ||||
|       "docx", | ||||
|       "dokuwiki", | ||||
|       "dzslides", | ||||
|       "epub", | ||||
|       "epub2", | ||||
|       "epub3", | ||||
|       "fb2", | ||||
|       "gfm", | ||||
|       "haddock", | ||||
|       "html", | ||||
|       "html4", | ||||
|       "html5", | ||||
|       "icml", | ||||
|       "ipynb", | ||||
|       "jats", | ||||
|       "jats_archiving", | ||||
|       "jats_articleauthoring", | ||||
|       "jats_publishing", | ||||
|       "jira", | ||||
|       "json", | ||||
|       "latex", | ||||
|       "man", | ||||
|       "markdown", | ||||
|       "markdown_mmd", | ||||
|       "markdown_phpextra", | ||||
|       "markdown_strict", | ||||
|       "markua", | ||||
|       "mediawiki", | ||||
|       "ms", | ||||
|       "muse", | ||||
|       "pandoc native", | ||||
|       "odt", | ||||
|       "opendocument", | ||||
|       "opml", | ||||
|       "org", | ||||
|       "pdf", | ||||
|       "plain", | ||||
|       "pptx", | ||||
|       "revealjs", | ||||
|       "rst", | ||||
|       "rtf", | ||||
|       "s5", | ||||
|       "slideous", | ||||
|       "slidy", | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||||
|   options?: any, | ||||
| ): Promise<string> { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     exec( | ||||
|       `pandoc "${filePath}" -f ${fileType} -t ${convertTo} -o "${targetPath}"`, | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|         } | ||||
|  | ||||
|         if (stdout) { | ||||
|           console.log(`stdout: ${stdout}`); | ||||
|         } | ||||
|  | ||||
|         if (stderr) { | ||||
|           console.error(`stderr: ${stderr}`); | ||||
|         } | ||||
|  | ||||
|         resolve("success"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     text: [ | ||||
|       "textile", | ||||
|       "tikiwiki", | ||||
|       "tsv", | ||||
|       "twiki", | ||||
|       "typst", | ||||
|       "vimwiki", | ||||
|       "biblatex", | ||||
|       "bibtex", | ||||
|       "bits", | ||||
|       "commonmark", | ||||
|       "commonmark_x", | ||||
|       "creole", | ||||
|       "csljson", | ||||
|       "csv", | ||||
|       "djot", | ||||
|       "docbook", | ||||
|       "docx", | ||||
|       "dokuwiki", | ||||
|       "endnotexml", | ||||
|       "epub", | ||||
|       "fb2", | ||||
|       "gfm", | ||||
|       "haddock", | ||||
|       "html", | ||||
|       "ipynb", | ||||
|       "jats", | ||||
|       "jira", | ||||
|       "json", | ||||
|       "latex", | ||||
|       "man", | ||||
|       "markdown", | ||||
|       "markdown_mmd", | ||||
|       "markdown_phpextra", | ||||
|       "markdown_strict", | ||||
|       "mediawiki", | ||||
|       "muse", | ||||
|       "pandoc native", | ||||
|       "opml", | ||||
|       "org", | ||||
|       "ris", | ||||
|       "rst", | ||||
|       "rtf", | ||||
|       "t2t", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     text: [ | ||||
|       "tei", | ||||
|       "texinfo", | ||||
|       "textile", | ||||
|       "typst", | ||||
|       "xwiki", | ||||
|       "zimwiki", | ||||
|       "asciidoc", | ||||
|       "asciidoc_legacy", | ||||
|       "asciidoctor", | ||||
|       "beamer", | ||||
|       "biblatex", | ||||
|       "bibtex", | ||||
|       "chunkedhtml", | ||||
|       "commonmark", | ||||
|       "commonmark_x", | ||||
|       "context", | ||||
|       "csljson", | ||||
|       "djot", | ||||
|       "docbook", | ||||
|       "docbook4", | ||||
|       "docbook5", | ||||
|       "docx", | ||||
|       "dokuwiki", | ||||
|       "dzslides", | ||||
|       "epub", | ||||
|       "epub2", | ||||
|       "epub3", | ||||
|       "fb2", | ||||
|       "gfm", | ||||
|       "haddock", | ||||
|       "html", | ||||
|       "html4", | ||||
|       "html5", | ||||
|       "icml", | ||||
|       "ipynb", | ||||
|       "jats", | ||||
|       "jats_archiving", | ||||
|       "jats_articleauthoring", | ||||
|       "jats_publishing", | ||||
|       "jira", | ||||
|       "json", | ||||
|       "latex", | ||||
|       "man", | ||||
|       "markdown", | ||||
|       "markdown_mmd", | ||||
|       "markdown_phpextra", | ||||
|       "markdown_strict", | ||||
|       "markua", | ||||
|       "mediawiki", | ||||
|       "ms", | ||||
|       "muse", | ||||
|       "pandoc native", | ||||
|       "odt", | ||||
|       "opendocument", | ||||
|       "opml", | ||||
|       "org", | ||||
|       "pdf", | ||||
|       "plain", | ||||
|       "pptx", | ||||
|       "revealjs", | ||||
|       "rst", | ||||
|       "rtf", | ||||
|       "s5", | ||||
|       "slideous", | ||||
|       "slidy", | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   // set xelatex here | ||||
|   const xelatex = ["pdf", "latex"]; | ||||
|  | ||||
|   // Build arguments array | ||||
|   const args: string[] = []; | ||||
|  | ||||
|   if (xelatex.includes(convertTo)) { | ||||
|     args.push("--pdf-engine=xelatex"); | ||||
|   } | ||||
|  | ||||
|   args.push(filePath); | ||||
|   args.push("-f", fileType); | ||||
|   args.push("-t", convertTo); | ||||
|   args.push("-o", targetPath); | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     execFile("pandoc", args, (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|  | ||||
|       if (stdout) { | ||||
|         console.log(`stdout: ${stdout}`); | ||||
|       } | ||||
|  | ||||
|       if (stderr) { | ||||
|         console.error(`stderr: ${stderr}`); | ||||
|       } | ||||
|  | ||||
|       resolve("Done"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|   | ||||
							
								
								
									
										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"); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
							
								
								
									
										37
									
								
								src/converters/resvg.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,37 @@ | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     images: ["svg"], | ||||
|   }, | ||||
|   to: { | ||||
|     images: ["png"], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| 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("resvg", [filePath, targetPath], (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|  | ||||
|       if (stdout) { | ||||
|         console.log(`stdout: ${stdout}`); | ||||
|       } | ||||
|  | ||||
|       if (stderr) { | ||||
|         console.error(`stderr: ${stderr}`); | ||||
|       } | ||||
|  | ||||
|       resolve("Done"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| @@ -1,134 +1,142 @@ | ||||
| import { exec } from "node:child_process"; | ||||
|  | ||||
| // declare possible conversions | ||||
| export const properties = { | ||||
|   from: { | ||||
|     images: [ | ||||
|       "avif", | ||||
|       "bif", | ||||
|       "csv", | ||||
|       "exr", | ||||
|       "fits", | ||||
|       "gif", | ||||
|       "hdr.gz", | ||||
|       "hdr", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "img.gz", | ||||
|       "img", | ||||
|       "j2c", | ||||
|       "j2k", | ||||
|       "jp2", | ||||
|       "jpeg", | ||||
|       "jpx", | ||||
|       "jxl", | ||||
|       "mat", | ||||
|       "mrxs", | ||||
|       "ndpi", | ||||
|       "nia.gz", | ||||
|       "nia", | ||||
|       "nii.gz", | ||||
|       "nii", | ||||
|       "pdf", | ||||
|       "pfm", | ||||
|       "pgm", | ||||
|       "pic", | ||||
|       "png", | ||||
|       "ppm", | ||||
|       "raw", | ||||
|       "scn", | ||||
|       "svg", | ||||
|       "svs", | ||||
|       "svslide", | ||||
|       "szi", | ||||
|       "tif", | ||||
|       "tiff", | ||||
|       "v", | ||||
|       "vips", | ||||
|       "vms", | ||||
|       "vmu", | ||||
|       "webp", | ||||
|       "zip", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     images: [ | ||||
|       "avif", | ||||
|       "dzi", | ||||
|       "fits", | ||||
|       "gif", | ||||
|       "hdr.gz", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "img.gz", | ||||
|       "j2c", | ||||
|       "j2k", | ||||
|       "jp2", | ||||
|       "jpeg", | ||||
|       "jpx", | ||||
|       "jxl", | ||||
|       "mat", | ||||
|       "nia.gz", | ||||
|       "nia", | ||||
|       "nii.gz", | ||||
|       "nii", | ||||
|       "png", | ||||
|       "tiff", | ||||
|       "vips", | ||||
|       "webp", | ||||
|     ], | ||||
|   }, | ||||
|   options: { | ||||
|     svg: { | ||||
|       scale: { | ||||
|         description: "Scale the image up or down", | ||||
|         type: "number", | ||||
|         default: 1, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||||
|   options?: any, | ||||
| ): Promise<string> { | ||||
|   // if (fileType === "svg") { | ||||
|   //   const scale = options.scale || 1; | ||||
|   //   const metadata = await sharp(filePath).metadata(); | ||||
|  | ||||
|   //   if (!metadata || !metadata.width || !metadata.height) { | ||||
|   //     throw new Error("Could not get metadata from image"); | ||||
|   //   } | ||||
|  | ||||
|   //   const newWidth = Math.round(metadata.width * scale); | ||||
|   //   const newHeight = Math.round(metadata.height * scale); | ||||
|  | ||||
|   //   return await sharp(filePath) | ||||
|   //     .resize(newWidth, newHeight) | ||||
|   //     .toFormat(convertTo) | ||||
|   //     .toFile(targetPath); | ||||
|   // } | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     exec(`vips copy "${filePath}" "${targetPath}"`, (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|  | ||||
|       if (stdout) { | ||||
|         console.log(`stdout: ${stdout}`); | ||||
|       } | ||||
|  | ||||
|       if (stderr) { | ||||
|         console.error(`stderr: ${stderr}`); | ||||
|       } | ||||
|  | ||||
|       resolve("success"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| // declare possible conversions | ||||
| export const properties = { | ||||
|   from: { | ||||
|     images: [ | ||||
|       "avif", | ||||
|       "bif", | ||||
|       "csv", | ||||
|       "exr", | ||||
|       "fits", | ||||
|       "gif", | ||||
|       "hdr.gz", | ||||
|       "hdr", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "img.gz", | ||||
|       "img", | ||||
|       "j2c", | ||||
|       "j2k", | ||||
|       "jp2", | ||||
|       "jpeg", | ||||
|       "jpx", | ||||
|       "jxl", | ||||
|       "mat", | ||||
|       "mrxs", | ||||
|       "ndpi", | ||||
|       "nia.gz", | ||||
|       "nia", | ||||
|       "nii.gz", | ||||
|       "nii", | ||||
|       "pdf", | ||||
|       "pfm", | ||||
|       "pgm", | ||||
|       "pic", | ||||
|       "png", | ||||
|       "ppm", | ||||
|       "raw", | ||||
|       "scn", | ||||
|       "svg", | ||||
|       "svs", | ||||
|       "svslide", | ||||
|       "szi", | ||||
|       "tif", | ||||
|       "tiff", | ||||
|       "v", | ||||
|       "vips", | ||||
|       "vms", | ||||
|       "vmu", | ||||
|       "webp", | ||||
|       "zip", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     images: [ | ||||
|       "avif", | ||||
|       "dzi", | ||||
|       "fits", | ||||
|       "gif", | ||||
|       "hdr.gz", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "img.gz", | ||||
|       "j2c", | ||||
|       "j2k", | ||||
|       "jp2", | ||||
|       "jpeg", | ||||
|       "jpx", | ||||
|       "jxl", | ||||
|       "mat", | ||||
|       "nia.gz", | ||||
|       "nia", | ||||
|       "nii.gz", | ||||
|       "nii", | ||||
|       "png", | ||||
|       "tiff", | ||||
|       "vips", | ||||
|       "webp", | ||||
|     ], | ||||
|   }, | ||||
|   options: { | ||||
|     svg: { | ||||
|       scale: { | ||||
|         description: "Scale the image up or down", | ||||
|         type: "number", | ||||
|         default: 1, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   // if (fileType === "svg") { | ||||
|   //   const scale = options.scale || 1; | ||||
|   //   const metadata = await sharp(filePath).metadata(); | ||||
|  | ||||
|   //   if (!metadata || !metadata.width || !metadata.height) { | ||||
|   //     throw new Error("Could not get metadata from image"); | ||||
|   //   } | ||||
|  | ||||
|   //   const newWidth = Math.round(metadata.width * scale); | ||||
|   //   const newHeight = Math.round(metadata.height * scale); | ||||
|  | ||||
|   //   return await sharp(filePath) | ||||
|   //     .resize(newWidth, newHeight) | ||||
|   //     .toFormat(convertTo) | ||||
|   //     .toFile(targetPath); | ||||
|   // } | ||||
|   let action = "copy"; | ||||
|   if (fileType === "pdf") { | ||||
|     action = "pdfload"; | ||||
|   } | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     execFile( | ||||
|       "vips", | ||||
|       [action, filePath, targetPath], | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|         } | ||||
|  | ||||
|         if (stdout) { | ||||
|           console.log(`stdout: ${stdout}`); | ||||
|         } | ||||
|  | ||||
|         if (stderr) { | ||||
|           console.error(`stderr: ${stderr}`); | ||||
|         } | ||||
|  | ||||
|         resolve("Done"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -1,46 +1,53 @@ | ||||
| import { exec } from "node:child_process"; | ||||
| 
 | ||||
| export const properties = { | ||||
|   from: { | ||||
|     text: ["tex", "latex"], | ||||
|   }, | ||||
|   to: { | ||||
|     text: ["pdf"], | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | ||||
|   options?: any, | ||||
| ): Promise<string> { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     // const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
 | ||||
|     const outputPath = targetPath | ||||
|       .split("/") | ||||
|       .slice(0, -1) | ||||
|       .join("/") | ||||
|       .replace("./", ""); | ||||
|     exec( | ||||
|       `pdflatex -interaction=nonstopmode -output-directory="${outputPath}" "${filePath}"`, | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|         } | ||||
| 
 | ||||
|         if (stdout) { | ||||
|           console.log(`stdout: ${stdout}`); | ||||
|         } | ||||
| 
 | ||||
|         if (stderr) { | ||||
|           console.error(`stderr: ${stderr}`); | ||||
|         } | ||||
| 
 | ||||
|         resolve("success"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
| 
 | ||||
| export const properties = { | ||||
|   from: { | ||||
|     text: ["tex", "latex"], | ||||
|   }, | ||||
|   to: { | ||||
|     text: ["pdf"], | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| 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) => { | ||||
|     // const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
 | ||||
|     const outputPath = targetPath | ||||
|       .split("/") | ||||
|       .slice(0, -1) | ||||
|       .join("/") | ||||
|       .replace("./", ""); | ||||
| 
 | ||||
|     execFile( | ||||
|       "latexmk", | ||||
|       [ | ||||
|         "-xelatex", | ||||
|         "-interaction=nonstopmode", | ||||
|         `-output-directory=${outputPath}`, | ||||
|         filePath, | ||||
|       ], | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|         } | ||||
| 
 | ||||
|         if (stdout) { | ||||
|           console.log(`stdout: ${stdout}`); | ||||
|         } | ||||
| 
 | ||||
|         if (stderr) { | ||||
|           console.error(`stderr: ${stderr}`); | ||||
|         } | ||||
| 
 | ||||
|         resolve("Done"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
| @@ -1,31 +1,37 @@ | ||||
| export const normalizeFiletype = (filetype: string): string => { | ||||
|   const lowercaseFiletype = filetype.toLowerCase(); | ||||
|  | ||||
|   switch (lowercaseFiletype) { | ||||
|     case "jpg": | ||||
|       return "jpeg"; | ||||
|     case "htm": | ||||
|       return "html"; | ||||
|     case "tex": | ||||
|       return "latex"; | ||||
|     case "md": | ||||
|       return "markdown"; | ||||
|     default: | ||||
|       return lowercaseFiletype; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export const normalizeOutputFiletype = (filetype: string): string => { | ||||
|   const lowercaseFiletype = filetype.toLowerCase(); | ||||
|  | ||||
|   switch (lowercaseFiletype) { | ||||
|     case "jpeg": | ||||
|       return "jpg"; | ||||
|     case "latex": | ||||
|       return "tex"; | ||||
|     case "markdown": | ||||
|       return "md"; | ||||
|     default: | ||||
|       return lowercaseFiletype; | ||||
|   } | ||||
| }; | ||||
| export const normalizeFiletype = (filetype: string): string => { | ||||
|   const lowercaseFiletype = filetype.toLowerCase(); | ||||
|  | ||||
|   switch (lowercaseFiletype) { | ||||
|     case "jfif": | ||||
|     case "jpg": | ||||
|       return "jpeg"; | ||||
|     case "htm": | ||||
|       return "html"; | ||||
|     case "tex": | ||||
|       return "latex"; | ||||
|     case "md": | ||||
|       return "markdown"; | ||||
|     case "unknown": | ||||
|       return "m4a"; | ||||
|     default: | ||||
|       return lowercaseFiletype; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export const normalizeOutputFiletype = (filetype: string): string => { | ||||
|   const lowercaseFiletype = filetype.toLowerCase(); | ||||
|  | ||||
|   switch (lowercaseFiletype) { | ||||
|     case "jpeg": | ||||
|       return "jpg"; | ||||
|     case "latex": | ||||
|       return "tex"; | ||||
|     case "markdown_phpextra": | ||||
|     case "markdown_strict": | ||||
|     case "markdown_mmd": | ||||
|     case "markdown": | ||||
|       return "md"; | ||||
|     default: | ||||
|       return lowercaseFiletype; | ||||
|   } | ||||
| }; | ||||
|   | ||||
							
								
								
									
										146
									
								
								src/helpers/printVersions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,146 @@ | ||||
| import { exec } from "node:child_process"; | ||||
| import { version } from "../../package.json"; | ||||
|  | ||||
| console.log(`ConvertX v${version}`); | ||||
|  | ||||
| if (process.env.NODE_ENV === "production") { | ||||
|   exec("cat /etc/os-release", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Not running on docker, this is not supported."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split('PRETTY_NAME="')[1]?.split('"')[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("pandoc -v", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Pandoc is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("ffmpeg -version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("FFmpeg is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("vips -v", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Vips is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("gm version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("GraphicsMagick is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("inkscape --version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Inkscape is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("djxl --version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("libjxl-tools is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("xelatex -version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Tex Live with XeTeX is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("resvg -V", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("resvg is not installed"); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(`resvg v${stdout.split("\n")[0]}`); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("assimp version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("assimp is not installed"); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(`assimp ${stdout.split("\n")[5]}`); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("ebook-convert --version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("ebook-convert (calibre) is not installed"); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   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("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"); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(`Bun v${stdout.split("\n")[0]}`); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/helpers/tailwind.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | ||||
| import tailwind from "@tailwindcss/postcss"; | ||||
| import postcss from "postcss"; | ||||
|  | ||||
| export const generateTailwind = async () => { | ||||
|   const result = await Bun.file("./src/main.css") | ||||
|     .text() | ||||
|     .then((sourceText) => { | ||||
|       return postcss([tailwind]).process(sourceText, { | ||||
|         from: "./src/main.css", | ||||
|         to: "./public/generated.css", | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|   return result; | ||||
| }; | ||||
							
								
								
									
										1421
									
								
								src/index.tsx
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										78
									
								
								src/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,78 @@ | ||||
| @import "tailwindcss"; | ||||
|  | ||||
| @plugin 'tailwind-scrollbar'; | ||||
|  | ||||
| @theme { | ||||
|   --color-contrast: rgba(var(--contrast)); | ||||
|   --color-neutral-900: rgba(var(--neutral-900)); | ||||
|   --color-neutral-800: rgba(var(--neutral-800)); | ||||
|   --color-neutral-700: rgba(var(--neutral-700)); | ||||
|   --color-neutral-600: rgba(var(--neutral-600)); | ||||
|   --color-neutral-500: rgba(var(--neutral-500)); | ||||
|   --color-neutral-400: rgba(var(--neutral-400)); | ||||
|   --color-neutral-300: rgba(var(--neutral-300)); | ||||
|   --color-neutral-200: rgba(var(--neutral-200)); | ||||
|   --color-neutral-100: rgba(var(--neutral-100)); | ||||
|   --color-accent-600: rgba(var(--accent-600)); | ||||
|   --color-accent-500: rgba(var(--accent-500)); | ||||
|   --color-accent-400: rgba(var(--accent-400)); | ||||
| } | ||||
|  | ||||
| /* | ||||
|   The default border color has changed to `currentColor` in Tailwind CSS v4, | ||||
|   so we've added these compatibility styles to make sure everything still | ||||
|   looks the same as it did with Tailwind CSS v3. | ||||
|  | ||||
|   If we ever want to remove these styles, we need to add an explicit border | ||||
|   color utility to any element that depends on these defaults. | ||||
| */ | ||||
| @layer base { | ||||
|   *, | ||||
|   ::after, | ||||
|   ::before, | ||||
|   ::backdrop, | ||||
|   ::file-selector-button { | ||||
|     border-color: var(--color-gray-200, currentColor); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @utility article { | ||||
|   @apply px-2 sm:px-4 py-4 mb-4 bg-neutral-800/40 w-full mx-auto max-w-4xl rounded-sm; | ||||
| } | ||||
|  | ||||
| @utility btn-primary { | ||||
|   @apply bg-accent-500 text-contrast rounded-sm p-2 sm:p-4 hover:bg-accent-400 cursor-pointer transition-colors; | ||||
| } | ||||
|  | ||||
| :root { | ||||
|   --contrast: 255, 255, 255; | ||||
|   --neutral-900: 243, 244, 246; | ||||
|   --neutral-800: 229, 231, 235; | ||||
|   --neutral-700: 209, 213, 219; | ||||
|   --neutral-600: 156, 163, 175; | ||||
|   --neutral-500: 180, 180, 180; | ||||
|   --neutral-400: 75, 85, 99; | ||||
|   --neutral-300: 55, 65, 81; | ||||
|   --neutral-200: 31, 41, 55; | ||||
|   --neutral-100: 17, 24, 39; | ||||
|   --accent-400: 132, 204, 22; | ||||
|   --accent-500: 101, 163, 13; | ||||
|   --accent-600: 77, 124, 15; | ||||
| } | ||||
|  | ||||
| @media (prefers-color-scheme: dark) { | ||||
|   :root { | ||||
|     --contrast: 0, 0, 0; | ||||
|     --neutral-900: 17, 24, 39; | ||||
|     --neutral-800: 31, 41, 55; | ||||
|     --neutral-700: 55, 65, 81; | ||||
|     --neutral-600: 75, 85, 99; | ||||
|     --neutral-500: 107, 114, 128; | ||||
|     --neutral-300: 209, 213, 219; | ||||
|     --neutral-400: 156, 163, 175; | ||||
|     --neutral-200: 229, 231, 235; | ||||
|     --accent-600: 101, 163, 13; | ||||
|     --accent-500: 132, 204, 22; | ||||
|     --accent-400: 163, 230, 53; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										4
									
								
								src/public/pico.lime.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,125 +0,0 @@ | ||||
| // Select the file input element | ||||
| const fileInput = document.querySelector('input[type="file"]'); | ||||
| const fileNames = []; | ||||
| let fileType; | ||||
|  | ||||
| const selectContainer = document.querySelector("form > article"); | ||||
|  | ||||
| // const convertFromSelect = document.querySelector("select[name='convert_from']"); | ||||
|  | ||||
| // Add a 'change' event listener to the file input element | ||||
| fileInput.addEventListener("change", (e) => { | ||||
|   // console.log(e.target.files); | ||||
|   // 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>${(file.size / 1024).toFixed(2)} kB</td> | ||||
|       <td><a class="secondary" onclick="deleteRow(this)" style="cursor: pointer">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("/conversions", { | ||||
|         method: "POST", | ||||
|         body: JSON.stringify({ fileType: fileType }), | ||||
|         headers: { | ||||
|           "Content-Type": "application/json", | ||||
|         }, | ||||
|       }) | ||||
|         .then((res) => res.text()) | ||||
|         .then((html) => { | ||||
|           selectContainer.innerHTML = html; | ||||
|         }) | ||||
|         .catch((err) => console.log(err)); | ||||
|     } | ||||
|  | ||||
|     // Append the row to the file-list table | ||||
|     fileList.appendChild(row); | ||||
|  | ||||
|     // Append the file to the hidden input | ||||
|     fileNames.push(file.name); | ||||
|   } | ||||
|  | ||||
|   uploadFiles(files); | ||||
| }); | ||||
|  | ||||
| const setTitle = () => { | ||||
|   const title = document.querySelector("h1"); | ||||
|   title.textContent = `Convert ${fileType ? `.${fileType}` : ""}`; | ||||
| }; | ||||
|  | ||||
| // Add a onclick for the delete button | ||||
| const deleteRow = (target) => { | ||||
|   const filename = target.parentElement.parentElement.children[0].textContent; | ||||
|   const row = target.parentElement.parentElement; | ||||
|   row.remove(); | ||||
|  | ||||
|   // remove from fileNames | ||||
|   const index = fileNames.indexOf(filename); | ||||
|   fileNames.splice(index, 1); | ||||
|  | ||||
|   // if fileNames is empty, reset fileType | ||||
|   if (fileNames.length === 0) { | ||||
|     fileType = null; | ||||
|     fileInput.removeAttribute("accept"); | ||||
|     setTitle(); | ||||
|   } | ||||
|  | ||||
|   fetch("/delete", { | ||||
|     method: "POST", | ||||
|     body: JSON.stringify({ filename: filename }), | ||||
|     headers: { | ||||
|       "Content-Type": "application/json", | ||||
|     }, | ||||
|   }) | ||||
|     .then((res) => res.json()) | ||||
|     .then((data) => { | ||||
|       console.log(data); | ||||
|     }) | ||||
|     .catch((err) => console.log(err)); | ||||
| }; | ||||
|  | ||||
| const uploadFiles = (files) => { | ||||
|   const formData = new FormData(); | ||||
|  | ||||
|   for (const file of files) { | ||||
|     formData.append("file", file, file.name); | ||||
|   } | ||||
|  | ||||
|   fetch("/upload", { | ||||
|     method: "POST", | ||||
|     body: formData, | ||||
|   }) | ||||
|     .then((res) => res.json()) | ||||
|     .then((data) => { | ||||
|       console.log(data); | ||||
|     }) | ||||
|     .catch((err) => console.log(err)); | ||||
| }; | ||||
|  | ||||
| const formConvert = document.querySelector("form[action='/convert']"); | ||||
|  | ||||
| formConvert.addEventListener("submit", (e) => { | ||||
|   const hiddenInput = document.querySelector("input[name='file_names']"); | ||||
|   hiddenInput.value = JSON.stringify(fileNames); | ||||
| }); | ||||
| @@ -1,15 +0,0 @@ | ||||
| div.icon { | ||||
|   height: 100px; | ||||
|   width: 100px; | ||||
| } | ||||
|  | ||||
| button[type="submit"] { | ||||
|   width: 50% | ||||
| } | ||||
|  | ||||
| div.center { | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
| } | ||||
| @@ -1,8 +1,8 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "lib": ["ESNext"], | ||||
|     "module": "esnext", | ||||
|     "target": "esnext", | ||||
|     "module": "ESNext", | ||||
|     "target": "ES2021", | ||||
|     "moduleResolution": "bundler", | ||||
|     "moduleDetection": "force", | ||||
|     "allowImportingTsExtensions": true, | ||||
| @@ -17,9 +17,6 @@ | ||||
|     "allowSyntheticDefaultImports": true, | ||||
|     "forceConsistentCasingInFileNames": true, | ||||
|     "allowJs": true, | ||||
|     "types": [ | ||||
|       "bun-types" // add Bun global | ||||
|     ], | ||||
|     // non bun init | ||||
|     "plugins": [{ "name": "@kitajs/ts-html-plugin" }], | ||||
|     "noUncheckedIndexedAccess": true, | ||||
| @@ -30,4 +27,4 @@ | ||||
|     "noImplicitOverride": true | ||||
|     // "noImplicitReturns": true | ||||
|   } | ||||
| } | ||||
| } | ||||
|   | ||||