Compare commits
222 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 |
@@ -1,7 +0,0 @@
|
||||
version = 1
|
||||
|
||||
[[analyzers]]
|
||||
name = "javascript"
|
||||
|
||||
[analyzers.meta]
|
||||
environment = ["nodejs"]
|
||||
@@ -1,16 +1,19 @@
|
||||
node_modules
|
||||
Dockerfile*
|
||||
docker-compose*
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
LICENSE
|
||||
.vscode
|
||||
Makefile
|
||||
helm-charts
|
||||
.env
|
||||
.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
|
||||
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']
|
||||
13
.github/workflows/docker-publish.yml
vendored
@@ -19,6 +19,7 @@ env:
|
||||
REGISTRY: ghcr.io
|
||||
# github.repository as <account>/<repo>
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
DOCKERHUB_USERNAME: c4illin
|
||||
|
||||
|
||||
jobs:
|
||||
@@ -46,13 +47,22 @@ jobs:
|
||||
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 }}
|
||||
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
|
||||
@@ -67,3 +77,4 @@ jobs:
|
||||
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
|
||||
2
.gitignore
vendored
@@ -48,4 +48,4 @@ package-lock.json
|
||||
/data
|
||||
/Bruno
|
||||
/tsconfig.tsbuildinfo
|
||||
/src/public/generated.css
|
||||
/public/generated.css
|
||||
|
||||
131
CHANGELOG.md
@@ -1,5 +1,136 @@
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
35
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM oven/bun:1.1.29-alpine AS base
|
||||
FROM oven/bun:1.2.2-alpine AS base
|
||||
LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX"
|
||||
WORKDIR /app
|
||||
|
||||
@@ -6,12 +6,12 @@ WORKDIR /app
|
||||
# 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
|
||||
|
||||
FROM base AS builder
|
||||
@@ -20,22 +20,18 @@ ENV PATH=/root/.cargo/bin:$PATH
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
RUN cargo install resvg
|
||||
|
||||
# copy node_modules from temp directory
|
||||
# then copy all (non-ignored) project files into the image
|
||||
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
|
||||
# ENV NODE_ENV=production
|
||||
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 \
|
||||
@@ -49,16 +45,25 @@ RUN apk --no-cache add \
|
||||
vips-tools \
|
||||
vips-poppler \
|
||||
vips-jxl \
|
||||
libjxl-tools
|
||||
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=builder /root/.cargo/bin/resvg /usr/local/bin/resvg
|
||||
COPY --from=prerelease /app/src/public/generated.css /app/src/public/
|
||||
# COPY --from=prerelease /app/src/index.tsx /app/src/
|
||||
# COPY --from=prerelease /app/package.json .
|
||||
COPY --from=prerelease /app/public/generated.css /app/public/
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000/tcp
|
||||
|
||||
73
README.md
@@ -1,13 +1,17 @@
|
||||

|
||||
|
||||
# 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 831 different formats. Written with TypeScript, Bun and Elysia.
|
||||
A self-hosted online file converter. Supports over a thousand different formats. Written with TypeScript, Bun and Elysia.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -23,10 +27,16 @@ A self-hosted online file converter. Supports 831 different formats. Written wit
|
||||
| [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 | 166 | 133 |
|
||||
| [FFmpeg](https://ffmpeg.org/) | Video | ~473 | ~280 |
|
||||
| [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 -->
|
||||
|
||||
@@ -34,6 +44,9 @@ Any missing converter? Open an issue or pull request!
|
||||
|
||||
## Deployment
|
||||
|
||||
> [!WARNING]
|
||||
> If you can't login, make sure you are accessing the service over localhost or https otherwise set HTTP_ALLOWED=true
|
||||
|
||||
```yml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
@@ -43,14 +56,10 @@ services:
|
||||
restart: unless-stopped
|
||||
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
|
||||
- ALLOW_UNAUTHENTICATED=false # allows anyone to use the service without logging in, 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
|
||||
environment:
|
||||
- JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() if unset
|
||||
volumes:
|
||||
- convertx:/app/data
|
||||
- ./data:/app/data
|
||||
```
|
||||
|
||||
or
|
||||
@@ -63,9 +72,46 @@ Then visit `http://localhost:3000` in your browser and create your account. Don'
|
||||
|
||||
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
|
||||
|
||||
Tutorial in french: https://belginux.com/installer-convertx-avec-docker/
|
||||
> [!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
|
||||
|
||||
@@ -81,6 +127,7 @@ Tutorial in french: https://belginux.com/installer-convertx-avec-docker/
|
||||
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
|
||||
@@ -96,7 +143,7 @@ Pull requests are welcome! See below and open issues for the list of todos.
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/C4illin/ConvertX/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=C4illin/ConvertX" />
|
||||
<img src="https://contrib.rocks/image?repo=C4illin/ConvertX" alt="Image with all contributors"/>
|
||||
</a>
|
||||
|
||||
## Star History
|
||||
|
||||
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.
|
||||
@@ -11,5 +11,8 @@ services:
|
||||
- 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
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
|
||||
import { fixupPluginRules } from "@eslint/compat";
|
||||
import js from "@eslint/js";
|
||||
import deprecationPlugin from "eslint-plugin-deprecation";
|
||||
import importPlugin from "eslint-plugin-import";
|
||||
import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
|
||||
import tailwind from "eslint-plugin-tailwindcss";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default tseslint.config(
|
||||
js.configs.recommended,
|
||||
importPlugin.flatConfigs.recommended,
|
||||
comments.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...tailwind.configs["flat/recommended"],
|
||||
{
|
||||
plugins: {
|
||||
"@typescript-eslint": tseslint.plugin,
|
||||
deprecation: fixupPluginRules(deprecationPlugin),
|
||||
import: fixupPluginRules(importPlugin),
|
||||
"simple-import-sort": simpleImportSortPlugin,
|
||||
},
|
||||
ignores: ["**/node_modules/**", "**/public/**"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
globals: {
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
files: ["**/*.{js,mjs,cjs}"],
|
||||
rules: {
|
||||
"tailwindcss/no-custom-classname": [
|
||||
"error",
|
||||
{
|
||||
config: "./tailwind.config.js",
|
||||
whitelist: [
|
||||
"select_container",
|
||||
"convert_to_popup",
|
||||
"convert_to_group",
|
||||
"target",
|
||||
"convert_to_target",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
80
package.json
@@ -1,22 +1,24 @@
|
||||
{
|
||||
"name": "convertx-frontend",
|
||||
"version": "0.6.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",
|
||||
"build": "postcss ./src/main.css -o ./src/public/generated.css",
|
||||
"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:biome": "biome lint --error-on-warnings ./src"
|
||||
"lint:eslint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@elysiajs/cookie": "^0.8.0",
|
||||
"@elysiajs/html": "1.0.2",
|
||||
"@elysiajs/jwt": "^1.1.1",
|
||||
"@elysiajs/static": "1.0.3",
|
||||
"elysia": "^1.1.16"
|
||||
"@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",
|
||||
@@ -24,44 +26,30 @@
|
||||
"start": "bun run src/index.tsx"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.2",
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.0",
|
||||
"@eslint/compat": "^1.1.1",
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
|
||||
"@kitajs/ts-html-plugin": "^4.1.0",
|
||||
"@picocss/pico": "^2.0.6",
|
||||
"@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.1.10",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/bun": "^1.2.13",
|
||||
"@types/eslint-plugin-tailwindcss": "^3.17.0",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/node": "^22.6.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
||||
"@typescript-eslint/parser": "^8.7.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cssnano": "^7.0.6",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-deprecation": "^3.0.0",
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
"eslint-plugin-isaacscript": "^4.0.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"@types/node": "^22.15.17",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"cssnano": "^7.0.7",
|
||||
"eslint": "^9.26.0",
|
||||
"eslint-plugin-readable-tailwind": "^2.1.1",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-tailwindcss": "^3.17.4",
|
||||
"globals": "^15.9.0",
|
||||
"knip": "^5.30.5",
|
||||
"npm-run-all2": "^6.2.3",
|
||||
"postcss": "^8.4.47",
|
||||
"postcss-cli": "^11.0.0",
|
||||
"postcss-lightningcss": "^1.0.1",
|
||||
"prettier": "^3.3.3",
|
||||
"tailwind-scrollbar": "^3.1.0",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript-eslint": "^8.7.0"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@biomejs/biome"
|
||||
]
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// eslint-disable-next-line no-undef
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
// eslint-disable-next-line no-undef
|
||||
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
|
||||
}
|
||||
}
|
||||
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())
|
||||
@@ -1,17 +1,74 @@
|
||||
// Select the file input element
|
||||
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", (e) => {
|
||||
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 = () => {
|
||||
@@ -57,6 +114,10 @@ const updateSearchBar = () => {
|
||||
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("");
|
||||
};
|
||||
}
|
||||
@@ -68,6 +129,12 @@ const updateSearchBar = () => {
|
||||
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
|
||||
@@ -87,64 +154,12 @@ const updateSearchBar = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 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();
|
||||
console.log("fileType", fileType);
|
||||
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;
|
||||
updateSearchBar();
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
handleFile(file);
|
||||
}
|
||||
|
||||
// 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 = () => {
|
||||
@@ -153,6 +168,7 @@ const setTitle = () => {
|
||||
};
|
||||
|
||||
// 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;
|
||||
@@ -162,48 +178,75 @@ const deleteRow = (target) => {
|
||||
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("/delete", {
|
||||
fetch(`${webroot}/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();
|
||||
const uploadFile = (file) => {
|
||||
convertButton.disabled = true;
|
||||
convertButton.textContent = "Uploading...";
|
||||
pendingFiles += 1;
|
||||
|
||||
for (const file of files) {
|
||||
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";
|
||||
}
|
||||
|
||||
fetch("/upload", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
//Remove the progress bar when upload is done
|
||||
let progressbar = file.htmlRow.getElementsByTagName("progress");
|
||||
progressbar[0].parentElement.remove();
|
||||
console.log(data);
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
};
|
||||
|
||||
const formConvert = document.querySelector("form[action='/convert']");
|
||||
xhr.upload.onprogress = (e) => {
|
||||
let sent = e.loaded;
|
||||
let total = e.total;
|
||||
console.log(`upload progress (${file.name}):`, (100 * sent) / total);
|
||||
|
||||
formConvert.addEventListener("submit", (e) => {
|
||||
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);
|
||||
});
|
||||
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
]
|
||||
"config:recommended",
|
||||
":disableDependencyDashboard"
|
||||
],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"automerge": true
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,40 @@
|
||||
import { Html } from "@elysiajs/html";
|
||||
|
||||
export const BaseHtml = ({
|
||||
children,
|
||||
title = "ConvertX",
|
||||
}: { children: JSX.Element; title?: string }) => (
|
||||
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="/generated.css" />
|
||||
<link rel="stylesheet" href={`${webroot}/generated.css`} />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/apple-touch-icon.png"
|
||||
href={`${webroot}/apple-touch-icon.png`}
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicon-32x32.png"
|
||||
href={`${webroot}/favicon-32x32.png`}
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicon-16x16.png"
|
||||
href={`${webroot}/favicon-16x16.png`}
|
||||
/>
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="manifest" href={`${webroot}/site.webmanifest`} />
|
||||
</head>
|
||||
<body class="w-full bg-gray-900 text-gray-200">{children}</body>
|
||||
<body class="w-full bg-neutral-900 text-neutral-200">{children}</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,48 @@
|
||||
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 class="flex gap-4">
|
||||
{!hideHistory && (
|
||||
<li>
|
||||
<a
|
||||
class="text-lime-600 transition-all hover:text-lime-500 hover:underline"
|
||||
href="/history">
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/history`}
|
||||
>
|
||||
History
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{!allowUnauthenticated ? (
|
||||
<li>
|
||||
<a
|
||||
class="text-lime-600 transition-all hover:text-lime-500 hover:underline"
|
||||
href="/logoff">
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/logoff`}
|
||||
>
|
||||
Logout
|
||||
</a>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
);
|
||||
} else {
|
||||
@@ -27,16 +50,24 @@ export const Header = ({
|
||||
<ul class="flex gap-4">
|
||||
<li>
|
||||
<a
|
||||
class="text-lime-600 transition-all hover:text-lime-500 hover:underline"
|
||||
href="/login">
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/login`}
|
||||
>
|
||||
Login
|
||||
</a>
|
||||
</li>
|
||||
{accountRegistration ? (
|
||||
<li>
|
||||
<a
|
||||
class="text-lime-600 transition-all hover:text-lime-500 hover:underline"
|
||||
href="/register">
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/register`}
|
||||
>
|
||||
Register
|
||||
</a>
|
||||
</li>
|
||||
@@ -47,11 +78,11 @@ export const Header = ({
|
||||
|
||||
return (
|
||||
<header class="w-full p-4">
|
||||
<nav class="mx-auto flex max-w-4xl justify-between rounded bg-gray-900 p-4">
|
||||
<nav class="mx-auto flex max-w-4xl justify-between rounded-sm bg-neutral-900 p-4">
|
||||
<ul>
|
||||
<li>
|
||||
<strong>
|
||||
<a href="/">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,4 +1,4 @@
|
||||
import { exec } from "node:child_process";
|
||||
import { execFile } from "node:child_process";
|
||||
|
||||
// This could be done dynamically by running `ffmpeg -formats` and parsing the output
|
||||
export const properties = {
|
||||
@@ -6,6 +6,7 @@ export const properties = {
|
||||
muxer: [
|
||||
"264",
|
||||
"265",
|
||||
"266",
|
||||
"302",
|
||||
"3dostr",
|
||||
"3g2",
|
||||
@@ -18,6 +19,7 @@ export const properties = {
|
||||
"aac",
|
||||
"aax",
|
||||
"ac3",
|
||||
"ac4",
|
||||
"ace",
|
||||
"acm",
|
||||
"act",
|
||||
@@ -48,7 +50,6 @@ export const properties = {
|
||||
"apng",
|
||||
"aptx",
|
||||
"aptxhd",
|
||||
"aptx_hd",
|
||||
"aqt",
|
||||
"aqtitle",
|
||||
"argo_asf",
|
||||
@@ -63,10 +64,12 @@ export const properties = {
|
||||
"av1",
|
||||
"avc",
|
||||
"avi",
|
||||
"avif",
|
||||
"avr",
|
||||
"avs",
|
||||
"avs2",
|
||||
"avs3",
|
||||
"awb",
|
||||
"bcstm",
|
||||
"bethsoftvid",
|
||||
"bfi",
|
||||
@@ -75,8 +78,10 @@ export const properties = {
|
||||
"bink",
|
||||
"binka",
|
||||
"bit",
|
||||
"bmp_pipe",
|
||||
"bitpacked",
|
||||
"bmv",
|
||||
"bmp",
|
||||
"bonk",
|
||||
"boa",
|
||||
"brender_pix",
|
||||
"brstm",
|
||||
@@ -93,7 +98,7 @@ export const properties = {
|
||||
"codec2",
|
||||
"codec2raw",
|
||||
"concat",
|
||||
"cri_pipe",
|
||||
"cri",
|
||||
"dash",
|
||||
"dat",
|
||||
"data",
|
||||
@@ -101,8 +106,9 @@ export const properties = {
|
||||
"dav",
|
||||
"dbm",
|
||||
"dcstr",
|
||||
"dds_pipe",
|
||||
"dds",
|
||||
"derf",
|
||||
"dfpwm",
|
||||
"dfa",
|
||||
"dhav",
|
||||
"dif",
|
||||
@@ -131,6 +137,8 @@ export const properties = {
|
||||
"exr_pipe",
|
||||
"f32be",
|
||||
"f32le",
|
||||
"ec3",
|
||||
"evc",
|
||||
"f4v",
|
||||
"f64be",
|
||||
"f64le",
|
||||
@@ -157,13 +165,13 @@ export const properties = {
|
||||
"gdv",
|
||||
"genh",
|
||||
"gif",
|
||||
"gif_pipe",
|
||||
"gsm",
|
||||
"gxf",
|
||||
"h261",
|
||||
"h263",
|
||||
"h264",
|
||||
"h265",
|
||||
"h266",
|
||||
"h26l",
|
||||
"hca",
|
||||
"hcom",
|
||||
@@ -180,7 +188,6 @@ export const properties = {
|
||||
"ifv",
|
||||
"ilbc",
|
||||
"image2",
|
||||
"image2pipe",
|
||||
"imf",
|
||||
"imx",
|
||||
"ingenient",
|
||||
@@ -197,21 +204,17 @@ export const properties = {
|
||||
"ivr",
|
||||
"j2b",
|
||||
"j2k",
|
||||
"j2k_pipe",
|
||||
"jack",
|
||||
"jacosub",
|
||||
"jpegls_pipe",
|
||||
"jpeg_pipe",
|
||||
"jv",
|
||||
"jpegls",
|
||||
"jpeg",
|
||||
"jxl",
|
||||
"kmsgrab",
|
||||
"kux",
|
||||
"kvag",
|
||||
"lavfi",
|
||||
"libcdio",
|
||||
"libdc1394",
|
||||
"libgme",
|
||||
"libopenmpt",
|
||||
"live_flv",
|
||||
"laf",
|
||||
"lmlm4",
|
||||
"loas",
|
||||
"lrc",
|
||||
@@ -224,16 +227,13 @@ export const properties = {
|
||||
"m4b",
|
||||
"m4v",
|
||||
"mac",
|
||||
"matroska",
|
||||
"mca",
|
||||
"mcc",
|
||||
"mdl",
|
||||
"med",
|
||||
"mgsts",
|
||||
"microdvd",
|
||||
"mj2",
|
||||
"mjpeg",
|
||||
"mjpeg_2000",
|
||||
"mjpg",
|
||||
"mk3d",
|
||||
"mka",
|
||||
@@ -257,9 +257,6 @@ export const properties = {
|
||||
"mpc",
|
||||
"mpc8",
|
||||
"mpeg",
|
||||
"mpegts",
|
||||
"mpegtsraw",
|
||||
"mpegvideo",
|
||||
"mpg",
|
||||
"mpjpeg",
|
||||
"mpl2",
|
||||
@@ -294,25 +291,27 @@ export const properties = {
|
||||
"okt",
|
||||
"oma",
|
||||
"omg",
|
||||
"opus",
|
||||
"openal",
|
||||
"oss",
|
||||
"osq",
|
||||
"paf",
|
||||
"pam_pipe",
|
||||
"pbm_pipe",
|
||||
"pcx_pipe",
|
||||
"pgmyuv_pipe",
|
||||
"pgm_pipe",
|
||||
"pgx_pipe",
|
||||
"photocd_pipe",
|
||||
"pictor_pipe",
|
||||
"pdv",
|
||||
"pam",
|
||||
"pbm",
|
||||
"pcx",
|
||||
"pgmyuv",
|
||||
"pgm",
|
||||
"pgx",
|
||||
"photocd",
|
||||
"pictor",
|
||||
"pjs",
|
||||
"plm",
|
||||
"pmp",
|
||||
"png_pipe",
|
||||
"png",
|
||||
"ppm",
|
||||
"ppm_pipe",
|
||||
"pp_bnk",
|
||||
"psd_pipe",
|
||||
"pp",
|
||||
"psd",
|
||||
"psm",
|
||||
"psp",
|
||||
"psxstr",
|
||||
@@ -323,7 +322,7 @@ export const properties = {
|
||||
"pvf",
|
||||
"qcif",
|
||||
"qcp",
|
||||
"qdraw_pipe",
|
||||
"qdraw",
|
||||
"r3d",
|
||||
"rawvideo",
|
||||
"rco",
|
||||
@@ -335,6 +334,7 @@ export const properties = {
|
||||
"rm",
|
||||
"roq",
|
||||
"rpl",
|
||||
"rka",
|
||||
"rsd",
|
||||
"rso",
|
||||
"rt",
|
||||
@@ -355,6 +355,7 @@ export const properties = {
|
||||
"sbc",
|
||||
"sbg",
|
||||
"scc",
|
||||
"sdns",
|
||||
"sdp",
|
||||
"sdr2",
|
||||
"sds",
|
||||
@@ -364,10 +365,9 @@ export const properties = {
|
||||
"sfx",
|
||||
"sfx2",
|
||||
"sga",
|
||||
"sgi_pipe",
|
||||
"sgi",
|
||||
"shn",
|
||||
"siff",
|
||||
"simbiosis_imx",
|
||||
"sln",
|
||||
"smi",
|
||||
"smjpeg",
|
||||
@@ -389,12 +389,9 @@ export const properties = {
|
||||
"stp",
|
||||
"str",
|
||||
"sub",
|
||||
"subviewer",
|
||||
"subviewer1",
|
||||
"sunrast_pipe",
|
||||
"sup",
|
||||
"svag",
|
||||
"svg_pipe",
|
||||
"svg",
|
||||
"svs",
|
||||
"sw",
|
||||
"swf",
|
||||
@@ -404,7 +401,8 @@ export const properties = {
|
||||
"thd",
|
||||
"thp",
|
||||
"tiertexseq",
|
||||
"tiff_pipe",
|
||||
"tif",
|
||||
"tiff",
|
||||
"tmv",
|
||||
"truehd",
|
||||
"tta",
|
||||
@@ -424,6 +422,7 @@ export const properties = {
|
||||
"ul",
|
||||
"ult",
|
||||
"umx",
|
||||
"usm",
|
||||
"uw",
|
||||
"v",
|
||||
"v210",
|
||||
@@ -447,12 +446,14 @@ export const properties = {
|
||||
"vql",
|
||||
"vt",
|
||||
"vtt",
|
||||
"vvc",
|
||||
"w64",
|
||||
"wa",
|
||||
"wav",
|
||||
"way",
|
||||
"wc3movie",
|
||||
"webm",
|
||||
"webm_dash_manifest",
|
||||
"webp_pipe",
|
||||
"webp",
|
||||
"webvtt",
|
||||
"wow",
|
||||
"wsaud",
|
||||
@@ -464,32 +465,31 @@ export const properties = {
|
||||
"x11grab",
|
||||
"xa",
|
||||
"xbin",
|
||||
"xbm_pipe",
|
||||
"xl",
|
||||
"xm",
|
||||
"xmd",
|
||||
"xmv",
|
||||
"xpk",
|
||||
"xpm_pipe",
|
||||
"xvag",
|
||||
"xwd_pipe",
|
||||
"xwma",
|
||||
"y4m",
|
||||
"yop",
|
||||
"yuv",
|
||||
"yuv10",
|
||||
"yuv4mpegpipe",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
muxer: [
|
||||
"264",
|
||||
"265",
|
||||
"266",
|
||||
"302",
|
||||
"3g2",
|
||||
"3gp",
|
||||
"a64",
|
||||
"aac",
|
||||
"ac3",
|
||||
"ac4",
|
||||
"adts",
|
||||
"adx",
|
||||
"afc",
|
||||
@@ -497,43 +497,32 @@ export const properties = {
|
||||
"aifc",
|
||||
"aiff",
|
||||
"al",
|
||||
"alaw",
|
||||
"alp",
|
||||
"alsa",
|
||||
"amr",
|
||||
"amv",
|
||||
"apm",
|
||||
"apng",
|
||||
"aptx",
|
||||
"aptxhd",
|
||||
"aptx_hd",
|
||||
"argo_asf",
|
||||
"asf",
|
||||
"asf_stream",
|
||||
"ass",
|
||||
"ast",
|
||||
"au",
|
||||
"aud",
|
||||
"av1",
|
||||
"avi",
|
||||
"avm2",
|
||||
"avif",
|
||||
"avs",
|
||||
"avs2",
|
||||
"avs3",
|
||||
"bit",
|
||||
"bmp",
|
||||
"c2",
|
||||
"caca",
|
||||
"caf",
|
||||
"cavs",
|
||||
"cavsvideo",
|
||||
"chk",
|
||||
"chromaprint",
|
||||
"codec2",
|
||||
"codec2raw",
|
||||
"cpk",
|
||||
"crc",
|
||||
"dash",
|
||||
"data",
|
||||
"daud",
|
||||
"dirac",
|
||||
"cvg",
|
||||
"dfpwm",
|
||||
"dnxhd",
|
||||
"dnxhr",
|
||||
"dpx",
|
||||
@@ -542,30 +531,16 @@ export const properties = {
|
||||
"dv",
|
||||
"dvd",
|
||||
"eac3",
|
||||
"ec3",
|
||||
"evc",
|
||||
"exr",
|
||||
"f32be",
|
||||
"f32le",
|
||||
"f4v",
|
||||
"f64be",
|
||||
"f64le",
|
||||
"fbdev",
|
||||
"ffmeta",
|
||||
"ffmetadata",
|
||||
"fifo",
|
||||
"fifo_test",
|
||||
"filmstrip",
|
||||
"film_cpk",
|
||||
"fits",
|
||||
"flac",
|
||||
"flm",
|
||||
"flv",
|
||||
"framecrc",
|
||||
"framehash",
|
||||
"framemd5",
|
||||
"g722",
|
||||
"g723_1",
|
||||
"g726",
|
||||
"g726le",
|
||||
"gif",
|
||||
"gsm",
|
||||
"gxf",
|
||||
@@ -573,32 +548,26 @@ export const properties = {
|
||||
"h263",
|
||||
"h264",
|
||||
"h265",
|
||||
"hash",
|
||||
"hds",
|
||||
"h266",
|
||||
"hdr",
|
||||
"hevc",
|
||||
"hls",
|
||||
"ico",
|
||||
"ilbc",
|
||||
"im1",
|
||||
"im24",
|
||||
"im8",
|
||||
"image2",
|
||||
"image2pipe",
|
||||
"ipod",
|
||||
"ircam",
|
||||
"isma",
|
||||
"ismv",
|
||||
"ivf",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jacosub",
|
||||
"jls",
|
||||
"jp2",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"js",
|
||||
"jss",
|
||||
"kvag",
|
||||
"jxl",
|
||||
"latm",
|
||||
"lbc",
|
||||
"ljpg",
|
||||
@@ -613,13 +582,9 @@ export const properties = {
|
||||
"m4a",
|
||||
"m4b",
|
||||
"m4v",
|
||||
"matroska",
|
||||
"md5",
|
||||
"microdvd",
|
||||
"mjpeg",
|
||||
"mjpg",
|
||||
"mkv",
|
||||
"mkvtimestamp_v2",
|
||||
"mlp",
|
||||
"mmf",
|
||||
"mov",
|
||||
@@ -629,26 +594,17 @@ export const properties = {
|
||||
"mpa",
|
||||
"mpd",
|
||||
"mpeg",
|
||||
"mpeg1video",
|
||||
"mpeg2video",
|
||||
"mpegts",
|
||||
"mpg",
|
||||
"mpjpeg",
|
||||
"msbc",
|
||||
"mts",
|
||||
"mulaw",
|
||||
"mxf",
|
||||
"mxf_d10",
|
||||
"mxf_opatom",
|
||||
"null",
|
||||
"nut",
|
||||
"obu",
|
||||
"oga",
|
||||
"ogg",
|
||||
"ogv",
|
||||
"oma",
|
||||
"opengl",
|
||||
"opus",
|
||||
"oss",
|
||||
"pam",
|
||||
"pbm",
|
||||
"pcm",
|
||||
@@ -656,14 +612,14 @@ export const properties = {
|
||||
"pfm",
|
||||
"pgm",
|
||||
"pgmyuv",
|
||||
"phm",
|
||||
"pix",
|
||||
"png",
|
||||
"ppm",
|
||||
"psp",
|
||||
"pulse",
|
||||
"qoi",
|
||||
"ra",
|
||||
"ras",
|
||||
"rawvideo",
|
||||
"rco",
|
||||
"rcv",
|
||||
"rgb",
|
||||
@@ -671,84 +627,47 @@ export const properties = {
|
||||
"roq",
|
||||
"rs",
|
||||
"rso",
|
||||
"rtp",
|
||||
"rtp_mpegts",
|
||||
"rtsp",
|
||||
"s16be",
|
||||
"s16le",
|
||||
"s24be",
|
||||
"s24le",
|
||||
"s32be",
|
||||
"s32le",
|
||||
"s8",
|
||||
"sap",
|
||||
"sb",
|
||||
"sbc",
|
||||
"scc",
|
||||
"sdl",
|
||||
"sdl2",
|
||||
"segment",
|
||||
"sf",
|
||||
"sgi",
|
||||
"singlejpeg",
|
||||
"smjpeg",
|
||||
"smoothstreaming",
|
||||
"sndio",
|
||||
"sox",
|
||||
"spdif",
|
||||
"spx",
|
||||
"srt",
|
||||
"ssa",
|
||||
"ssegment",
|
||||
"streamhash",
|
||||
"stream_segment",
|
||||
"sub",
|
||||
"sun",
|
||||
"sunras",
|
||||
"sup",
|
||||
"svcd",
|
||||
"sw",
|
||||
"swf",
|
||||
"tco",
|
||||
"tee",
|
||||
"tga",
|
||||
"thd",
|
||||
"tif",
|
||||
"tiff",
|
||||
"truehd",
|
||||
"ts",
|
||||
"tta",
|
||||
"ttml",
|
||||
"tun",
|
||||
"u16be",
|
||||
"u16le",
|
||||
"u24be",
|
||||
"u24le",
|
||||
"u32be",
|
||||
"u32le",
|
||||
"u8",
|
||||
"ub",
|
||||
"ul",
|
||||
"uncodedframecrc",
|
||||
"uw",
|
||||
"v4l2",
|
||||
"vag",
|
||||
"vbn",
|
||||
"vc1",
|
||||
"vc1test",
|
||||
"vc2",
|
||||
"vcd",
|
||||
"vidc",
|
||||
"video4linux2",
|
||||
"vob",
|
||||
"voc",
|
||||
"vtt",
|
||||
"vvc",
|
||||
"w64",
|
||||
"wav",
|
||||
"wbmp",
|
||||
"webm",
|
||||
"webm_chunk",
|
||||
"webm_dash_manifest",
|
||||
"webp",
|
||||
"webvtt",
|
||||
"wma",
|
||||
"wmv",
|
||||
"wtv",
|
||||
@@ -756,12 +675,10 @@ export const properties = {
|
||||
"xbm",
|
||||
"xface",
|
||||
"xml",
|
||||
"xv",
|
||||
"xwd",
|
||||
"y",
|
||||
"y4m",
|
||||
"yuv",
|
||||
"yuv4mpegpipe",
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -771,45 +688,31 @@ export async function convert(
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
options?: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
options?: unknown,
|
||||
): Promise<string> {
|
||||
// let command = "ffmpeg";
|
||||
let extraArgs: string[] = [];
|
||||
let message = "Done";
|
||||
|
||||
// these are containers that can contain multiple formats
|
||||
// const autoDetect = [
|
||||
// "mp4",
|
||||
// "mkv",
|
||||
// "avi",
|
||||
// "mov",
|
||||
// "m4a",
|
||||
// "3gp",
|
||||
// "3g2",
|
||||
// "mj2",
|
||||
// "psp",
|
||||
// "m4b",
|
||||
// "ism",
|
||||
// "ismv",
|
||||
// "isma",
|
||||
// "f4v",
|
||||
// ];
|
||||
if (convertTo === "ico") {
|
||||
// make sure image is 256x256 or smaller
|
||||
extraArgs = ['-filter:v', "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease"];
|
||||
message = "Done: resized to 256x256";
|
||||
}
|
||||
|
||||
// if (!(fileType in autoDetect)) {
|
||||
// command += ` -f "${fileType}"`;
|
||||
// }
|
||||
// Parse FFMPEG_ARGS environment variable into array
|
||||
const ffmpegArgs = process.env.FFMPEG_ARGS ? process.env.FFMPEG_ARGS.split(/\s+/) : [];
|
||||
|
||||
// command += ` -i "${filePath}"`;
|
||||
|
||||
// if (!(convertTo in autoDetect)) {
|
||||
// command += ` -f "${convertTo}"`;
|
||||
// }
|
||||
|
||||
// command += ` "${targetPath}"`;
|
||||
|
||||
const command = `ffmpeg -i "${filePath}" "${targetPath}"`;
|
||||
// Build arguments array
|
||||
const args = [
|
||||
...ffmpegArgs,
|
||||
"-i", filePath,
|
||||
...extraArgs,
|
||||
targetPath
|
||||
];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(command, (error, stdout, stderr) => {
|
||||
execFile("ffmpeg", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
@@ -822,7 +725,7 @@ export async function convert(
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("success");
|
||||
resolve(message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { exec } from "node:child_process";
|
||||
import { execFile } from "node:child_process";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
@@ -143,6 +143,7 @@ export const properties = {
|
||||
"svgz",
|
||||
"text",
|
||||
"tga",
|
||||
"tif",
|
||||
"tiff",
|
||||
"tile",
|
||||
"tim",
|
||||
@@ -227,7 +228,6 @@ export const properties = {
|
||||
"jbig",
|
||||
"jng",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"k",
|
||||
"m",
|
||||
"m2v",
|
||||
@@ -313,12 +313,13 @@ export function convert(
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
options?: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
options?: unknown,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(
|
||||
`gm convert "${filePath}" "${targetPath}"`,
|
||||
execFile(
|
||||
"gm",
|
||||
["convert", filePath, targetPath],
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
@@ -332,7 +333,7 @@ export function convert(
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("success");
|
||||
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,4 +1,4 @@
|
||||
import { exec } from "node:child_process";
|
||||
import { execFile } from "node:child_process";
|
||||
|
||||
// declare possible conversions
|
||||
export const properties = {
|
||||
@@ -39,8 +39,8 @@ export function convert(
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
options?: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
options?: unknown,
|
||||
): Promise<string> {
|
||||
let tool = "";
|
||||
if (fileType === "jxl") {
|
||||
@@ -52,7 +52,7 @@ export function convert(
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(`${tool} "${filePath}" "${targetPath}"`, (error, stdout, stderr) => {
|
||||
execFile(tool, [filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
@@ -65,7 +65,7 @@ export function convert(
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("success");
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,59 +1,48 @@
|
||||
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 convertxelatex,
|
||||
properties as propertiesxelatex,
|
||||
} from "./xelatex";
|
||||
|
||||
import {
|
||||
convert as convertLibjxl,
|
||||
properties as propertiesLibjxl,
|
||||
} from "./libjxl";
|
||||
|
||||
import {
|
||||
convert as convertresvg,
|
||||
properties as propertiesresvg,
|
||||
} from "./resvg";
|
||||
|
||||
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: Record<string, {
|
||||
const properties: Record<
|
||||
string,
|
||||
{
|
||||
properties: {
|
||||
from: Record<string, string[]>;
|
||||
to: Record<string, string[]>;
|
||||
options?: Record<string, Record<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,
|
||||
@@ -66,10 +55,18 @@ const properties: Record<string, {
|
||||
properties: propertiesImage,
|
||||
converter: convertImage,
|
||||
},
|
||||
libheif: {
|
||||
properties: propertiesLibheif,
|
||||
converter: convertLibheif,
|
||||
},
|
||||
xelatex: {
|
||||
properties: propertiesxelatex,
|
||||
converter: convertxelatex,
|
||||
},
|
||||
// calibre: {
|
||||
// properties: propertiesCalibre,
|
||||
// converter: convertCalibre,
|
||||
// },
|
||||
pandoc: {
|
||||
properties: propertiesPandoc,
|
||||
converter: convertPandoc,
|
||||
@@ -78,33 +75,40 @@ const properties: Record<string, {
|
||||
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];
|
||||
|
||||
@@ -132,7 +136,7 @@ export async function mainConverter(
|
||||
}
|
||||
|
||||
try {
|
||||
await converterFunc(
|
||||
const result = await converterFunc(
|
||||
inputFilePath,
|
||||
fileType,
|
||||
convertTo,
|
||||
@@ -142,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(
|
||||
@@ -178,9 +188,7 @@ for (const converterName in properties) {
|
||||
}
|
||||
}
|
||||
|
||||
export const getPossibleTargets = (
|
||||
from: string,
|
||||
): Record<string, string[]> => {
|
||||
export const getPossibleTargets = (from: string): Record<string, string[]> => {
|
||||
const fromClean = normalizeFiletype(from);
|
||||
|
||||
return possibleTargets[fromClean] || {};
|
||||
@@ -204,6 +212,7 @@ for (const converterName in properties) {
|
||||
}
|
||||
possibleInputs.sort();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const getPossibleInputs = () => {
|
||||
return possibleInputs;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { exec } from "node:child_process";
|
||||
import { execFile } from "node:child_process";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
@@ -124,19 +124,26 @@ export function convert(
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
options?: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
options?: unknown,
|
||||
): Promise<string> {
|
||||
// set xelatex here
|
||||
const xelatex = ["pdf", "latex"];
|
||||
let option = "";
|
||||
|
||||
// Build arguments array
|
||||
const args: string[] = [];
|
||||
|
||||
if (xelatex.includes(convertTo)) {
|
||||
option = "--pdf-engine=xelatex";
|
||||
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) => {
|
||||
exec(
|
||||
`pandoc ${option} "${filePath}" -f ${fileType} -t ${convertTo} -o "${targetPath}"`,
|
||||
(error, stdout, stderr) => {
|
||||
execFile("pandoc", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
@@ -149,8 +156,7 @@ export function convert(
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("success");
|
||||
},
|
||||
);
|
||||
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");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { exec } from "node:child_process";
|
||||
import { execFile } from "node:child_process";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
@@ -14,11 +14,11 @@ export function convert(
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
options?: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
options?: unknown,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(`resvg "${filePath}" "${targetPath}"`, (error, stdout, stderr) => {
|
||||
execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export function convert(
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("success");
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { exec } from "node:child_process";
|
||||
import { execFile } from "node:child_process";
|
||||
|
||||
// declare possible conversions
|
||||
export const properties = {
|
||||
@@ -94,8 +94,8 @@ export function convert(
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
options?: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
options?: unknown,
|
||||
): Promise<string> {
|
||||
// if (fileType === "svg") {
|
||||
// const scale = options.scale || 1;
|
||||
@@ -119,8 +119,9 @@ export function convert(
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(
|
||||
`vips ${action} "${filePath}" "${targetPath}"`,
|
||||
execFile(
|
||||
"vips",
|
||||
[action, filePath, targetPath],
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
@@ -134,7 +135,7 @@ export function convert(
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("success");
|
||||
resolve("Done");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { exec } from "node:child_process";
|
||||
import { execFile } from "node:child_process";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
@@ -14,8 +14,8 @@ export function convert(
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
options?: any,
|
||||
// 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", "")
|
||||
@@ -24,8 +24,15 @@ export function convert(
|
||||
.slice(0, -1)
|
||||
.join("/")
|
||||
.replace("./", "");
|
||||
exec(
|
||||
`latexmk -xelatex -interaction=nonstopmode -output-directory="${outputPath}" "${filePath}"`,
|
||||
|
||||
execFile(
|
||||
"latexmk",
|
||||
[
|
||||
"-xelatex",
|
||||
"-interaction=nonstopmode",
|
||||
`-output-directory=${outputPath}`,
|
||||
filePath,
|
||||
],
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
@@ -39,7 +46,7 @@ export function convert(
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("success");
|
||||
resolve("Done");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ export const normalizeFiletype = (filetype: string): string => {
|
||||
const lowercaseFiletype = filetype.toLowerCase();
|
||||
|
||||
switch (lowercaseFiletype) {
|
||||
case "jfif":
|
||||
case "jpg":
|
||||
return "jpeg";
|
||||
case "htm":
|
||||
@@ -10,6 +11,8 @@ export const normalizeFiletype = (filetype: string): string => {
|
||||
return "latex";
|
||||
case "md":
|
||||
return "markdown";
|
||||
case "unknown":
|
||||
return "m4a";
|
||||
default:
|
||||
return lowercaseFiletype;
|
||||
}
|
||||
@@ -23,6 +26,9 @@ export const normalizeOutputFiletype = (filetype: string): string => {
|
||||
return "jpg";
|
||||
case "latex":
|
||||
return "tex";
|
||||
case "markdown_phpextra":
|
||||
case "markdown_strict":
|
||||
case "markdown_mmd":
|
||||
case "markdown":
|
||||
return "md";
|
||||
default:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { exec } from "node:child_process";
|
||||
import { version } from "../../package.json";
|
||||
|
||||
console.log(`ConvertX v${version}`);
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
@@ -53,6 +54,16 @@ if (process.env.NODE_ENV === "production") {
|
||||
}
|
||||
});
|
||||
|
||||
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.");
|
||||
@@ -83,6 +94,46 @@ if (process.env.NODE_ENV === "production") {
|
||||
}
|
||||
});
|
||||
|
||||
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");
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import tw from "tailwindcss";
|
||||
import tailwind from "@tailwindcss/postcss";
|
||||
import postcss from "postcss";
|
||||
|
||||
export const generateTailwind = async () => {
|
||||
const result = await Bun.file("./src/main.css")
|
||||
.text()
|
||||
.then((sourceText) => {
|
||||
const config = "./tailwind.config.js";
|
||||
|
||||
return postcss([tw(config)]).process(sourceText, {
|
||||
return postcss([tailwind]).process(sourceText, {
|
||||
from: "./src/main.css",
|
||||
to: "./public/generated.css",
|
||||
});
|
||||
|
||||
585
src/index.tsx
82
src/main.css
@@ -1,12 +1,78 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
|
||||
@layer components {
|
||||
.article {
|
||||
@apply p-4 mb-4 bg-gray-800/40 w-full mx-auto max-w-4xl rounded;
|
||||
@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));
|
||||
}
|
||||
.btn-primary {
|
||||
@apply bg-lime-500 text-black rounded p-4 hover:bg-lime-400 cursor-pointer;
|
||||
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
// eslint-disable-next-line no-undef
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{html,js,tsx,jsx,cjs,mjs}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [require('tailwind-scrollbar')],
|
||||
}
|
||||