Compare commits

...

56 commits
v1.3.4 ... main

Author SHA1 Message Date
split / May b59d1b24ff
I should probably change this now 2023-11-16 12:16:23 -08:00
Jack W 35616abb48
Merge pull request #38 from mollersuite/imgbot 2023-10-17 16:53:24 -04:00
ImgBotApp 1d6ac68c7d
[ImgBot] Optimize images
*Total -- 456.58kb -> 418.60kb (8.32%)

/assets/apple-touch-icon.png -- 1.83kb -> 1.42kb (22.8%)
/assets/banner.png -- 438.38kb -> 401.05kb (8.52%)
/assets/monofileLogo.svg -- 0.89kb -> 0.83kb (7.53%)
/assets/icons/icon.svg -- 0.89kb -> 0.83kb (7.53%)
/assets/icons/file.svg -- 0.26kb -> 0.25kb (2.25%)
/assets/icons/more.svg -- 0.23kb -> 0.22kb (1.29%)
/assets/icons/admin/delete_file.svg -- 0.50kb -> 0.49kb (1.18%)
/assets/icons/link.svg -- 0.59kb -> 0.59kb (0.99%)
/assets/icons/public.svg -- 0.30kb -> 0.30kb (0.98%)
/assets/icons/logout_all.svg -- 0.30kb -> 0.30kb (0.96%)
/assets/icons/tag.svg -- 0.35kb -> 0.35kb (0.84%)
/assets/icons/person.svg -- 0.36kb -> 0.36kb (0.81%)
/assets/icons/mail.svg -- 0.37kb -> 0.37kb (0.8%)
/assets/icons/delete.svg -- 0.40kb -> 0.40kb (0.73%)
/assets/icons/update.svg -- 0.46kb -> 0.46kb (0.63%)
/assets/icons/logout.svg -- 0.48kb -> 0.48kb (0.61%)
/assets/icons/change_username.svg -- 0.48kb -> 0.48kb (0.6%)
/assets/icons/pound.svg -- 0.52kb -> 0.51kb (0.57%)
/assets/icons/change_password.svg -- 0.52kb -> 0.51kb (0.57%)
/assets/icons/admin/elevate_user.svg -- 0.53kb -> 0.52kb (0.56%)
/assets/icons/paint.svg -- 0.54kb -> 0.54kb (0.54%)
/assets/icons/refresh.svg -- 0.56kb -> 0.56kb (0.52%)
/assets/icons/multiselect.svg -- 0.56kb -> 0.56kb (0.52%)
/assets/icons/private.svg -- 0.58kb -> 0.58kb (0.51%)
/assets/icons/image.svg -- 0.58kb -> 0.58kb (0.5%)
/assets/icons/change_email.svg -- 0.58kb -> 0.58kb (0.5%)
/assets/icons/tag_remove.svg -- 0.61kb -> 0.61kb (0.48%)
/assets/icons/disconnect_email.svg -- 0.67kb -> 0.67kb (0.44%)
/assets/icons/admin/change_file_id.svg -- 0.70kb -> 0.70kb (0.42%)
/assets/icons/anonymous.svg -- 0.75kb -> 0.75kb (0.39%)
/assets/icons/delete_account.svg -- 0.77kb -> 0.77kb (0.38%)
/assets/icons/small_image.svg -- 1.01kb -> 1.01kb (0.29%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2023-10-16 06:58:46 +00:00
split / May 467180b189 add apple-touch-icon 2023-10-16 05:17:54 +00:00
split / May 633dc45687
woops 2023-10-15 16:41:59 -07:00
split / May 2a5a1f75f5
Better meta tags 2023-10-15 16:35:16 -07:00
split / May 9523664f74
Update README.md 2023-10-14 21:36:07 -07:00
split / May 9489a6dd13
newer banner 2023-10-14 21:34:11 -07:00
split / May 8d843169d9
update the top of readme 2023-10-14 21:28:41 -07:00
split / May 2510f31a58
woops 2023-10-14 15:53:06 -07:00
split / May 89b8b2c7b1
switch to hosting the banner image on monofile!
Yeah
2023-10-14 15:34:14 -07:00
Jack W. 196b661b52
fix: 🐛 add default for notificationPermission
Fixes #37
2023-10-14 17:58:59 -04:00
split / May 58aa0aac7e add apple-touch-icon 2023-10-14 21:21:21 +00:00
Jack W cbb9152529
Merge pull request #34 from mollersuite/notifs 2023-10-14 16:42:25 -04:00
split / May a84400c903 firefox has my css cached so i hope this looks good! 2023-10-14 20:22:55 +00:00
Jack W. ab073db311
fix: Add alt text to logo 2023-10-14 16:13:08 -04:00
Jack W. f5ffe42fb4
perf: 🍱 Remove useless gradient attributes from logo 2023-10-14 16:12:58 -04:00
Jack W. 939f6ad0aa
perf: 🍱 Merge download arrow into number 2023-10-14 16:12:43 -04:00
split / May ead3b59917 i hope this looks better 2023-10-14 20:12:10 +00:00
split / May 85f1238897 notifs: use npm run build, probably fine? 2023-10-14 19:48:39 +00:00
Jack W. e617a9464e
refactor: ⚰️ Remove unimplemented Delete action 2023-10-14 15:27:49 -04:00
Jack W. 667acf54f2
feat: Notifications
button isn't as polished as i'd like
2023-10-13 22:18:15 -04:00
Jack W. 2f043d5eb9
refactor: 👥 Welcome to Etcetera, monofile! 2023-10-13 20:21:54 -04:00
Jack W. 1beb4a9632
refactor: 🍱 Final logo 2023-10-13 20:08:51 -04:00
split / May 693f453833 Merge branch 'main' of https://github.com/nbitzz/monofile 2023-10-10 21:12:13 -07:00
split / May dca41242e1 fix brighter pulldowns lol oops 2023-10-10 21:11:30 -07:00
split / May 6dfb18a042
Merge pull request #24 from nbitzz/brighter-pulldowns
Brighter pulldowns
2023-10-09 02:25:08 -07:00
split / May 0bf53801ed brighter pulldowns 2023-10-08 23:20:21 -07:00
split / May 783d6be808
remove the stupid webdrop thing
its not like i was ever gonna complete it lmfao
sharedrop.io exists anyway
2023-10-08 20:20:27 -07:00
split / May 052d51abfe
add silly live test instance 2023-10-08 20:18:34 -07:00
Jack W 1ca2ea28ae
docs: cooler introduction 2023-10-08 23:16:00 -04:00
unlinkability bf2b398a1a
Merge pull request #14 from nbitzz/btr-inline-docs
Better inline documentation
2023-10-03 23:02:36 +01:00
split / May 3163ad649b
TSDoc files.ts & errors.ts 2023-10-03 13:37:37 -07:00
split / May 937363c9ee
TSDoc: mail.ts, middleware.ts, ratelimit.ts 2023-10-03 13:21:28 -07:00
split / May 9f480cbc87
btr-inline-docs: TSDoc accounts.ts 2023-10-03 11:48:57 -07:00
unlinkability aa840e176f
Merge pull request #13 from nbitzz/token-permissions
Add permissions to tokens
2023-10-03 17:01:49 +01:00
split / May 0071c17fe5 tokens: ability to set no expiration 2023-10-02 22:00:21 -07:00
split / May 5e15b20c10 token-permissions: bugfix 2023-10-02 21:01:53 -07:00
split / May 8bec8a4360 token-permissions: update adminRoutes 2023-10-02 19:20:32 -07:00
split / May af9c3233d8 token-permissions: update primaryApi 2023-10-02 19:20:11 -07:00
split / May 6742bf724f token-permissions: update fileApiRoutes 2023-10-02 19:18:29 -07:00
split / May 2006fa1cca token-permissions: update authRoutes 2023-10-02 19:14:00 -07:00
split / May fad320d7fb token-permissions: update middleware further 2023-10-02 18:27:48 -07:00
split / May a04cc9a376 token-permissions: update middleware 2023-10-02 18:22:18 -07:00
split / May b3efd8ca29 token-permissions: implement into tokens 2023-10-02 17:17:38 -07:00
split / May 8a4dfd361c more accepted characters in ids (doom) 2023-10-01 17:48:44 -07:00
split / May 8f6a5b5474 swap to crypto.randomInt over Math.random 2023-10-01 17:45:07 -07:00
split / May 261b81ee04 bump token length to 36bytes 2023-10-01 17:42:00 -07:00
split / May 11602b5556 bugfix: deindex not working for idx 0 2023-10-01 17:41:31 -07:00
split / May e54f6a5b8f
Merge pull request #11 from nbitzz/bearer-auth
Implement bearer authentication
2023-10-01 17:35:53 -07:00
split / May 76119811ff bearer-auth: customcss cleanup 2023-10-01 16:32:04 -07:00
split / May c8e486630e bearer-auth: move /download route to middleware 2023-10-01 16:28:15 -07:00
split / May a5d3131180 bearer-auth: make primaryApi use middleware 2023-10-01 16:27:22 -07:00
split / May 14d4261858 bearer-auth: make fileApiRoutes use middleware 2023-10-01 16:25:21 -07:00
split / May 64dd66dc03 bearer-auth: add to middleware:getAccount 2023-10-01 16:18:47 -07:00
split / May 1dfd095563 begin hell 2 2023-10-01 15:59:24 -07:00
62 changed files with 640 additions and 464 deletions

4
.vscode/tasks.json vendored
View file

@ -3,7 +3,7 @@
"tasks": [
{
"type": "shell",
"command":"tsc\nsass src/style:out/style\nrollup -c",
"command":"npm run build",
"group": {
"kind": "build",
"isDefault": true
@ -12,7 +12,7 @@
},
{
"type": "shell",
"command":"tsc\nsass src/style:out/style\nrollup -c\nnode ./out/server/index.js\ndel ./out/* -Recurse",
"command":"npm run build\nnode ./out/server/index.js\ndel ./out/* -Recurse",
"group": {
"kind": "build",
"isDefault": true

View file

@ -1,6 +1,5 @@
# monofile
The open-source, Discord-based file sharing service.
[Live instance](https://fyle.uk)
<img src="https://fyle.uk/fiAb3" alt="monofile: 'File sharing over Discord', topped with the monofile logo. Blue gradient with an image of monofile 1.4.0-dev on the side.">
<p align="center">The open-source, Discord-based file sharing service.<br><a href="https://fyle.uk">Flagship instance</a> &mdash; <a href="https://beta.fyle.uk">Live test instance</a></p>
<br>
@ -66,10 +65,10 @@ monofile should now be running on either `env.MONOFILE_PORT` or port `3000`.
## Disclaimer
Although we believe monofile is not against Discord's developer terms of service, monofile's contributors are not liable if Discord takes action against you for running an instance.
Although we believe monofile is not against Discord's developer terms of service, Etcetera is not liable if Discord takes action against you for running an instance.
## License
Code written by monofile's contributors is currently licensed under [Unlicense](https://github.com/nbitzz/monofile/blob/main/LICENSE).
Code written by Etcetera is currently licensed under [Unlicense](./LICENSE).
Icons under `/assets/icons` were created by Microsoft, and as such are licensed under [different terms](https://github.com/nbitzz/monofile/blob/1.3.0/assets/icons/README.md) (MIT).
Icons under `/assets/icons` were created by Microsoft, and as such are licensed under [different terms](./assets/icons/README.md) (MIT).

BIN
assets/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 448 KiB

After

Width:  |  Height:  |  Size: 401 KiB

BIN
assets/banner.xcf Normal file

Binary file not shown.

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m11.256 13 .238-1.5h1.481l-.237 1.5h-1.482Z" fill="#8aadf4"/><path d="M17.75 2.001a2.25 2.25 0 0 1 2.245 2.096L20 4.25v15.498a2.25 2.25 0 0 1-2.096 2.245l-.154.005H6.25a2.25 2.25 0 0 1-2.245-2.096L4 19.75V4.251a2.25 2.25 0 0 1 2.096-2.245l.154-.005h11.5Zm-5.355 13.16a.75.75 0 1 0 1.482.234l.142-.895h.731a.75.75 0 0 0 0-1.5h-.494l.238-1.5h.756a.75.75 0 0 0 0-1.5h-.519l.162-1.025a.75.75 0 1 0-1.481-.234l-.2 1.259h-1.48l.161-1.025a.75.75 0 1 0-1.481-.234l-.2 1.259H9.25a.75.75 0 1 0 0 1.5h.725L9.738 13H8.75a.75.75 0 1 0 0 1.5h.75l-.105.66a.75.75 0 0 0 1.482.235l.142-.895H12.5l-.105.66Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#8aadf4" d="m11.256 13 .238-1.5h1.481l-.237 1.5h-1.482Z"/><path fill="#DDD" d="M17.75 2.001a2.25 2.25 0 0 1 2.245 2.096L20 4.25v15.498a2.25 2.25 0 0 1-2.096 2.245l-.154.005H6.25a2.25 2.25 0 0 1-2.245-2.096L4 19.75V4.251a2.25 2.25 0 0 1 2.096-2.245l.154-.005h11.5Zm-5.355 13.16a.75.75 0 1 0 1.482.234l.142-.895h.731a.75.75 0 0 0 0-1.5h-.494l.238-1.5h.756a.75.75 0 0 0 0-1.5h-.519l.162-1.025a.75.75 0 1 0-1.481-.234l-.2 1.259h-1.48l.161-1.025a.75.75 0 1 0-1.481-.234l-.2 1.259H9.25a.75.75 0 1 0 0 1.5h.725L9.738 13H8.75a.75.75 0 1 0 0 1.5h.75l-.105.66a.75.75 0 0 0 1.482.235l.142-.895H12.5l-.105.66Z"/></svg>

Before

Width:  |  Height:  |  Size: 716 B

After

Width:  |  Height:  |  Size: 713 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2v6a2 2 0 0 0 2 2h6v10a2 2 0 0 1-2 2h-6.81A6.5 6.5 0 0 0 4 11.498V4a2 2 0 0 1 2-2h6Z" fill="#DDDDDD"/><path d="M13.5 2.5V8a.5.5 0 0 0 .5.5h5.5l-6-6ZM6.5 12a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11Zm2.478 3.731-1.77 1.77 1.77 1.769a.5.5 0 1 1-.707.707l-1.77-1.77-1.769 1.768a.5.5 0 1 1-.707-.708L5.794 17.5 4.025 15.73a.5.5 0 1 1 .707-.707l1.77 1.769 1.77-1.769a.5.5 0 0 1 .706.707Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M12 2v6a2 2 0 0 0 2 2h6v10a2 2 0 0 1-2 2h-6.81A6.5 6.5 0 0 0 4 11.498V4a2 2 0 0 1 2-2h6Z"/><path fill="#DDD" d="M13.5 2.5V8a.5.5 0 0 0 .5.5h5.5l-6-6ZM6.5 12a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11Zm2.478 3.731-1.77 1.77 1.77 1.769a.5.5 0 1 1-.707.707l-1.77-1.77-1.769 1.768a.5.5 0 1 1-.707-.708L5.794 17.5 4.025 15.73a.5.5 0 1 1 .707-.707l1.77 1.769 1.77-1.769a.5.5 0 0 1 .706.707Z"/></svg>

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 504 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14 14.05V14H4.253a2.249 2.249 0 0 0-2.25 2.25v.919c0 .572.18 1.13.511 1.596C4.056 20.929 6.58 22 10 22c.715 0 1.39-.046 2.026-.14A2.51 2.51 0 0 1 12 21.5v-5a2.5 2.5 0 0 1 2-2.45ZM10 2.005a5 5 0 1 1 0 10 5 5 0 0 1 0-10ZM15 15v-1a2.5 2.5 0 0 1 5 0v1h.5a1.5 1.5 0 0 1 1.5 1.5v5a1.5 1.5 0 0 1-1.5 1.5h-6a1.5 1.5 0 0 1-1.5-1.5v-5a1.5 1.5 0 0 1 1.5-1.5h.5Zm1.5-1v1h2v-1a1 1 0 1 0-2 0Zm2 5a1 1 0 1 0-2 0 1 1 0 0 0 2 0Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M14 14.05V14H4.253a2.249 2.249 0 0 0-2.25 2.25v.919c0 .572.18 1.13.511 1.596C4.056 20.929 6.58 22 10 22c.715 0 1.39-.046 2.026-.14A2.51 2.51 0 0 1 12 21.5v-5a2.5 2.5 0 0 1 2-2.45ZM10 2.005a5 5 0 1 1 0 10 5 5 0 0 1 0-10ZM15 15v-1a2.5 2.5 0 0 1 5 0v1h.5a1.5 1.5 0 0 1 1.5 1.5v5a1.5 1.5 0 0 1-1.5 1.5h-6a1.5 1.5 0 0 1-1.5-1.5v-5a1.5 1.5 0 0 1 1.5-1.5h.5Zm1.5-1v1h2v-1a1 1 0 1 0-2 0Zm2 5a1 1 0 1 0-2 0 1 1 0 0 0 2 0Z"/></svg>

Before

Width:  |  Height:  |  Size: 540 B

After

Width:  |  Height:  |  Size: 537 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.022 14A6.47 6.47 0 0 0 11 17.5c0 1.63.6 3.12 1.592 4.261-.796.16-1.66.24-2.592.24-3.42 0-5.944-1.072-7.486-3.237a2.75 2.75 0 0 1-.51-1.595v-.92a2.249 2.249 0 0 1 2.249-2.25h7.77Zm5.478-2a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Zm0 7.75a.625.625 0 1 0 0 1.25.625.625 0 0 0 0-1.25Zm0-5.876c-1.048 0-1.864.817-1.853 1.954a.5.5 0 1 0 1-.01c-.006-.579.36-.944.853-.944.473 0 .854.392.854.95 0 .192-.056.342-.224.56l-.094.117-.1.113-.265.29-.136.157c-.384.457-.535.793-.535 1.31a.5.5 0 1 0 1 0c0-.203.059-.359.239-.59l.085-.104.1-.116.267-.29.134-.155c.378-.45.529-.783.529-1.293 0-1.103-.823-1.95-1.854-1.95ZM10 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M12.022 14A6.47 6.47 0 0 0 11 17.5c0 1.63.6 3.12 1.592 4.261-.796.16-1.66.24-2.592.24-3.42 0-5.944-1.072-7.486-3.237a2.75 2.75 0 0 1-.51-1.595v-.92a2.249 2.249 0 0 1 2.249-2.25h7.77Zm5.478-2a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Zm0 7.75a.625.625 0 1 0 0 1.25.625.625 0 0 0 0-1.25Zm0-5.876c-1.048 0-1.864.817-1.853 1.954a.5.5 0 1 0 1-.01c-.006-.579.36-.944.853-.944.473 0 .854.392.854.95 0 .192-.056.342-.224.56l-.094.117-.1.113-.265.29-.136.157c-.384.457-.535.793-.535 1.31a.5.5 0 1 0 1 0c0-.203.059-.359.239-.59l.085-.104.1-.116.267-.29.134-.155c.378-.45.529-.783.529-1.293 0-1.103-.823-1.95-1.854-1.95ZM10 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z"/></svg>

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 769 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23 6.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0ZM18 7l.001 2.504a.5.5 0 1 1-1 0V7h-2.505a.5.5 0 0 1 0-1H17V3.5a.5.5 0 0 1 1 0V6h2.497a.5.5 0 0 1 0 1H18Zm-.5 6a6.478 6.478 0 0 0 4.5-1.81v5.56a3.25 3.25 0 0 1-3.066 3.245L18.75 20H5.25a3.25 3.25 0 0 1-3.245-3.066L2 16.75V8.608l9.652 5.056a.75.75 0 0 0 .696 0l2.417-1.266A6.477 6.477 0 0 0 17.5 13ZM5.25 4h6.248A6.479 6.479 0 0 0 11 6.5c0 1.993.897 3.776 2.308 4.968L12 12.153l-9.984-5.23a3.25 3.25 0 0 1 3.048-2.918L5.25 4Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M23 6.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0ZM18 7l.001 2.504a.5.5 0 1 1-1 0V7h-2.505a.5.5 0 0 1 0-1H17V3.5a.5.5 0 0 1 1 0V6h2.497a.5.5 0 0 1 0 1H18Zm-.5 6a6.478 6.478 0 0 0 4.5-1.81v5.56a3.25 3.25 0 0 1-3.066 3.245L18.75 20H5.25a3.25 3.25 0 0 1-3.245-3.066L2 16.75V8.608l9.652 5.056a.75.75 0 0 0 .696 0l2.417-1.266A6.477 6.477 0 0 0 17.5 13ZM5.25 4h6.248A6.479 6.479 0 0 0 11 6.5c0 1.993.897 3.776 2.308 4.968L12 12.153l-9.984-5.23a3.25 3.25 0 0 1 3.048-2.918L5.25 4Z"/></svg>

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 594 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.95 8.6a6.554 6.554 0 0 1 6.55-6.55c3.596 0 6.55 2.819 6.55 6.45a6.554 6.554 0 0 1-6.55 6.55c-.531 0-1.055-.076-1.552-.204A1.25 1.25 0 0 1 12.7 16.05h-1.75v1.75c0 .69-.56 1.25-1.25 1.25H7.95v1.25a1.75 1.75 0 0 1-1.75 1.75H3.7a1.75 1.75 0 0 1-1.75-1.75v-2.172c0-.73.29-1.429.806-1.944L8.99 9.948a.275.275 0 0 0 .07-.244A6.386 6.386 0 0 1 8.95 8.6Zm9.3-1.6a1.25 1.25 0 1 0-2.5 0 1.25 1.25 0 0 0 2.5 0Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M8.95 8.6a6.554 6.554 0 0 1 6.55-6.55c3.596 0 6.55 2.819 6.55 6.45a6.554 6.554 0 0 1-6.55 6.55c-.531 0-1.055-.076-1.552-.204A1.25 1.25 0 0 1 12.7 16.05h-1.75v1.75c0 .69-.56 1.25-1.25 1.25H7.95v1.25a1.75 1.75 0 0 1-1.75 1.75H3.7a1.75 1.75 0 0 1-1.75-1.75v-2.172c0-.73.29-1.429.806-1.944L8.99 9.948a.275.275 0 0 0 .07-.244A6.386 6.386 0 0 1 8.95 8.6Zm9.3-1.6a1.25 1.25 0 1 0-2.5 0 1.25 1.25 0 0 0 2.5 0Z"/></svg>

Before

Width:  |  Height:  |  Size: 529 B

After

Width:  |  Height:  |  Size: 526 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11 15c0-.35.06-.687.17-1H4.253a2.249 2.249 0 0 0-2.249 2.249v.92c0 .572.179 1.13.51 1.596C4.057 20.929 6.58 22 10 22c.397 0 .783-.014 1.156-.043A2.997 2.997 0 0 1 11 21v-6ZM10 2.005a5 5 0 1 1 0 10 5 5 0 0 1 0-10ZM12 15a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2v-6Zm2.5 1a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6Zm0 3a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M11 15c0-.35.06-.687.17-1H4.253a2.249 2.249 0 0 0-2.249 2.249v.92c0 .572.179 1.13.51 1.596C4.057 20.929 6.58 22 10 22c.397 0 .783-.014 1.156-.043A2.997 2.997 0 0 1 11 21v-6ZM10 2.005a5 5 0 1 1 0 10 5 5 0 0 1 0-10ZM12 15a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2v-6Zm2.5 1a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6Zm0 3a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6Z"/></svg>

Before

Width:  |  Height:  |  Size: 496 B

After

Width:  |  Height:  |  Size: 493 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m4.21 4.387.083-.094a1 1 0 0 1 1.32-.083l.094.083L12 10.585l6.293-6.292a1 1 0 1 1 1.414 1.414L13.415 12l6.292 6.293a1 1 0 0 1 .083 1.32l-.083.094a1 1 0 0 1-1.32.083l-.094-.083L12 13.415l-6.293 6.292a1 1 0 0 1-1.414-1.414L10.585 12 4.293 5.707a1 1 0 0 1-.083-1.32l.083-.094-.083.094Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="m4.21 4.387.083-.094a1 1 0 0 1 1.32-.083l.094.083L12 10.585l6.293-6.292a1 1 0 1 1 1.414 1.414L13.415 12l6.292 6.293a1 1 0 0 1 .083 1.32l-.083.094a1 1 0 0 1-1.32.083l-.094-.083L12 13.415l-6.293 6.292a1 1 0 0 1-1.414-1.414L10.585 12 4.293 5.707a1 1 0 0 1-.083-1.32l.083-.094-.083.094Z"/></svg>

Before

Width:  |  Height:  |  Size: 410 B

After

Width:  |  Height:  |  Size: 407 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17.5 12a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Zm-5.478 2A6.47 6.47 0 0 0 11 17.5c0 1.644.61 3.145 1.617 4.29-.802.141-1.675.21-2.617.21-2.89 0-5.128-.656-6.691-2a3.75 3.75 0 0 1-1.305-2.843v-.907A2.25 2.25 0 0 1 4.254 14h7.768Zm3.071.966-.07.058-.057.07a.5.5 0 0 0 0 .568l.058.069 1.77 1.77-1.768 1.766-.057.07a.5.5 0 0 0 0 .568l.058.07.069.057a.5.5 0 0 0 .568 0l.07-.058 1.766-1.767 1.77 1.77.069.058a.5.5 0 0 0 .568 0l.07-.058.058-.07a.5.5 0 0 0 0-.568l-.058-.07-1.77-1.768 1.772-1.77.058-.07a.5.5 0 0 0 0-.568l-.058-.069-.069-.058a.5.5 0 0 0-.569 0l-.069.058-1.771 1.77-1.77-1.77-.07-.058a.5.5 0 0 0-.492-.043l-.076.043ZM10 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M17.5 12a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Zm-5.478 2A6.47 6.47 0 0 0 11 17.5c0 1.644.61 3.145 1.617 4.29-.802.141-1.675.21-2.617.21-2.89 0-5.128-.656-6.691-2a3.75 3.75 0 0 1-1.305-2.843v-.907A2.25 2.25 0 0 1 4.254 14h7.768Zm3.071.966-.07.058-.057.07a.5.5 0 0 0 0 .568l.058.069 1.77 1.77-1.768 1.766-.057.07a.5.5 0 0 0 0 .568l.058.07.069.057a.5.5 0 0 0 .568 0l.07-.058 1.766-1.767 1.77 1.77.069.058a.5.5 0 0 0 .568 0l.07-.058.058-.07a.5.5 0 0 0 0-.568l-.058-.07-1.77-1.768 1.772-1.77.058-.07a.5.5 0 0 0 0-.568l-.058-.069-.069-.058a.5.5 0 0 0-.569 0l-.069.058-1.771 1.77-1.77-1.77-.07-.058a.5.5 0 0 0-.492-.043l-.076.043ZM10 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z"/></svg>

Before

Width:  |  Height:  |  Size: 791 B

After

Width:  |  Height:  |  Size: 788 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0Zm-7.146-2.354a.5.5 0 0 0-.708.708L16.793 6.5l-1.647 1.646a.5.5 0 0 0 .708.708L17.5 7.207l1.646 1.647a.5.5 0 0 0 .708-.708L18.207 6.5l1.647-1.646a.5.5 0 0 0-.708-.708L17.5 5.793l-1.646-1.647ZM17.5 13a6.478 6.478 0 0 0 4.5-1.81v5.56a3.25 3.25 0 0 1-3.066 3.245L18.75 20H5.25a3.25 3.25 0 0 1-3.245-3.066L2 16.75V8.608l9.652 5.056a.75.75 0 0 0 .696 0l2.417-1.266A6.477 6.477 0 0 0 17.5 13ZM5.25 4h6.248A6.479 6.479 0 0 0 11 6.5c0 1.993.897 3.776 2.308 4.968L12 12.153l-9.984-5.23a3.25 3.25 0 0 1 3.048-2.918L5.25 4Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M23 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0Zm-7.146-2.354a.5.5 0 0 0-.708.708L16.793 6.5l-1.647 1.646a.5.5 0 0 0 .708.708L17.5 7.207l1.646 1.647a.5.5 0 0 0 .708-.708L18.207 6.5l1.647-1.646a.5.5 0 0 0-.708-.708L17.5 5.793l-1.646-1.647ZM17.5 13a6.478 6.478 0 0 0 4.5-1.81v5.56a3.25 3.25 0 0 1-3.066 3.245L18.75 20H5.25a3.25 3.25 0 0 1-3.245-3.066L2 16.75V8.608l9.652 5.056a.75.75 0 0 0 .696 0l2.417-1.266A6.477 6.477 0 0 0 17.5 13ZM5.25 4h6.248A6.479 6.479 0 0 0 11 6.5c0 1.993.897 3.776 2.308 4.968L12 12.153l-9.984-5.23a3.25 3.25 0 0 1 3.048-2.918L5.25 4Z"/></svg>

Before

Width:  |  Height:  |  Size: 685 B

After

Width:  |  Height:  |  Size: 682 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2v6a2 2 0 0 0 2 2h6v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h6Z" fill="#DDDDDD"/><path d="M13.5 2.5V8a.5.5 0 0 0 .5.5h5.5l-6-6Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M12 2v6a2 2 0 0 0 2 2h6v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h6Z"/><path fill="#DDD" d="M13.5 2.5V8a.5.5 0 0 0 .5.5h5.5l-6-6Z"/></svg>

Before

Width:  |  Height:  |  Size: 267 B

After

Width:  |  Height:  |  Size: 261 B

1
assets/icons/icon.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="15.009" version="1.1" viewBox="0 0 3.175 3.971"><defs><linearGradient id="gradient"><stop offset="0" stop-color="#fff" stop-opacity="0"/><stop offset=".5" stop-color="#fff"/></linearGradient></defs><title>monofile</title><path fill="#fff" d="m0 3.442"/><path fill="url(#gradient)" d="m0 3.442v.52917h3.175v-.52917z"/><path fill="#fff" d="m1.3219 0-0.56845 0.34727v0.53228l0.5054-0.30231h0.013436v1.6464l-0.55605-0.55605c-0.04961-0.0496-0.11679-0.077516-0.18707-0.077516s-0.13746 0.027916-0.18707 0.077516c-0.10335 0.10336-0.10335 0.27079 0 0.37414l1.0584 1.0578c0.10335 0.10335 0.27078 0.10335 0.37414 0l1.0584-1.0578c0.10335-0.10335 0.10335-0.27078 0-0.37414-0.10335-0.10335-0.27078-0.10335-0.37414 0l-0.55967 0.55967v-2.2273h-0.57724z"/></svg>

After

Width:  |  Height:  |  Size: 847 B

View file

@ -1 +0,0 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m11.256 13 .238-1.5h1.481l-.237 1.5h-1.482Z" fill="#8aadf4"/><path d="M17.75 2.001a2.25 2.25 0 0 1 2.245 2.096L20 4.25v15.498a2.25 2.25 0 0 1-2.096 2.245l-.154.005H6.25a2.25 2.25 0 0 1-2.245-2.096L4 19.75V4.251a2.25 2.25 0 0 1 2.096-2.245l.154-.005h11.5Zm-5.355 13.16a.75.75 0 1 0 1.482.234l.142-.895h.731a.75.75 0 0 0 0-1.5h-.494l.238-1.5h.756a.75.75 0 0 0 0-1.5h-.519l.162-1.025a.75.75 0 1 0-1.481-.234l-.2 1.259h-1.48l.161-1.025a.75.75 0 1 0-1.481-.234l-.2 1.259H9.25a.75.75 0 1 0 0 1.5h.725L9.738 13H8.75a.75.75 0 1 0 0 1.5h.75l-.105.66a.75.75 0 0 0 1.482.235l.142-.895H12.5l-.105.66Z" fill="#8aadf4"/></svg>

Before

Width:  |  Height:  |  Size: 716 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m11.475 13.718.083-.071a.75.75 0 0 1 .874-.007l.093.078 6.928 6.8A3.235 3.235 0 0 1 17.75 21H6.25a3.235 3.235 0 0 1-1.703-.481l6.928-6.801.083-.071-.083.07ZM17.75 3A3.25 3.25 0 0 1 21 6.25v11.5c0 .627-.178 1.213-.485 1.71l-6.939-6.813-.128-.116a2.25 2.25 0 0 0-2.889-.006l-.135.123-6.939 6.811A3.235 3.235 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h11.5Zm-1.998 3a2.252 2.252 0 1 0 0 4.504 2.252 2.252 0 0 0 0-4.504Zm0 1.5a.752.752 0 1 1 0 1.504.752.752 0 0 1 0-1.504Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="m11.475 13.718.083-.071a.75.75 0 0 1 .874-.007l.093.078 6.928 6.8A3.235 3.235 0 0 1 17.75 21H6.25a3.235 3.235 0 0 1-1.703-.481l6.928-6.801.083-.071-.083.07ZM17.75 3A3.25 3.25 0 0 1 21 6.25v11.5c0 .627-.178 1.213-.485 1.71l-6.939-6.813-.128-.116a2.25 2.25 0 0 0-2.889-.006l-.135.123-6.939 6.811A3.235 3.235 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h11.5Zm-1.998 3a2.252 2.252 0 1 0 0 4.504 2.252 2.252 0 0 0 0-4.504Zm0 1.5a.752.752 0 1 1 0 1.504.752.752 0 0 1 0-1.504Z"/></svg>

Before

Width:  |  Height:  |  Size: 595 B

After

Width:  |  Height:  |  Size: 592 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.502 3.003a3.5 3.5 0 0 0-3.5 3.5v6a3.5 3.5 0 0 0 3.5 3.5H7v-2h-.497a1.5 1.5 0 0 1-1.5-1.5v-6a1.5 1.5 0 0 1 1.5-1.5h6a1.5 1.5 0 0 1 1.5 1.5v6a1.5 1.5 0 0 1-1.5 1.5h-1.506v2h1.506a3.5 3.5 0 0 0 3.5-3.5v-6a3.5 3.5 0 0 0-3.5-3.5h-6Z" fill="#DDDDDD"/><path d="M10 11.5a1.5 1.5 0 0 1 1.5-1.5h1.499V8H11.5A3.5 3.5 0 0 0 8 11.5v6a3.5 3.5 0 0 0 3.5 3.5h6a3.5 3.5 0 0 0 3.5-3.5v-6A3.5 3.5 0 0 0 17.5 8h-.495v2h.495a1.5 1.5 0 0 1 1.5 1.5v6a1.5 1.5 0 0 1-1.5 1.5h-6a1.5 1.5 0 0 1-1.5-1.5v-6Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M6.502 3.003a3.5 3.5 0 0 0-3.5 3.5v6a3.5 3.5 0 0 0 3.5 3.5H7v-2h-.497a1.5 1.5 0 0 1-1.5-1.5v-6a1.5 1.5 0 0 1 1.5-1.5h6a1.5 1.5 0 0 1 1.5 1.5v6a1.5 1.5 0 0 1-1.5 1.5h-1.506v2h1.506a3.5 3.5 0 0 0 3.5-3.5v-6a3.5 3.5 0 0 0-3.5-3.5h-6Z"/><path fill="#DDD" d="M10 11.5a1.5 1.5 0 0 1 1.5-1.5h1.499V8H11.5A3.5 3.5 0 0 0 8 11.5v6a3.5 3.5 0 0 0 3.5 3.5h6a3.5 3.5 0 0 0 3.5-3.5v-6A3.5 3.5 0 0 0 17.5 8h-.495v2h.495a1.5 1.5 0 0 1 1.5 1.5v6a1.5 1.5 0 0 1-1.5 1.5h-6a1.5 1.5 0 0 1-1.5-1.5v-6Z"/></svg>

Before

Width:  |  Height:  |  Size: 609 B

After

Width:  |  Height:  |  Size: 603 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.25 2.75a1.5 1.5 0 0 0-1.5 1.5v15.5a1.5 1.5 0 0 0 1.5 1.5h5.94a6.5 6.5 0 0 1 7.06-10.012V4.25a1.5 1.5 0 0 0-1.5-1.5H6.25Zm2.25 10.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3Zm9 9.75a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11Zm3.5-5.5a.5.5 0 0 1-.5.5h-4.793l1.647 1.646a.5.5 0 0 1-.708.708l-2.5-2.5a.5.5 0 0 1 0-.708l2.5-2.5a.5.5 0 0 1 .708.708L15.707 17H20.5a.5.5 0 0 1 .5.5Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M6.25 2.75a1.5 1.5 0 0 0-1.5 1.5v15.5a1.5 1.5 0 0 0 1.5 1.5h5.94a6.5 6.5 0 0 1 7.06-10.012V4.25a1.5 1.5 0 0 0-1.5-1.5H6.25Zm2.25 10.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3Zm9 9.75a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11Zm3.5-5.5a.5.5 0 0 1-.5.5h-4.793l1.647 1.646a.5.5 0 0 1-.708.708l-2.5-2.5a.5.5 0 0 1 0-.708l2.5-2.5a.5.5 0 0 1 .708.708L15.707 17H20.5a.5.5 0 0 1 .5.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 491 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.207 2.543a1 1 0 0 1 0 1.414L5.414 5.75h7.836a8 8 0 1 1-8 8 1 1 0 1 1 2 0 6 6 0 1 0 6-6H5.414l1.793 1.793a1 1 0 0 1-1.414 1.414l-3.5-3.5a1 1 0 0 1 0-1.414l3.5-3.5a1 1 0 0 1 1.414 0Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M7.207 2.543a1 1 0 0 1 0 1.414L5.414 5.75h7.836a8 8 0 1 1-8 8 1 1 0 1 1 2 0 6 6 0 1 0 6-6H5.414l1.793 1.793a1 1 0 0 1-1.414 1.414l-3.5-3.5a1 1 0 0 1 0-1.414l3.5-3.5a1 1 0 0 1 1.414 0Z"/></svg>

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 308 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22 8.608v8.142a3.25 3.25 0 0 1-3.066 3.245L18.75 20H5.25a3.25 3.25 0 0 1-3.245-3.066L2 16.75V8.608l9.652 5.056a.75.75 0 0 0 .696 0L22 8.608ZM5.25 4h13.5a3.25 3.25 0 0 1 3.234 2.924L12 12.154l-9.984-5.23a3.25 3.25 0 0 1 3.048-2.919L5.25 4h13.5-13.5Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M22 8.608v8.142a3.25 3.25 0 0 1-3.066 3.245L18.75 20H5.25a3.25 3.25 0 0 1-3.245-3.066L2 16.75V8.608l9.652 5.056a.75.75 0 0 0 .696 0L22 8.608ZM5.25 4h13.5a3.25 3.25 0 0 1 3.234 2.924L12 12.154l-9.984-5.23a3.25 3.25 0 0 1 3.048-2.919L5.25 4h13.5-13.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 374 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8 12a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM14 12a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM18 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M8 12a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM14 12a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM18 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/></svg>

Before

Width:  |  Height:  |  Size: 232 B

After

Width:  |  Height:  |  Size: 229 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.707 3.293a1 1 0 0 0-1.414 0L4 4.586l-.293-.293a1 1 0 0 0-1.414 1.414l1 1a1 1 0 0 0 1.414 0l2-2a1 1 0 0 0 0-1.414ZM10 16.993h11.003a1 1 0 0 1 .117 1.994l-.117.006H10A1 1 0 0 1 9.883 17l.117-.007ZM10 11h11.003a1 1 0 0 1 .117 1.993l-.117.007H10a1 1 0 0 1-.117-1.993L10 11Zm0-6h11.003a1 1 0 0 1 .117 1.993L21.003 7H10a1 1 0 0 1-.117-1.993L10 5ZM5.293 16.293a1 1 0 0 1 1.414 1.414l-2 2a1 1 0 0 1-1.414 0l-1-1a1 1 0 1 1 1.414-1.414l.293.293 1.293-1.293Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M6.707 3.293a1 1 0 0 0-1.414 0L4 4.586l-.293-.293a1 1 0 0 0-1.414 1.414l1 1a1 1 0 0 0 1.414 0l2-2a1 1 0 0 0 0-1.414ZM10 16.993h11.003a1 1 0 0 1 .117 1.994l-.117.006H10A1 1 0 0 1 9.883 17l.117-.007ZM10 11h11.003a1 1 0 0 1 .117 1.993l-.117.007H10a1 1 0 0 1-.117-1.993L10 11Zm0-6h11.003a1 1 0 0 1 .117 1.993L21.003 7H10a1 1 0 0 1-.117-1.993L10 5ZM5.293 16.293a1 1 0 0 1 1.414 1.414l-2 2a1 1 0 0 1-1.414 0l-1-1a1 1 0 1 1 1.414-1.414l.293.293 1.293-1.293Z"/></svg>

Before

Width:  |  Height:  |  Size: 578 B

After

Width:  |  Height:  |  Size: 575 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2.25a.75.75 0 0 0-1.5 0V3.5a2.24 2.24 0 0 0-.841.53L2.78 10.91a2.25 2.25 0 0 0 0 3.182L7.66 18.97a2.25 2.25 0 0 0 3.182 0l6.879-6.879a2.25 2.25 0 0 0 0-3.182L12.84 4.03A2.24 2.24 0 0 0 12 3.5V2.25Zm-1.5 3.06v1.44a.75.75 0 0 0 1.5 0V5.31l4.659 4.66a.75.75 0 0 1 0 1.06l-.97.97H3.812l.029-.03L10.5 5.31ZM19.521 13.602a.874.874 0 0 0-1.542 0l-2.008 3.766C14.85 19.466 16.372 22 18.75 22s3.898-2.534 2.78-4.632l-2.009-3.766Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M12 2.25a.75.75 0 0 0-1.5 0V3.5a2.24 2.24 0 0 0-.841.53L2.78 10.91a2.25 2.25 0 0 0 0 3.182L7.66 18.97a2.25 2.25 0 0 0 3.182 0l6.879-6.879a2.25 2.25 0 0 0 0-3.182L12.84 4.03A2.24 2.24 0 0 0 12 3.5V2.25Zm-1.5 3.06v1.44a.75.75 0 0 0 1.5 0V5.31l4.659 4.66a.75.75 0 0 1 0 1.06l-.97.97H3.812l.029-.03L10.5 5.31ZM19.521 13.602a.874.874 0 0 0-1.542 0l-2.008 3.766C14.85 19.466 16.372 22 18.75 22s3.898-2.534 2.78-4.632l-2.009-3.766Z"/></svg>

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 549 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17.754 14a2.249 2.249 0 0 1 2.25 2.249v.918a2.75 2.75 0 0 1-.513 1.599C17.945 20.929 15.42 22 12 22c-3.422 0-5.945-1.072-7.487-3.237a2.75 2.75 0 0 1-.51-1.595v-.92a2.249 2.249 0 0 1 2.249-2.25h11.501ZM12 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M17.754 14a2.249 2.249 0 0 1 2.25 2.249v.918a2.75 2.75 0 0 1-.513 1.599C17.945 20.929 15.42 22 12 22c-3.422 0-5.945-1.072-7.487-3.237a2.75 2.75 0 0 1-.51-1.595v-.92a2.249 2.249 0 0 1 2.249-2.25h11.501ZM12 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z"/></svg>

Before

Width:  |  Height:  |  Size: 369 B

After

Width:  |  Height:  |  Size: 366 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M10.985 3.165a1 1 0 0 0-1.973-.33l-.86 5.163L3.998 8a1 1 0 1 0 .002 2l3.817-.002-.667 4L3 14a1 1 0 1 0 0 2l3.817-.002-.807 4.838a1 1 0 1 0 1.973.329l.862-5.167 4.975-.003-.806 4.84a1 1 0 1 0 1.972.33l.862-5.17L20 15.992a1 1 0 0 0 0-2l-3.819.001.667-4.001L21 9.99a1 1 0 0 0 0-2l-3.818.002.804-4.827a1 1 0 1 0-1.972-.33l-.86 5.159-4.975.003.806-4.832Zm-1.14 6.832 4.976-.003-.667 4.001-4.976.002.667-4Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M10.985 3.165a1 1 0 0 0-1.973-.33l-.86 5.163L3.998 8a1 1 0 1 0 .002 2l3.817-.002-.667 4L3 14a1 1 0 1 0 0 2l3.817-.002-.807 4.838a1 1 0 1 0 1.973.329l.862-5.167 4.975-.003-.806 4.84a1 1 0 1 0 1.972.33l.862-5.17L20 15.992a1 1 0 0 0 0-2l-3.819.001.667-4.001L21 9.99a1 1 0 0 0 0-2l-3.818.002.804-4.827a1 1 0 1 0-1.972-.33l-.86 5.159-4.975.003.806-4.832Zm-1.14 6.832 4.976-.003-.667 4.001-4.976.002.667-4Z"/></svg>

Before

Width:  |  Height:  |  Size: 528 B

After

Width:  |  Height:  |  Size: 525 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M2.22 2.22a.75.75 0 0 0-.073.976l.073.084 4.034 4.035a9.986 9.986 0 0 0-3.955 5.75.75.75 0 0 0 1.455.364 8.49 8.49 0 0 1 3.58-5.034l1.81 1.81A4 4 0 0 0 14.8 15.86l5.919 5.92a.75.75 0 0 0 1.133-.977l-.073-.084-6.113-6.114.001-.002-6.95-6.946.002-.002-1.133-1.13L3.28 2.22a.75.75 0 0 0-1.06 0ZM12 5.5c-1 0-1.97.148-2.889.425l1.237 1.236a8.503 8.503 0 0 1 9.899 6.272.75.75 0 0 0 1.455-.363A10.003 10.003 0 0 0 12 5.5Zm.195 3.51 3.801 3.8a4.003 4.003 0 0 0-3.801-3.8Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M2.22 2.22a.75.75 0 0 0-.073.976l.073.084 4.034 4.035a9.986 9.986 0 0 0-3.955 5.75.75.75 0 0 0 1.455.364 8.49 8.49 0 0 1 3.58-5.034l1.81 1.81A4 4 0 0 0 14.8 15.86l5.919 5.92a.75.75 0 0 0 1.133-.977l-.073-.084-6.113-6.114.001-.002-6.95-6.946.002-.002-1.133-1.13L3.28 2.22a.75.75 0 0 0-1.06 0ZM12 5.5c-1 0-1.97.148-2.889.425l1.237 1.236a8.503 8.503 0 0 1 9.899 6.272.75.75 0 0 0 1.455-.363A10.003 10.003 0 0 0 12 5.5Zm.195 3.51 3.801 3.8a4.003 4.003 0 0 0-3.801-3.8Z"/></svg>

Before

Width:  |  Height:  |  Size: 592 B

After

Width:  |  Height:  |  Size: 589 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 9.005a4 4 0 1 1 0 8 4 4 0 0 1 0-8ZM12 5.5c4.613 0 8.596 3.15 9.701 7.564a.75.75 0 1 1-1.455.365 8.503 8.503 0 0 0-16.493.004.75.75 0 0 1-1.455-.363A10.003 10.003 0 0 1 12 5.5Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M12 9.005a4 4 0 1 1 0 8 4 4 0 0 1 0-8ZM12 5.5c4.613 0 8.596 3.15 9.701 7.564a.75.75 0 1 1-1.455.365 8.503 8.503 0 0 0-16.493.004.75.75 0 0 1-1.455-.363A10.003 10.003 0 0 1 12 5.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 304 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10Zm3.27-11.25H14a.75.75 0 0 0 0 1.5h2.75a.75.75 0 0 0 .75-.75V8.25a.75.75 0 0 0-1.5 0V9a4.991 4.991 0 0 0-4-2c-1.537 0-2.904.66-3.827 1.77a.75.75 0 0 0 1.154.96C9.963 8.963 10.907 8.5 12 8.5c1.492 0 2.767.934 3.27 2.25Zm-7.27 5V15a5.013 5.013 0 0 0 7.821.237.75.75 0 1 0-1.142-.972 3.513 3.513 0 0 1-5.842-.765H10a.75.75 0 0 0 0-1.5H7.25a.75.75 0 0 0-.75.75v3a.75.75 0 0 0 1.5 0Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10Zm3.27-11.25H14a.75.75 0 0 0 0 1.5h2.75a.75.75 0 0 0 .75-.75V8.25a.75.75 0 0 0-1.5 0V9a4.991 4.991 0 0 0-4-2c-1.537 0-2.904.66-3.827 1.77a.75.75 0 0 0 1.154.96C9.963 8.963 10.907 8.5 12 8.5c1.492 0 2.767.934 3.27 2.25Zm-7.27 5V15a5.013 5.013 0 0 0 7.821.237.75.75 0 1 0-1.142-.972 3.513 3.513 0 0 1-5.842-.765H10a.75.75 0 0 0 0-1.5H7.25a.75.75 0 0 0-.75.75v3a.75.75 0 0 0 1.5 0Z"/></svg>

Before

Width:  |  Height:  |  Size: 578 B

After

Width:  |  Height:  |  Size: 575 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.25 13a.75.75 0 0 1 .743.648l.007.102v5a3.25 3.25 0 0 1-3.066 3.245L18.75 22h-4.668c.536-.385.973-.9 1.265-1.499l3.403-.001a1.75 1.75 0 0 0 1.744-1.607l.006-.143v-5a.75.75 0 0 1 .75-.75ZM9.447 17.165l.114.103 4.085 4.086.023.019A3.235 3.235 0 0 1 11.75 22h-6.5a3.235 3.235 0 0 1-1.92-.627l.024-.02 4.085-4.085.114-.103a1.5 1.5 0 0 1 1.894 0ZM11.75 9A3.25 3.25 0 0 1 15 12.25v6.5c0 .718-.233 1.382-.627 1.92l-.02-.024-4.085-4.085-.13-.122a2.5 2.5 0 0 0-3.269-.006l-.137.128-4.086 4.085-.019.023A3.235 3.235 0 0 1 2 18.75v-6.5A3.25 3.25 0 0 1 5.25 9h6.5ZM11 12a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm7.75-10a3.25 3.25 0 0 1 3.245 3.066L22 5.25v5a.75.75 0 0 1-1.493.102l-.007-.102v-5a1.75 1.75 0 0 0-1.606-1.744L18.75 3.5h-5a.75.75 0 0 1-.102-1.493L13.75 2h5Zm-8.5 0a.75.75 0 0 1 .102 1.493l-.102.007h-5a1.75 1.75 0 0 0-1.744 1.606L3.5 5.25v3.402c-.6.292-1.114.73-1.5 1.266V5.25a3.25 3.25 0 0 1 3.066-3.245L5.25 2h5Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M21.25 13a.75.75 0 0 1 .743.648l.007.102v5a3.25 3.25 0 0 1-3.066 3.245L18.75 22h-4.668c.536-.385.973-.9 1.265-1.499l3.403-.001a1.75 1.75 0 0 0 1.744-1.607l.006-.143v-5a.75.75 0 0 1 .75-.75ZM9.447 17.165l.114.103 4.085 4.086.023.019A3.235 3.235 0 0 1 11.75 22h-6.5a3.235 3.235 0 0 1-1.92-.627l.024-.02 4.085-4.085.114-.103a1.5 1.5 0 0 1 1.894 0ZM11.75 9A3.25 3.25 0 0 1 15 12.25v6.5c0 .718-.233 1.382-.627 1.92l-.02-.024-4.085-4.085-.13-.122a2.5 2.5 0 0 0-3.269-.006l-.137.128-4.086 4.085-.019.023A3.235 3.235 0 0 1 2 18.75v-6.5A3.25 3.25 0 0 1 5.25 9h6.5ZM11 12a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm7.75-10a3.25 3.25 0 0 1 3.245 3.066L22 5.25v5a.75.75 0 0 1-1.493.102l-.007-.102v-5a1.75 1.75 0 0 0-1.606-1.744L18.75 3.5h-5a.75.75 0 0 1-.102-1.493L13.75 2h5Zm-8.5 0a.75.75 0 0 1 .102 1.493l-.102.007h-5a1.75 1.75 0 0 0-1.744 1.606L3.5 5.25v3.402c-.6.292-1.114.73-1.5 1.266V5.25a3.25 3.25 0 0 1 3.066-3.245L5.25 2h5Z"/></svg>

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-8.5 8.503a3.255 3.255 0 0 1-4.597.001L3.489 16.06a3.25 3.25 0 0 1-.003-4.596l8.5-8.51A3.25 3.25 0 0 1 14.284 2h5.465ZM17 5.502a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-8.5 8.503a3.255 3.255 0 0 1-4.597.001L3.489 16.06a3.25 3.25 0 0 1-.003-4.596l8.5-8.51A3.25 3.25 0 0 1 14.284 2h5.465ZM17 5.502a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"/></svg>

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 355 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-.026.026a6.5 6.5 0 0 0-9.028 8.92 3.256 3.256 0 0 1-4.043-.442L3.489 16.06a3.25 3.25 0 0 1-.004-4.596l8.5-8.51a3.25 3.25 0 0 1 2.3-.953h5.465ZM17 5.502a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3ZM23 17.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0Zm-7.147-2.354a.5.5 0 0 0-.707.708l1.647 1.646-1.647 1.646a.5.5 0 0 0 .707.708l1.647-1.647 1.646 1.647a.5.5 0 0 0 .707-.708L18.207 17.5l1.646-1.646a.5.5 0 0 0-.707-.708L17.5 16.793l-1.647-1.647Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-.026.026a6.5 6.5 0 0 0-9.028 8.92 3.256 3.256 0 0 1-4.043-.442L3.489 16.06a3.25 3.25 0 0 1-.004-4.596l8.5-8.51a3.25 3.25 0 0 1 2.3-.953h5.465ZM17 5.502a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3ZM23 17.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0Zm-7.147-2.354a.5.5 0 0 0-.707.708l1.647 1.646-1.647 1.646a.5.5 0 0 0 .707.708l1.647-1.647 1.646 1.647a.5.5 0 0 0 .707-.708L18.207 17.5l1.646-1.646a.5.5 0 0 0-.707-.708L17.5 16.793l-1.647-1.647Z"/></svg>

Before

Width:  |  Height:  |  Size: 623 B

After

Width:  |  Height:  |  Size: 620 B

View file

@ -1 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22 12.001c0-5.523-4.476-10-10-10-5.522 0-10 4.477-10 10s4.478 10 10 10c5.524 0 10-4.477 10-10Zm-14.53.28a.75.75 0 0 1-.073-.976l.073-.085 4-4a.75.75 0 0 1 .977-.073l.085.073 4 4.001a.75.75 0 0 1-.977 1.133l-.084-.072-2.72-2.722v6.691a.75.75 0 0 1-.649.744L12 17a.75.75 0 0 1-.743-.648l-.007-.102v-6.69l-2.72 2.72a.75.75 0 0 1-.976.073l-.084-.073Z" fill="#DDDDDD"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#DDD" d="M22 12.001c0-5.523-4.476-10-10-10-5.522 0-10 4.477-10 10s4.478 10 10 10c5.524 0 10-4.477 10-10Zm-14.53.28a.75.75 0 0 1-.073-.976l.073-.085 4-4a.75.75 0 0 1 .977-.073l.085.073 4 4.001a.75.75 0 0 1-.977 1.133l-.084-.072-2.72-2.722v6.691a.75.75 0 0 1-.649.744L12 17a.75.75 0 0 1-.743-.648l-.007-.102v-6.69l-2.72 2.72a.75.75 0 0 1-.976.073l-.084-.073Z"/></svg>

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 472 B

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

1
assets/monofileLogo.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="15.009" version="1.1" viewBox="0 0 3.175 3.971"><defs><linearGradient id="gradient"><stop offset="0" stop-color="#fff" stop-opacity="0"/><stop offset=".5" stop-color="#fff"/></linearGradient></defs><title>monofile</title><path fill="#fff" d="m0 3.442"/><path fill="url(#gradient)" d="m0 3.442v.52917h3.175v-.52917z"/><path fill="#fff" d="m1.3219 0-0.56845 0.34727v0.53228l0.5054-0.30231h0.013436v1.6464l-0.55605-0.55605c-0.04961-0.0496-0.11679-0.077516-0.18707-0.077516s-0.13746 0.027916-0.18707 0.077516c-0.10335 0.10336-0.10335 0.27079 0 0.37414l1.0584 1.0578c0.10335 0.10335 0.27078 0.10335 0.37414 0l1.0584-1.0578c0.10335-0.10335 0.10335-0.27078 0-0.37414-0.10335-0.10335-0.27078-0.10335-0.37414 0l-0.55967 0.55967v-2.2273h-0.57724z"/></svg>

After

Width:  |  Height:  |  Size: 847 B

View file

@ -11,10 +11,6 @@
"requiredForUpload": false
},
"webdrop": {
"accountRequired": false
},
"mail": {
"transport": {
"host": "smtp.fastmail.com",
@ -28,4 +24,4 @@
"trustProxy": true,
"forceSSL": true
}
}

39
package-lock.json generated
View file

@ -1,17 +1,14 @@
{
"name": "monofile",
"version": "1.3.0-beta",
"version": "1.4.0-dev",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "monofile",
"version": "1.3.0-beta",
"version": "1.4.0-dev",
"license": "Unlicense",
"dependencies": {
"@fontsource/fira-code": "^5.0.8",
"@fontsource/inconsolata": "^5.0.8",
"@fontsource/source-sans-pro": "^5.0.8",
"@types/body-parser": "^1.19.2",
"@types/express": "^4.17.14",
"@types/multer": "^1.4.7",
@ -37,7 +34,7 @@
"svelte": "^3.55.1"
},
"engines": {
"node": ">=v18"
"node": ">=v16.11"
}
},
"node_modules/@discordjs/builders": {
@ -90,21 +87,6 @@
"node": ">=16.9.0"
}
},
"node_modules/@fontsource/fira-code": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-5.0.8.tgz",
"integrity": "sha512-kp/tJUVnjaZeLHENMBFTTSgP2B7+/rIboeofuMfoGB40s2U0DKXNqQcOqIF5PtDhJ5QTG1LcviYXMnc1yG6oYQ=="
},
"node_modules/@fontsource/inconsolata": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.0.8.tgz",
"integrity": "sha512-KpBU6q1yCovfycaFprVEauh8U5RsWty3konFfUukyRRxZBK4Sf73XmGQc8iJ4CPrOP4dplGfdX2kjbRgdymajA=="
},
"node_modules/@fontsource/source-sans-pro": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/source-sans-pro/-/source-sans-pro-5.0.8.tgz",
"integrity": "sha512-5U2UvIYRkCMozZ388gCE73PEpa2MFgN/0t9O4a1FF7bGT/MIneQWSL1XpWZ8iMVYdh6ntxRf3iFA6slCIuFgkg=="
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz",
@ -1750,21 +1732,6 @@
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.1.0.tgz",
"integrity": "sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ=="
},
"@fontsource/fira-code": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-5.0.8.tgz",
"integrity": "sha512-kp/tJUVnjaZeLHENMBFTTSgP2B7+/rIboeofuMfoGB40s2U0DKXNqQcOqIF5PtDhJ5QTG1LcviYXMnc1yG6oYQ=="
},
"@fontsource/inconsolata": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.0.8.tgz",
"integrity": "sha512-KpBU6q1yCovfycaFprVEauh8U5RsWty3konFfUukyRRxZBK4Sf73XmGQc8iJ4CPrOP4dplGfdX2kjbRgdymajA=="
},
"@fontsource/source-sans-pro": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/source-sans-pro/-/source-sans-pro-5.0.8.tgz",
"integrity": "sha512-5U2UvIYRkCMozZ388gCE73PEpa2MFgN/0t9O4a1FF7bGT/MIneQWSL1XpWZ8iMVYdh6ntxRf3iFA6slCIuFgkg=="
},
"@rollup/plugin-node-resolve": {
"version": "15.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz",

View file

@ -1,14 +1,15 @@
{
"name": "monofile",
"version": "1.3.4",
"version": "2.0.0-dev",
"description": "Discord-based file sharing",
"main": "index.js",
"scripts": {
"start": "node ./out/server/index.js",
"build": "tsc\nsass src/style:out/style\nrollup -c",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "nbitzz",
"author": "Etcetera (https://cetera.uk)",
"license": "Unlicense",
"engines": {
"node": ">=v16.11"

View file

@ -14,10 +14,15 @@
href="/static/style/app.css"
>
<link
rel="apple-touch-icon"
href="/static/assets/apple-touch-icon.png"
>
<link
rel="icon"
type="image/svg"
href="/static/assets/icons/icon_temp.svg"
href="/static/assets/icons/icon.svg"
>
<link
@ -37,6 +42,9 @@
<meta name="title" content="monofile">
<meta name="description" content="The open-source Discord-based file sharing service">
<meta name="theme-color" content="rgb(30, 33, 36)">
<meta name="twitter:card" content="summary_large_image">
<meta name="image" content="/static/assets/banner.png">
<meta name="og:image" content="/static/assets/banner.png">
</head>
@ -44,4 +52,4 @@
</body>
</html>
</html>

View file

@ -13,6 +13,7 @@ import * as authRoutes from "./routes/authRoutes";
import * as fileApiRoutes from "./routes/fileApiRoutes";
import * as adminRoutes from "./routes/adminRoutes";
import * as primaryApi from "./routes/primaryApi";
import { getAccount } from "./lib/middleware";
require("dotenv").config()
@ -82,11 +83,14 @@ app.get("/", function(req,res) {
// serve download page
app.get("/download/:fileId",(req,res) => {
app.get("/download/:fileId", getAccount, (req,res) => {
let acc = res.locals.acc as Accounts.Account
if (files.getFilePointer(req.params.fileId)) {
let file = files.getFilePointer(req.params.fileId)
if (file.visibility == "private" && Accounts.getFromToken(req.cookies.auth)?.id != file.owner) {
if (file.visibility == "private" && acc?.id != file.owner) {
ServeError(res,403,"you do not own this file")
return
}

View file

@ -27,6 +27,14 @@ export interface Account {
}
}
/**
* @description Create a new account.
* @param username New account's username
* @param pwd New account's password
* @param admin Whether or not the account should have administrative rights
* @returns A Promise which returns the new account's ID
*/
export function create(username:string,pwd:string,admin:boolean=false):Promise<string> {
return new Promise((resolve,reject) => {
let accId = crypto.randomBytes(12).toString("hex")
@ -46,26 +54,52 @@ export function create(username:string,pwd:string,admin:boolean=false):Promise<s
})
}
/**
* @description Gets an account from its username.
* @param id The target account's username
* @returns An Account, if it exists
*/
export function getFromUsername(username:string) {
return Accounts.find(e => e.username == username)
}
/**
* @description Gets an account from its ID.
* @param id The target account's ID
* @returns An Account, if it exists
*/
export function getFromId(id:string) {
return Accounts.find(e => e.id == id)
}
/**
* @description Gets an account from an AuthToken. Equivalent to getFromId(auth.validate(token)).
* @param token A valid AuthToken
* @returns An Account, if the token is valid
*/
export function getFromToken(token:string) {
let accId = auth.validate(token)
if (!accId) return
return getFromId(accId)
}
/**
* @description Deletes an account.
* @param id The target account's ID
*/
export function deleteAccount(id:string) {
Accounts.splice(Accounts.findIndex(e => e.id == id),1)
return save()
}
export namespace password {
/**
* @description Generates a hashed and salted version of an input password.
* @param password Target password.
* @param _salt Designated password salt. Use to validate a password.
*/
export function hash(password:string,_salt?:string) {
let salt = _salt || crypto.randomBytes(12).toString('base64')
let hash = crypto.createHash('sha256').update(`${salt}${password}`).digest('hex')
@ -75,6 +109,12 @@ export namespace password {
hash:hash
}
}
/**
* @description Sets an account's password.
* @param id The target account's ID
* @param password New password
*/
export function set(id:string,password:string) {
let acc = Accounts.find(e => e.id == id)
@ -84,6 +124,12 @@ export namespace password {
return save()
}
/**
* @description Tests a password against an account.
* @param id The target account's ID
* @param password Password to check
*/
export function check(id:string,password:string) {
let acc = Accounts.find(e => e.id == id)
if (!acc) return
@ -93,6 +139,12 @@ export namespace password {
}
export namespace files {
/**
* @description Adds a file to an account's file index
* @param accountId The target account's ID
* @param fileId The target file's ID
* @returns Promise that resolves after accounts.json finishes writing
*/
export function index(accountId:string,fileId:string) {
// maybe replace with a obj like
// { x:true }
@ -105,17 +157,28 @@ export namespace files {
return save()
}
/**
* @description Removes a file from an account's file index
* @param accountId The target account's ID
* @param fileId The target file's ID
* @param noWrite Whether or not accounts.json should save
* @returns A Promise which resolves when accounts.json finishes writing, if `noWrite` is `false`
*/
export function deindex(accountId:string,fileId:string, noWrite:boolean=false) {
let acc = Accounts.find(e => e.id == accountId)
if (!acc) return
let fi = acc.files.findIndex(e => e == fileId)
if (fi) {
if (fi >= 0) {
acc.files.splice(fi,1)
if (!noWrite) return save()
}
}
}
/**
* @description Saves accounts.json
* @returns A promise which resolves when accounts.json finishes writing
*/
export function save() {
return writeFile(`${process.cwd()}/.data/accounts.json`,JSON.stringify(Accounts))
.catch((err) => console.error(err))

View file

@ -1,19 +1,46 @@
import crypto from "crypto"
import express from "express"
import { readFile, writeFile } from "fs/promises"
export let AuthTokens: AuthToken[] = []
export let AuthTokenTO:{[key:string]:NodeJS.Timeout} = {}
export const ValidTokenPermissions = [
"user", // permissions to /auth/me, with email docked
"email", // adds email back to /auth/me
"private", // allows app to read private files
"upload", // allows an app to upload under an account
"manage", // allows an app to manage an account's files
"customize", // allows an app to change customization settings
"admin" // only available for accounts with admin
// gives an app access to all admin tools
] as const
export type TokenType = "User" | "App"
export type TokenPermission = typeof ValidTokenPermissions[number]
export interface AuthToken {
account: string,
token: string,
expire: number
expire: number,
type?: TokenType, // if !type, assume User
tokenPermissions?: TokenPermission[] // default to user if type is App,
// give full permissions if type is User
}
export function create(id:string,expire:number=(24*60*60*1000)) {
export function create(
id:string,
expire:number=(24*60*60*1000),
type:TokenType="User",
tokenPermissions?:TokenPermission[]
) {
let token = {
account:id,
token:crypto.randomBytes(12).toString('hex'),
expire:Date.now()+expire
token:crypto.randomBytes(36).toString('hex'),
expire: expire ? Date.now()+expire : 0,
type,
tokenPermissions: type == "App" ? tokenPermissions || ["user"] : undefined
}
AuthTokens.push(token)
@ -24,11 +51,32 @@ export function create(id:string,expire:number=(24*60*60*1000)) {
return token.token
}
export function tokenFor(req: express.Request) {
return req.cookies.auth || (
req.header("authorization")?.startsWith("Bearer ")
? req.header("authorization")?.split(" ")[1]
: undefined
)
}
function getToken(token:string) {
return AuthTokens.find(e => e.token == token && (e.expire == 0 || Date.now() < e.expire))
}
export function validate(token:string) {
return AuthTokens.find(e => e.token == token && Date.now() < e.expire)?.account
return getToken(token)?.account
}
export function getType(token:string): TokenType | undefined {
return getToken(token)?.type
}
export function getPermissions(token:string): TokenPermission[] | undefined {
return getToken(token)?.tokenPermissions
}
export function tokenTimer(token:AuthToken) {
if (!token.expire) return // justincase
if (Date.now() >= token.expire) {
invalidate(token.token)
return

View file

@ -3,6 +3,12 @@ import { readFile } from "fs/promises"
let errorPage:string
/**
* @description Serves an error as a response to a request with an error page attached
* @param res Express response object
* @param code Error code
* @param reason Error reason
*/
export default async function ServeError(
res:Response,
code:number,
@ -29,7 +35,12 @@ export default async function ServeError(
.replace(/\$text/g,reason)
)
}
/**
* @description Redirects a user to another page.
* @param res Express response object
* @param url Target URL
* @deprecated Use `res.redirect` instead.
*/
export function Redirect(res:Response,url:string) {
res.status(302)
res.header("Location",url)

View file

@ -2,21 +2,27 @@ import axios from "axios";
import Discord, { Client, TextBasedChannel } from "discord.js";
import { readFile, writeFile } from "fs";
import { Readable } from "node:stream";
import crypto from "node:crypto";
import { files } from "./accounts";
import * as Accounts from "./accounts";
export let id_check_regex = /[A-Za-z0-9_\-\.\!\=\:]+/
export let id_check_regex = /[A-Za-z0-9_\-\.\!\=\:\&\$\,\+\;\@\~\*\(\)\']+/
export let alphanum = Array.from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
// bad solution but whatever
export type FileVisibility = "public" | "anonymous" | "private"
/**
* @description Generates an alphanumeric string, used for files
* @param length Length of the ID
* @returns a random alphanumeric string
*/
export function generateFileId(length:number=5) {
let fid = ""
for (let i = 0; i < length; i++) {
fid += alphanum[Math.floor(Math.random()*alphanum.length)]
fid += alphanum[crypto.randomInt(0,alphanum.length)]
}
return fid
}
@ -39,7 +45,10 @@ export interface Configuration {
accounts: {
registrationEnabled: boolean,
requiredForUpload: boolean
}
},
trustProxy: boolean,
forceSSL: boolean
}
export interface FilePointer {
@ -92,6 +101,12 @@ export default class Files {
}
/**
* @description Uploads a new file
* @param settings Settings for your new upload
* @param fBuffer Buffer containing file content
* @returns Promise which resolves to the ID of the new file
*/
uploadFile(settings:FileUploadSettings,fBuffer:Buffer):Promise<string|StatusCodeError> {
return new Promise<string>(async (resolve,reject) => {
if (!this.uploadChannel) {
@ -243,6 +258,12 @@ export default class Files {
// fs
/**
* @description Writes a file to disk
* @param uploadId New file's ID
* @param file FilePointer representing the new file
* @returns Promise which resolves to the file's ID
*/
writeFile(uploadId: string, file: FilePointer):Promise<string> {
return new Promise((resolve, reject) => {
@ -263,8 +284,12 @@ export default class Files {
})
}
// todo: move read code here
/**
* @description Read a file
* @param uploadId Target file's ID
* @param range Byte range to get
* @returns A `Readable` containing the file's contents
*/
readFileStream(uploadId: string, range?: {start:number, end:number}):Promise<Readable> {
return new Promise(async (resolve,reject) => {
if (!this.uploadChannel) {
@ -400,6 +425,11 @@ export default class Files {
})
}
/**
* @description Deletes a file
* @param uploadId Target file's ID
* @param noWrite Whether or not the change should be written to disk. Enable for bulk deletes
*/
unlink(uploadId:string, noWrite: boolean = false):Promise<void> {
return new Promise(async (resolve,reject) => {
let tmp = this.files[uploadId];
@ -430,6 +460,11 @@ export default class Files {
})
}
/**
* @description Get a file's FilePointer
* @param uploadId Target file's ID
* @returns FilePointer for the file
*/
getFilePointer(uploadId:string):FilePointer {
return this.files[uploadId]
}

View file

@ -19,6 +19,13 @@ transport =
// lazy but
/**
* @description Sends an email
* @param to Target email address
* @param subject Email subject
* @param content Email content
* @returns Promise which resolves to the output from nodemailer.transport.sendMail
*/
export function sendMail(to: string, subject: string, content: string) {
return new Promise((resolve,reject) => {
transport.sendMail({

View file

@ -1,13 +1,20 @@
import * as Accounts from "./accounts";
import express, { type RequestHandler } from "express"
import ServeError from "../lib/errors";
import * as auth from "./auth";
export let getAccount: RequestHandler = function(req, res, next) {
res.locals.acc = Accounts.getFromToken(req.cookies.auth)
/**
* @description Middleware which adds an account, if any, to res.locals.acc
*/
export const getAccount: RequestHandler = function(req, res, next) {
res.locals.acc = Accounts.getFromToken(auth.tokenFor(req))
next()
}
export let requiresAccount: RequestHandler = function(_req, res, next) {
/**
* @description Middleware which blocks requests which do not have res.locals.acc set
*/
export const requiresAccount: RequestHandler = function(_req, res, next) {
if (!res.locals.acc) {
ServeError(res, 401, "not logged in")
return
@ -15,10 +22,52 @@ export let requiresAccount: RequestHandler = function(_req, res, next) {
next()
}
export let requiresAdmin: RequestHandler = function(_req, res, next) {
/**
* @description Middleware which blocks requests that have res.locals.acc.admin set to a falsy value
*/
export const requiresAdmin: RequestHandler = function(_req, res, next) {
if (!res.locals.acc.admin) {
ServeError(res, 403, "you are not an administrator")
return
}
next()
}
/**
* @description Blocks requests based on the permissions which a token has. Does not apply to routes being accessed with a token of type `User`
* @param tokenPermissions Permissions which your route requires.
* @returns Express middleware
*/
export const requiresPermissions = function(...tokenPermissions: auth.TokenPermission[]): RequestHandler {
return function(req, res, next) {
let token = auth.tokenFor(req)
let type = auth.getType(token)
if (type == "App") {
let permissions = auth.getPermissions(token)
if (!permissions) ServeError(res, 403, "insufficient permissions")
else {
for (let v of tokenPermissions) {
if (!permissions.includes(v as auth.TokenPermission)) {
ServeError(res,403,"insufficient permissions")
return
}
}
next()
}
} else next()
}
}
/**
* @description Blocks requests based on whether or not the token being used to access the route is of type `User`.
*/
export const noAPIAccess: RequestHandler = function(req, res, next) {
if (auth.getType(auth.tokenFor(req)) == "App") ServeError(res, 403, "apps are not allowed to access this endpoint")
else next()
}

View file

@ -2,14 +2,19 @@ import { RequestHandler } from "express"
import { type Account } from "./accounts"
import ServeError from "./errors"
interface ratelimitSettings {
interface RatelimitSettings {
requests: number
per: number
}
export function accountRatelimit( settings: ratelimitSettings ): RequestHandler {
/**
* @description Ratelimits a route based on res.locals.acc
* @param settings Ratelimit settings
* @returns Express middleware
*/
export function accountRatelimit( settings: RatelimitSettings ): RequestHandler {
let activeLimits: {
[ key: string ]: {
requests: number,

View file

@ -5,7 +5,7 @@ import * as auth from "../lib/auth";
import bytes from "bytes"
import {writeFile} from "fs";
import { sendMail } from "../lib/mail";
import { getAccount, requiresAccount, requiresAdmin } from "../lib/middleware"
import { getAccount, requiresAccount, requiresAdmin, requiresPermissions } from "../lib/middleware"
import ServeError from "../lib/errors";
import Files from "../lib/files";
@ -19,6 +19,7 @@ adminRoutes
.use(getAccount)
.use(requiresAccount)
.use(requiresAdmin)
.use(requiresPermissions("admin"))
let files:Files
export function setFilesObj(newFiles:Files) {

View file

@ -3,7 +3,7 @@ import { Router } from "express";
import * as Accounts from "../lib/accounts";
import * as auth from "../lib/auth";
import { sendMail } from "../lib/mail";
import { getAccount, requiresAccount } from "../lib/middleware"
import { getAccount, noAPIAccess, requiresAccount, requiresPermissions } from "../lib/middleware"
import { accountRatelimit } from "../lib/ratelimit"
import ServeError from "../lib/errors";
@ -123,7 +123,7 @@ authRoutes.post("/logout", (req,res) => {
res.send("logged out")
})
authRoutes.post("/dfv", requiresAccount, parser, (req,res) => {
authRoutes.post("/dfv", requiresAccount, requiresPermissions("manage"), parser, (req,res) => {
let acc = res.locals.acc as Accounts.Account
if (['public','private','anonymous'].includes(req.body.defaultFileVisibility)) {
@ -136,7 +136,7 @@ authRoutes.post("/dfv", requiresAccount, parser, (req,res) => {
}
})
authRoutes.post("/customcss", requiresAccount, parser, (req,res) => {
authRoutes.post("/customcss", requiresAccount, requiresPermissions("customize"), parser, (req,res) => {
let acc = res.locals.acc as Accounts.Account
if (typeof req.body.fileId != "string") req.body.fileId = undefined;
@ -158,7 +158,7 @@ authRoutes.post("/customcss", requiresAccount, parser, (req,res) => {
}
})
authRoutes.post("/embedcolor", requiresAccount, parser, (req,res) => {
authRoutes.post("/embedcolor", requiresAccount, requiresPermissions("customize"), parser, (req,res) => {
let acc = res.locals.acc as Accounts.Account
if (typeof req.body.color != "string") req.body.color = undefined;
@ -181,7 +181,7 @@ authRoutes.post("/embedcolor", requiresAccount, parser, (req,res) => {
}
})
authRoutes.post("/embedsize", requiresAccount, parser, (req,res) => {
authRoutes.post("/embedsize", requiresAccount, requiresPermissions("customize"), parser, (req,res) => {
let acc = res.locals.acc as Accounts.Account
if (typeof req.body.largeImage != "boolean") req.body.color = false;
@ -193,7 +193,7 @@ authRoutes.post("/embedsize", requiresAccount, parser, (req,res) => {
res.send(`custom embed image size saved`)
})
authRoutes.post("/delete_account", requiresAccount, parser, async (req,res) => {
authRoutes.post("/delete_account", requiresAccount, noAPIAccess, parser, async (req,res) => {
let acc = res.locals.acc as Accounts.Account
let accId = acc.id
@ -217,7 +217,7 @@ authRoutes.post("/delete_account", requiresAccount, parser, async (req,res) => {
} else cpl()
})
authRoutes.post("/change_username", requiresAccount, parser, (req,res) => {
authRoutes.post("/change_username", requiresAccount, noAPIAccess, parser, (req,res) => {
let acc = res.locals.acc as Accounts.Account
if (typeof req.body.username != "string" || req.body.username.length < 3 || req.body.username.length > 20) {
@ -253,7 +253,7 @@ authRoutes.post("/change_username", requiresAccount, parser, (req,res) => {
let verificationCodes = new Map<string, {code: string, email: string, expiry: NodeJS.Timeout}>()
authRoutes.post("/request_email_change", requiresAccount, accountRatelimit({ requests: 4, per: 60*60*1000 }), parser, (req,res) => {
authRoutes.post("/request_email_change", requiresAccount, noAPIAccess, accountRatelimit({ requests: 4, per: 60*60*1000 }), parser, (req,res) => {
let acc = res.locals.acc as Accounts.Account
@ -292,7 +292,7 @@ authRoutes.post("/request_email_change", requiresAccount, accountRatelimit({ req
})
})
authRoutes.get("/confirm_email/:code", requiresAccount, (req,res) => {
authRoutes.get("/confirm_email/:code", requiresAccount, noAPIAccess, (req,res) => {
let acc = res.locals.acc as Accounts.Account
@ -314,7 +314,7 @@ authRoutes.get("/confirm_email/:code", requiresAccount, (req,res) => {
}
})
authRoutes.post("/remove_email", requiresAccount, (req,res) => {
authRoutes.post("/remove_email", requiresAccount, noAPIAccess, (req,res) => {
let acc = res.locals.acc as Accounts.Account
if (acc.email) {
@ -404,7 +404,7 @@ authRoutes.get("/emergency_login/:code", (req,res) => {
}
})
authRoutes.post("/change_password", requiresAccount, parser, (req,res) => {
authRoutes.post("/change_password", requiresAccount, noAPIAccess, parser, (req,res) => {
let acc = res.locals.acc as Accounts.Account
if (typeof req.body.password != "string" || req.body.password.length < 8) {
@ -429,7 +429,7 @@ authRoutes.post("/change_password", requiresAccount, parser, (req,res) => {
res.send("password changed - logged out all sessions")
})
authRoutes.post("/logout_sessions", requiresAccount, (req,res) => {
authRoutes.post("/logout_sessions", requiresAccount, noAPIAccess, (req,res) => {
let acc = res.locals.acc as Accounts.Account
let accId = acc.id
@ -441,32 +441,25 @@ authRoutes.post("/logout_sessions", requiresAccount, (req,res) => {
res.send("logged out all sessions")
})
authRoutes.get("/me", requiresAccount, (req,res) => {
authRoutes.get("/me", requiresAccount, requiresPermissions("user"), (req,res) => {
let acc = res.locals.acc as Accounts.Account
let sessionToken = auth.tokenFor(req)
let accId = acc.id
res.send({
...acc,
sessionCount: auth.AuthTokens.filter(e => e.account == accId && e.expire > Date.now()).length,
sessionExpires: auth.AuthTokens.find(e => e.token == req.cookies.auth)?.expire,
password: undefined
sessionCount: auth.AuthTokens.filter(e => e.type != "App" && e.account == accId && (e.expire > Date.now() || !e.expire)).length,
sessionExpires: auth.AuthTokens.find(e => e.token == sessionToken)?.expire,
password: undefined,
email:
auth.getType(sessionToken) == "User" || auth.getPermissions(sessionToken)?.includes("email")
? acc.email
: undefined
})
})
authRoutes.get("/customCSS", (req,res) => {
if (!auth.validate(req.cookies.auth)) {
ServeError(res, 401, "not logged in")
return
}
// lazy rn so
let acc = Accounts.getFromToken(req.cookies.auth)
if (acc) {
if (acc.customCSS) {
res.redirect(`/file/${acc.customCSS}`)
} else {
res.send("")
}
} else res.send("")
let acc = res.locals.acc
if (acc?.customCSS) res.redirect(`/file/${acc.customCSS}`)
else res.send("")
})

View file

@ -7,6 +7,7 @@ import {writeFile} from "fs";
import ServeError from "../lib/errors";
import Files from "../lib/files";
import { getAccount, requiresAccount, requiresPermissions } from "../lib/middleware";
let parser = bodyParser.json({
type: ["text/plain","application/json"]
@ -21,14 +22,11 @@ export function setFilesObj(newFiles:Files) {
let config = require(`${process.cwd()}/config.json`)
fileApiRoutes.get("/list", (req,res) => {
fileApiRoutes.use(getAccount);
if (!auth.validate(req.cookies.auth)) {
ServeError(res, 401, "not logged in")
return
}
fileApiRoutes.get("/list", requiresAccount, requiresPermissions("user"), (req,res) => {
let acc = Accounts.getFromToken(req.cookies.auth)
let acc = res.locals.acc as Accounts.Account
if (!acc) return
let accId = acc.id
@ -46,14 +44,9 @@ fileApiRoutes.get("/list", (req,res) => {
})
fileApiRoutes.post("/manage", parser, (req,res) => {
fileApiRoutes.post("/manage", parser, requiresPermissions("manage"), (req,res) => {
if (!auth.validate(req.cookies.auth)) {
ServeError(res, 401, "not logged in")
return
}
let acc = Accounts.getFromToken(req.cookies.auth) as Accounts.Account
let acc = res.locals.acc as Accounts.Account
if (!acc) return
if (!req.body.target || !(typeof req.body.target == "object") || req.body.target.length < 1) return

View file

@ -8,6 +8,7 @@ import multer, {memoryStorage} from "multer"
import ServeError from "../lib/errors";
import Files from "../lib/files";
import { getAccount, requiresPermissions } from "../lib/middleware";
let parser = bodyParser.json({
type: ["text/plain","application/json"]
@ -24,9 +25,12 @@ const multerSetup = multer({storage:memoryStorage()})
let config = require(`${process.cwd()}/config.json`)
primaryApi.use(getAccount);
primaryApi.get(["/file/:fileId", "/cpt/:fileId/*", "/:fileId"], async (req:express.Request,res:express.Response) => {
let acc = res.locals.acc as Accounts.Account
let file = files.getFilePointer(req.params.fileId)
res.setHeader("Access-Control-Allow-Origin", "*")
res.setHeader("Content-Security-Policy","sandbox allow-scripts")
@ -34,7 +38,7 @@ primaryApi.get(["/file/:fileId", "/cpt/:fileId/*", "/:fileId"], async (req:expre
if (file) {
if (file.visibility == "private" && Accounts.getFromToken(req.cookies.auth)?.id != file.owner) {
if (file.visibility == "private" && acc?.id != file.owner) {
ServeError(res,403,"you do not own this file")
return
}
@ -111,7 +115,10 @@ primaryApi.head(["/file/:fileId", "/cpt/:fileId/*", "/:fileId"], (req: express.R
// upload handlers
primaryApi.post("/upload",multerSetup.single('file'),async (req,res) => {
primaryApi.post("/upload", requiresPermissions("upload"), multerSetup.single('file'), async (req,res) => {
let acc = res.locals.acc as Accounts.Account
if (req.file) {
try {
let prm = req.header("monofile-params")
@ -121,7 +128,7 @@ primaryApi.post("/upload",multerSetup.single('file'),async (req,res) => {
}
files.uploadFile({
owner: auth.validate(req.cookies.auth),
owner: acc?.id,
uploadId:params.uploadId,
name:req.file.originalname,
@ -142,12 +149,15 @@ primaryApi.post("/upload",multerSetup.single('file'),async (req,res) => {
}
})
primaryApi.post("/clone", bodyParser.json({type: ["text/plain","application/json"]}) ,(req,res) => {
primaryApi.post("/clone", requiresPermissions("upload"), bodyParser.json({type: ["text/plain","application/json"]}) ,(req,res) => {
let acc = res.locals.acc as Accounts.Account
try {
axios.get(req.body.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => {
files.uploadFile({
owner: auth.validate(req.cookies.auth),
owner: acc?.id,
name:req.body.url.split("/")[req.body.url.split("/").length-1] || "generic",
mime:data.headers["content-type"],

View file

@ -70,7 +70,7 @@ body {
}
&::-webkit-scrollbar-track {
background-color:#191919;
background-color:#222222;
}
&::-webkit-scrollbar-thumb {

View file

@ -21,7 +21,7 @@
position: absolute;
width: 300px;
height: 400px;
background-color: #191919;
background-color: #222222;
color: #dddddd;
top:0px;

View file

@ -40,7 +40,7 @@
.searchBar {
transition-duration:150ms;
background-color:#171717;
background-color:#212121;
width:100%;
padding:8px;
color:#dddddd;

View file

@ -4,7 +4,7 @@
position:relative;
width:100%;
height:50px;
background-color: #191919;
background-color: #222222;
border:none;
border-bottom:1px solid #AAAAAA;
transition-duration:150ms;
@ -106,7 +106,7 @@
.modal {
position:absolute;
background-color:#191919;
background-color:#222222;
width:100%;
transform:translateY(-100%);
top:100%;

View file

@ -56,4 +56,4 @@
width:100%;
}
}
}
}

View file

@ -23,6 +23,20 @@
font-size: 14px;
}
h1 > button {
background-color: #0000 !important;
padding: 0;
margin-top: 5px;
img {
color: #666666;
}
&:hover {
img { color: #DDD; }
}
}
a {
color:#999;
}

View file

@ -1,22 +1,23 @@
<script>
import { _void } from "./transition/_void.js";
import { _void } from "./transition/_void.js"
import { padding_scaleY } from "./transition/padding_scaleY.js"
import { fade } from "svelte/transition";
import { circIn, circOut } from "svelte/easing";
import { serverStats, refresh_stats, account } from "./stores.mjs";
import { fade } from "svelte/transition"
import { circIn, circOut } from "svelte/easing"
import { serverStats, refresh_stats, account } from "./stores.mjs"
import AttachmentZone from "./uploader/AttachmentZone.svelte";
import AttachmentZone from "./uploader/AttachmentZone.svelte"
// stats
refresh_stats()
// uploads
let attachmentZone;
let uploads = {};
let uploadInProgress = false;
let attachmentZone
let uploads = {}
let uploadInProgress = false
let notificationPermission =
globalThis?.Notification?.permission ?? "denied"
let handle_file_upload = (ev) => {
if (ev.detail.type == "clone") {
uploads[Math.random().toString().slice(2)] = {
@ -25,31 +26,31 @@
url: ev.detail.url,
params: {
uploadId: ""
uploadId: "",
},
uploadStatus:{
uploadStatus: {
fileId: null,
error: null,
}
},
}
uploads = uploads
} else if (ev.detail.type == "upload") {
ev.detail.files.forEach((v,x) => {
ev.detail.files.forEach((v, x) => {
uploads[Math.random().toString().slice(2)] = {
type: "upload",
name: v.name,
file: v,
params: {
uploadId: ""
uploadId: "",
},
uploadStatus:{
uploadStatus: {
fileId: null,
error: null,
}
},
}
})
@ -57,18 +58,48 @@
}
}
let handle_fetch_promise = (x,prom) => {
return prom.then(async (res) => {
let txt = await res.text()
if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt;
else {
uploads[x].uploadStatus.fileId = txt;
refresh_stats();
}
}).catch((err) => {
uploads[x].uploadStatus.error = err.toString();
})
let handle_fetch_promise = (x, prom) => {
return prom
.then(async (res) => {
let txt = await res.text()
if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt
else {
uploads[x].uploadStatus.fileId = txt
try {
new Notification("Upload complete", {
body: `View at ${location.origin}/${uploads[x].uploadStatus.fileId}`,
actions: [
{
action: "open",
title: "Open",
},
{
action: "copy",
title: "Copy",
},
],
}).addEventListener(
"notificationclick",
({ action }) => {
if (action === "open") {
open(
"/download/" +
uploads[x].uploadStatus.fileId
)
} else {
navigator.clipboard.writeText(
`${location.origin}/${uploads[x].uploadStatus.fileId}`
)
}
}
)
} catch (_) {}
refresh_stats()
}
})
.catch((err) => {
uploads[x].uploadStatus.error = err.toString()
})
}
let upload_files = async () => {
@ -77,112 +108,224 @@
let sequential = localStorage.getItem("sequentialMode") == "true"
// go through all files
for (let [x,v] of Object.entries(uploads)) {
for (let [x, v] of Object.entries(uploads)) {
// quick patch-in to allow for a switch to have everything upload sequentially
// switch will have a proper menu option later, for now i'm lazy so it's just gonna be a Secret
let hdl = () => {
switch(v.type) {
switch (v.type) {
case "upload":
let fd = new FormData()
fd.append("file",v.file)
fd.append("file", v.file)
return handle_fetch_promise(x,fetch("/upload",{
headers: {
"monofile-params": JSON.stringify(v.params)
},
method: "POST",
body: fd
}))
break
case "clone":
return handle_fetch_promise(x,fetch("/clone",{
method: "POST",
body: JSON.stringify({
url: v.url,
...v.params
return handle_fetch_promise(
x,
fetch("/upload", {
headers: {
"monofile-params": JSON.stringify(v.params),
},
method: "POST",
body: fd,
})
}))
break
)
break
case "clone":
return handle_fetch_promise(
x,
fetch("/clone", {
method: "POST",
body: JSON.stringify({
url: v.url,
...v.params,
}),
})
)
break
}
}
if (sequential) await hdl();
else hdl();
if (sequential) await hdl()
else hdl()
}
}
// animation
function fileTransition(node) {
return {
duration: 300,
css: t => {
css: (t) => {
let eased = circOut(t)
return `
height: ${eased*(node.offsetHeight-22)}px;
padding: ${eased*10}px 10px;
height: ${eased * (node.offsetHeight - 22)}px;
padding: ${eased * 10}px 10px;
`
}
},
}
}
</script>
<div id="uploadWindow">
<h1>monofile</h1>
<h1>
monofile
{#if notificationPermission === "default"}
<button
on:click={() => {
Notification.requestPermission().then(
(permission) => (notificationPermission = permission)
)
}}
style="float:right"
title="Notify me when the upload finishes"
>
<svg
width="20"
height="20"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-label="Notify me when the upload finishes"
><path
d="M9.042 19.003h5.916a3 3 0 0 1-5.916 0Zm2.958-17a7.5 7.5 0 0 1 7.5 7.5v4l1.418 3.16A.95.95 0 0 1 20.052 18h-16.1a.95.95 0 0 1-.867-1.338l1.415-3.16V9.49l.005-.25A7.5 7.5 0 0 1 12 2.004Z"
fill="currentColor"
/></svg
>
</button>
{/if}
</h1>
<p style:color="#999999">
<span class="number">{$serverStats.version ? `v${$serverStats.version}` : "•••"}</span>&nbsp;&nbsp;&nbsp;&nbsp;Discord based file sharing
<span class="number"
>{$serverStats.version ? `v${$serverStats.version}` : "•••"}</span
>&nbsp;&nbsp;&nbsp;&nbsp;Discord based file sharing
</p>
<div style:min-height="10px" />
<!-- consider splitting the file thing into a separate element maybe -->
<div class="uploadContainer">
{#each Object.entries(uploads) as upload (upload[0])}
<!-- container to allow for animate directive -->
<div>
<div class="file" transition:fileTransition style:border={upload[1].uploadStatus.error ? "1px solid #BB7070" : ""}>
<h2>{upload[1].name} <span style:color="#999999" style:font-weight="400">{upload[1].type}{@html upload[1].type == "upload" ? `&nbsp;(${Math.round(upload[1].file.size/1048576)}MiB)` : ""}</span></h2>
<div
class="file"
transition:fileTransition
style:border={upload[1].uploadStatus.error
? "1px solid #BB7070"
: ""}
>
<h2>
{upload[1].name}
<span style:color="#999999" style:font-weight="400"
>{upload[1].type}{@html upload[1].type == "upload"
? `&nbsp;(${Math.round(
upload[1].file.size / 1048576
)}MiB)`
: ""}</span
>
</h2>
{#if upload[1].maximized && !uploadInProgress}
<div transition:padding_scaleY|local>
<div style:height="10px" />
<input placeholder="custom id" type="text" bind:value={ uploads[upload[0]].params.uploadId }>
<input
placeholder="custom id"
type="text"
bind:value={uploads[upload[0]].params.uploadId}
/>
<div style:height="10px" />
<div class="buttonContainer">
<button on:click={() => {delete uploads[upload[0]];uploads=uploads;}}>
<button
on:click={() => {
delete uploads[upload[0]]
uploads = uploads
}}
>
delete
</button>
<button on:click={() => uploads[upload[0]].maximized = false}>
<button
on:click={() =>
(uploads[upload[0]].maximized = false)}
>
minimize
</button>
</div>
</div>
{:else if !uploadInProgress}
<button on:click={() => uploads[upload[0]].maximized = true} class="hitbox"></button>
<button
on:click={() =>
(uploads[upload[0]].maximized = true)}
class="hitbox"
/>
{:else}
<div transition:padding_scaleY|local class="uploadingContainer">
<div
transition:padding_scaleY|local
class="uploadingContainer"
>
{#if !upload[1].uploadStatus.fileId}
<p in:fade={{duration:300, delay:400, easingFunc:circOut}} out:padding_scaleY={{easingFunc:circIn,op:true}}>{upload[1].uploadStatus.error ?? "Uploading..."}</p>
<p
in:fade={{
duration: 300,
delay: 400,
easingFunc: circOut,
}}
out:padding_scaleY={{
easingFunc: circIn,
op: true,
}}
>
{upload[1].uploadStatus.error ??
"Uploading..."}
</p>
{/if}
{#if upload[1].uploadStatus.fileId}
<div style:height="10px" transition:padding_scaleY />
<div
style:height="10px"
transition:padding_scaleY
/>
{#if !upload[1].viewingUrl}
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
<button on:click={() => uploads[upload[0]].viewingUrl = true}>
<div
class="buttonContainer"
out:_void
in:_void={{ easingFunc: circOut }}
>
<button
on:click={() =>
(uploads[
upload[0]
].viewingUrl = true)}
>
view url
</button>
<button on:click={() => navigator.clipboard.writeText(`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`)}>
<button
on:click={() =>
navigator.clipboard.writeText(
`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`
)}
>
copy url
</button>
</div>
{:else}
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
<input type="text" readonly value={`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`} style:flex-basis="80%">
<button on:click={() => uploads[upload[0]].viewingUrl = false} style:flex-basis="20%">
<div
class="buttonContainer"
out:_void
in:_void={{ easingFunc: circOut }}
>
<input
type="text"
readonly
value={`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`}
style:flex-basis="80%"
/>
<button
on:click={() =>
(uploads[
upload[0]
].viewingUrl = false)}
style:flex-basis="20%"
>
ok
</button>
</div>
@ -195,37 +338,73 @@
</div>
{/each}
</div>
{#if uploadInProgress == false}
<!-- if required for upload, check if logged in -->
{#if ($serverStats.accounts||{}).requiredForUpload ? !!$account.username : true}
<AttachmentZone bind:this={attachmentZone} on:addFiles={handle_file_upload}/>
<div style:min-height="10px" transition:_void={{rTarg:"height",prop:"min-height"}} />
{#if ($serverStats.accounts || {}).requiredForUpload ? !!$account.username : true}
<AttachmentZone
bind:this={attachmentZone}
on:addFiles={handle_file_upload}
/>
<div
style:min-height="10px"
transition:_void={{ rTarg: "height", prop: "min-height" }}
/>
{#if Object.keys(uploads).length > 0}
<button in:padding_scaleY={{easingFunc:circOut}} out:_void on:click={upload_files}>upload</button>
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" />
<button
in:padding_scaleY={{ easingFunc: circOut }}
out:_void
on:click={upload_files}>upload</button
>
<div
transition:_void={{ rTarg: "height", prop: "min-height" }}
style:min-height="10px"
/>
{/if}
{:else}
<p transition:_void style:color="#999999" style:text-align="center">Please log in to upload files.</p>
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" />
<p transition:_void style:color="#999999" style:text-align="center">
Please log in to upload files.
</p>
<div
transition:_void={{ rTarg: "height", prop: "min-height" }}
style:min-height="10px"
/>
{/if}
{/if}
<p style:color="#999999" style:text-align="center">
Hosting <span class="number" style:font-weight="600">{$serverStats.files || "•••"}</span> files
Maximum filesize is <span class="number" style:font-weight="600">{(($serverStats.maxDiscordFileSize || 0)*($serverStats.maxDiscordFiles || 0))/1048576 || "•••"}MiB</span>
Hosting <span class="number" style:font-weight="600"
>{$serverStats.files || "•••"}</span
>
files — Maximum filesize is
<span class="number" style:font-weight="600"
>{(($serverStats.maxDiscordFileSize || 0) *
($serverStats.maxDiscordFiles || 0)) /
1048576 || "•••"}MiB</span
>
<br />
</p>
<p style:color="#999999" style:text-align="center" style:font-size="12px">
Made with {Math.floor(Math.random()*10)==0 ? "🐟" : "❤"} by <a href="https://github.com/nbitzz" style:font-size="12px">@nbitzz</a><a href="https://github.com/nbitzz/monofile" style:font-size="12px">source</a>
Made with {Math.floor(Math.random() * 10) == 0 ? "🐟" : "❤"} by
<a href="https://cetera.uk" style:font-size="12px"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 173.8 275.72"
height="16"
style="vertical-align:middle"
fill="currentColor"
>
<circle cx="37.13" cy="26.43" r="21.6" />
<circle cx="34.62" cy="117.87" r="34.62" class="middle" />
<circle cx="119.78" cy="130.68" r="21.6" class="middle" />
<circle cx="127.16" cy="46.64" r="46.65" />
<circle cx="102.68" cy="219.58" r="56.14" class="bottom" />
</svg> Etcetera</a
>
<a href="https://github.com/mollersuite/monofile" style:font-size="12px"
>source</a
>
</p>
<div style:height="10px" />
</div>
</div>