Compare commits

...

327 Commits

Author SHA1 Message Date
diced
adb984b2db ima kms 2023-04-30 15:21:25 -07:00
dicedtomato
3be9f1521e Merge branch 'trunk' into feature/file-tags 2023-04-04 20:08:03 -07:00
Jayvin Hernandez
5ded128263 fix: user uuid (#355)
* fix: user uuid is used instead of user id for its uniqueness

* fix: use cuid instead & exclude from parser

* fix: apply new foreign key constraints to existing data

* fix: migration partly done

* not-fix: General form of migration achieved, still broken

* fix: migrate and use db's uuid function for existing users

* fix: Proper not nulling!

* fix: #354

* fix: migration & use uuid instead

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
Co-authored-by: diced <pranaco2@gmail.com>
2023-04-04 20:07:41 -07:00
dicedtomato
5d971a9fef Merge branch 'trunk' into feature/file-tags 2023-04-04 19:13:05 -07:00
diced
2c86abbf4e feat: file tags (experimental) 2023-04-04 19:12:06 -07:00
dicedtomato
eedeb89c7d feat: offloaded chunked uploads (#356)
* feat: offloaded chunked uploads

* fix: use temp_directory instead of tmpdir()

* feat: CHUNKS_ENABLED config
2023-04-03 22:42:27 -07:00
Jayvin Hernandez
bf40fa9cd2 feat: many things (#351)
* remove source from final image

* move check state to ClearStorage

* use inspect for fancy colors

* newlines are now possible! yay!

* Catch user's leave if uploading

* feat?: Temp directory can be specified by the user.
Default is /tmp/zipline (or os equivalent)

* fix: ignore onDash config, use only ?compress query

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-03-31 22:25:00 -07:00
diced
bc58c1b56e fix: milestone again again again again again again 2023-03-31 22:12:13 -07:00
diced
c57a6e1700 fix: milestone again again again again again 2023-03-31 22:07:17 -07:00
diced
8649a489d8 fix: milestone again again again again 2023-03-31 22:05:59 -07:00
diced
40f29907c7 fix: milestone again again again 2023-03-31 21:55:26 -07:00
diced
34005ece43 fix: milestone again again 2023-03-31 21:53:30 -07:00
diced
8e6fc1e8a3 fix: milestone again 2023-03-31 21:49:50 -07:00
diced
065f44b145 fix: milestone 2023-03-31 21:41:19 -07:00
diced
e5a07f568d fix: update milestone action 2023-03-27 16:48:56 -07:00
dicedtomato
a728d71da1 Merge branch '3.x' into trunk 2023-03-26 20:46:00 -07:00
diced
91e468791e feat(3.7.0): version 2023-03-26 20:45:01 -07:00
dicedtomato
169a2ea562 Revert "Release 3.7.0 (#328)" (#347)
This reverts commit f9060f8ae7.
2023-03-26 20:40:41 -07:00
dicedtomato
f9060f8ae7 Release 3.7.0 (#328)
* fix: oauthId optional

* fix: remove optional

* hotfix: make oauthid optional (#249)

* hotfix: fallback oauth find (#250)

* fix: add a forgotten ? to schema

* fix: catch null at other places (#252)

* fix: forgor (#253)

* Fix root url & uploader stuff (#254)

* fix: uploader route as root won't be broken

* fix: fix broken url route for when on root

* fix: catch hopefully the most of the edge cases (#251)

* fix: catch hopefully the most of the edge cases

* fix: invite only, fools

* feat: tsup, small fixes

* fix: #264

* fix: urls handle empty strings

* fix: remove esbuild

* fix: do not mutate res #266

* feat: new embed method

* fix: overwrite tmp ss flameshot

* fix: overrides for uploading

* refactor: chart.js -> recharts

* feat: download query on /r/

* fix: better icons on file vie2

* feat: ability to generate url shorten config

* fix: sxcu name

* fix: react hooks error

* feat: ability to insert tabs on Tab keypress

* feat: overhaul image upload

* fix: clean

* fix: group icons vertically

* fix: embeds not showing up

* fix: docker stuff

* feat(3.7.0-rc1): version

* fix: cors for files (#257)

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* refactor: many columns/tables in prisma

* feat: keep original name #247

* fix: ability to gen with original-name

* fix: type error

* fix: no name on dashboard

* feat(v3.7.0-rc2): version

* fix: sharex DestinationType

* fix: ensureDatabaseExists args

* fix: sharex config

* fix: #277 #272

* fix: 🐛 Add Menu component as parent

* refactor: popover -> menu

Co-authored-by: IceToast <>
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* fix: add a "skip" for fresh db's (#274)

* fix: add a "skip" for fresh db's

* fix: trimming

* fix: elevate logging!

* fix: allow more variables on view

* fix: optimize docker image

* fix:  root url for upload and shorten (#255)

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* fix: /app -> /zipline

* feat(v3.7.0-rc3): folders for files

* fix: use `name` instead of `file` (#281)

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* feat: search+create for folder select (#283)

* feat?: Search for the folder to add.
Also you can create a folder right from the file, rather than being redirected.

* woops wrong import

* fix: return null for no string in parser (#285)

* feat: use ENTRYPOINT in docker (#286)

* :3

* Update Dockerfile

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* Update Dockerfile

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* Update Dockerfile

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* test

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* fix: set password to actual text value

* fix: url encode password query

* fix: entrypoint executable (#289)

* feat: override domain header

* fix: random domains

* feat: better version checking

* feat: public folders

* fix: dates #278

* feat: devcontainers for codespaces, etc.

* experiment with devcontainer.json

* introduce a docker-compose for devcontainer

* Devcontainers!

* version pop

* Port labeling and a complimentary env variable

* see it to believe it

* Update .devcontainer/devcontainer.json

* Update .devcontainer/devcontainer.json

* Update .devcontainer/docker-compose.yml

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* fix: spaces and route fixes (#294)

* fix: spaces and route fixes

* fix: shorten url response

* feat: better version checking

* fix: use special characters should work

If it doesn't, better call saul

* save that extra byte

* fix: returning protocol again in domain

unrelated to this pr but whatever

* fix: above ^

* Rename shorten.tsv to shorten.ts

---------

Co-authored-by: diced <pranaco2@gmail.com>
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* fix: #296

* fix: show files per user (#299)

* feat: clearing orphaned files (#303)

* fix: default public folder (docker)

* feat: seperate discord webhooks (shorten/upload) (#260)

* fix: title for folders

* fix: clipboard & 2fa improvements

A workaround that shows the content that would have been copied if `navigator.clipboard` is unavailable for whatever reason.

2FA input autofocuses & submits on enter.

* fix: revamp uploaded file modal

* fix: revamp mobile ui

* feat: more functionality within files table

* feat: clear zero byte files script

* feat: logger improvements
- Timestamp is gray
- removed colorette dependency
- introduction of LOGGER_FILTERS

* chore: update deps

* feat(v3.7.0-rc4): version

* fix: show warning when password protect

* fix: fix (#310)

* Muted audio by default!

* Code renderin'

* not but still decently standard files being viewable

* reserved routes

* Update validateConfig.ts

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* feat: file size (#308)

* feat: baseline support for file sizes

* feat: script to add file sizes

* fix: #311

* chore: update to mantine@6

* refactor: remove old File.tsx

* feat: initial move to mantine v6

* feat: use api option

* remove: useless size modifier

* fix: user button

* feat: use pininput for 2fa

* fix: breaking changes in migrating mantine v6

---------

Co-authored-by: TacticalCoderJay <gogojayvin923@gmail.com>

* feat: add size to datatable

* fix: null on non originalName

* fix: allow download query on non raw

* fix: undef file

* fix: spacingg between count_by_user

* feat: new ui for shortened urls

* fix: spacing within appshell/paper

* feat: new login page

* feat: reorganize menu

* feat: keyboard spotlight

* feat: tabler icons

* fix: remove feather import

* fix: update 2fa enabled appropriately & delete files (#315)

* fix: update 2fa enabled appropriately

* fix: a proper delete

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* feat(v3.7.0-rc5): version

* feat: add feature request "contact_link"

* feat: multiple stuffs

* feat: gfycat url #322

* feat: gfycat attribution

* feat(3.7.0-rc6): version

* fix: type cast

* feat: list view for urls, invites, users: #302

* refactor: docker-compose -> docker compose

* fix: open folder in new tab

* fix: save list-view setting to localStorage

* fix: Bug: URLs list view #330

* fix: #332

* fix: #331

* fix: #333

* feat: link to view gallery (icon)

* fix: clean up Anchors

* refactor: new eslint changes

* fix: fine tune devcontainer (#329)

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* fix: FileModal scrollbars

* fix: dynamically import katex

* fix: remove rogue console.log

* fix: open folder onRowClick

* fix: filter on usePaginatedFiles

* fix: icon sizes

* fix: paste listener

* feat(actions): auto-assign milestone

* feat(v3.7.0-rc7): version

* fix: #339

* fix: resetting avatars

* feat: new icons / oauth icons changed

* feat: UPLOADER_ASSUME_MIMETYPES (#337)

* fix: any instance of #342

* fix: any instance of #345

* fix: make tables take entire vh

* chore: update deps

* fix: add bigger sizeLimit

* fix: token exposed on view/[id]

---------

Co-authored-by: Jayvin Hernandez <gogojayvin923@gmail.com>
Co-authored-by: IceToast <54889359+IceToast@users.noreply.github.com>
Co-authored-by: IThundxr <harshdhaliwal9767@gmail.com>
Co-authored-by: IThundxr <contact@ithundxr.dev>
2023-03-26 20:40:01 -07:00
diced
d379bf8b1c fix: token exposed on view/[id] 2023-03-26 12:14:41 -07:00
diced
67b71ceffe fix: add bigger sizeLimit 2023-03-26 00:17:59 -07:00
diced
eb6929b889 chore: update deps 2023-03-26 00:03:10 -07:00
diced
d7299f8220 fix: make tables take entire vh 2023-03-25 23:39:16 -07:00
diced
1ed267ad94 fix: any instance of #345 2023-03-25 23:36:16 -07:00
diced
40a0cce3e8 fix: any instance of #342 2023-03-25 23:33:37 -07:00
diced
556aafaad3 feat: UPLOADER_ASSUME_MIMETYPES (#337) 2023-03-24 17:57:56 -07:00
diced
fdc7901eff feat: new icons / oauth icons changed 2023-03-24 17:56:53 -07:00
diced
9632399f5d fix: resetting avatars 2023-03-24 17:24:15 -07:00
diced
cc8a5411ab fix: #339 2023-03-24 17:20:33 -07:00
diced
12bb804e6a feat(v3.7.0-rc7): version 2023-03-22 20:27:52 -07:00
diced
3a27f31a03 feat(actions): auto-assign milestone 2023-03-22 19:41:06 -07:00
diced
37e7ad840c fix: paste listener 2023-03-22 19:36:26 -07:00
diced
c57a1ea326 fix: icon sizes 2023-03-21 20:06:46 -07:00
diced
12d5d5f08f fix: filter on usePaginatedFiles 2023-03-21 19:57:16 -07:00
diced
e7cf44e8e9 fix: open folder onRowClick 2023-03-21 19:53:59 -07:00
diced
a81f797266 fix: remove rogue console.log 2023-03-21 19:51:02 -07:00
diced
6ada79017a fix: dynamically import katex 2023-03-21 19:46:13 -07:00
diced
bdf34bbbbf fix: FileModal scrollbars 2023-03-21 19:31:45 -07:00
Jayvin Hernandez
c0d1b3d887 fix: fine tune devcontainer (#329)
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-03-21 18:01:02 -07:00
diced
1b505d463c refactor: new eslint changes 2023-03-21 17:53:22 -07:00
diced
25606a80ec fix: clean up Anchors 2023-03-21 17:09:50 -07:00
diced
8b540bff62 feat: link to view gallery (icon) 2023-03-21 16:36:47 -07:00
diced
8a2064e09d fix: #333 2023-03-21 16:30:01 -07:00
diced
1f0fb32b9b fix: #331 2023-03-20 22:50:28 -07:00
diced
3cbc345c00 fix: #332 2023-03-20 22:49:24 -07:00
diced
3c66c18c77 fix: Bug: URLs list view #330 2023-03-20 22:37:30 -07:00
diced
bcc816ea55 fix: save list-view setting to localStorage 2023-03-20 22:36:10 -07:00
diced
eb2713bc23 fix: open folder in new tab 2023-03-20 22:25:56 -07:00
diced
bcd68ae98b refactor: docker-compose -> docker compose 2023-03-19 16:48:08 -07:00
diced
d1a486ac1f feat: list view for urls, invites, users: #302 2023-03-19 16:44:04 -07:00
diced
0d36f5f091 fix: type cast 2023-03-18 21:57:41 -07:00
diced
3d5cdf50e6 feat(3.7.0-rc6): version 2023-03-18 21:54:20 -07:00
diced
1e81822c11 feat: gfycat attribution 2023-03-18 21:52:14 -07:00
diced
f8cd847588 feat: gfycat url #322 2023-03-18 21:48:19 -07:00
Jayvin Hernandez
5b9b454330 feat: multiple stuffs 2023-03-18 20:52:04 -07:00
dicedtomato
9c5b3f60d5 feat: add feature request "contact_link" 2023-03-09 20:51:07 -08:00
diced
d83c255382 feat(v3.7.0-rc5): version 2023-03-04 22:08:09 -08:00
Jayvin Hernandez
656b900256 fix: update 2fa enabled appropriately & delete files (#315)
* fix: update 2fa enabled appropriately

* fix: a proper delete

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-03-04 22:07:29 -08:00
diced
a16b516163 fix: remove feather import 2023-03-04 21:56:47 -08:00
diced
6d8e66478c feat: tabler icons 2023-03-04 21:51:58 -08:00
diced
4428555762 feat: keyboard spotlight 2023-03-04 19:57:09 -08:00
diced
463e91c3bd feat: reorganize menu 2023-03-04 18:48:05 -08:00
diced
1e37f06ab6 feat: new login page 2023-03-04 18:33:30 -08:00
diced
3af3ba69f5 fix: spacing within appshell/paper 2023-03-04 18:11:11 -08:00
diced
0adc07ac38 feat: new ui for shortened urls 2023-03-04 18:04:22 -08:00
diced
4fe4faa202 fix: spacingg between count_by_user 2023-03-04 17:58:35 -08:00
diced
4912a872e0 fix: undef file 2023-03-04 17:51:40 -08:00
diced
ac05d82e3a fix: allow download query on non raw 2023-03-04 17:50:14 -08:00
diced
6583f1114c fix: null on non originalName 2023-03-04 17:47:40 -08:00
diced
e2673fa9e1 feat: add size to datatable 2023-03-04 17:20:49 -08:00
dicedtomato
bc4b528ac6 chore: update to mantine@6
* refactor: remove old File.tsx

* feat: initial move to mantine v6

* feat: use api option

* remove: useless size modifier

* fix: user button

* feat: use pininput for 2fa

* fix: breaking changes in migrating mantine v6

---------

Co-authored-by: TacticalCoderJay <gogojayvin923@gmail.com>
2023-03-04 17:08:43 -08:00
dicedtomato
986858345e fix: #311 2023-03-04 04:52:00 +00:00
dicedtomato
912e439645 feat: file size (#308)
* feat: baseline support for file sizes

* feat: script to add file sizes
2023-03-03 20:40:28 -08:00
Jayvin Hernandez
8e44b71614 fix: fix (#310)
* Muted audio by default!

* Code renderin'

* not but still decently standard files being viewable

* reserved routes

* Update validateConfig.ts

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-03-03 20:19:19 -08:00
dicedtomato
11bca28ef5 fix: show warning when password protect 2023-03-04 04:15:24 +00:00
dicedtomato
4ef0c6021a feat(v3.7.0-rc4): version 2023-02-27 03:46:50 +00:00
dicedtomato
4fbbd58ae9 chore: update deps 2023-02-27 03:46:27 +00:00
dicedtomato
81dea6cf90 feat: logger improvements
- Timestamp is gray
- removed colorette dependency
- introduction of LOGGER_FILTERS
2023-02-27 01:54:37 +00:00
dicedtomato
9b57fb280b feat: clear zero byte files script 2023-02-27 01:52:39 +00:00
dicedtomato
e804d0b31e feat: more functionality within files table 2023-02-27 01:52:22 +00:00
dicedtomato
76845fc7e4 fix: revamp mobile ui 2023-02-26 20:45:20 +00:00
dicedtomato
decd7f7918 fix: revamp uploaded file modal 2023-02-26 20:20:36 +00:00
Jayvin Hernandez
8c5ff4f230 fix: clipboard & 2fa improvements
A workaround that shows the content that would have been copied if `navigator.clipboard` is unavailable for whatever reason.

2FA input autofocuses & submits on enter.
2023-02-26 11:33:57 -08:00
dicedtomato
0848702f65 fix: title for folders 2023-02-26 04:57:44 +00:00
Jayvin Hernandez
5379374135 feat: seperate discord webhooks (shorten/upload) (#260) 2023-02-25 20:47:14 -08:00
Jayvin Hernandez
b7772128d7 fix: default public folder (docker) 2023-02-25 20:36:05 -08:00
Jayvin Hernandez
95a1c7f92c feat: clearing orphaned files (#303) 2023-02-25 20:35:08 -08:00
Jayvin Hernandez
2d69cd580a fix: show files per user (#299) 2023-02-23 21:33:12 -08:00
dicedtomato
34552926d1 fix: #296 2023-02-24 00:18:03 +00:00
Jayvin Hernandez
739f584921 fix: spaces and route fixes (#294)
* fix: spaces and route fixes

* fix: shorten url response

* feat: better version checking

* fix: use special characters should work

If it doesn't, better call saul

* save that extra byte

* fix: returning protocol again in domain

unrelated to this pr but whatever

* fix: above ^

* Rename shorten.tsv to shorten.ts

---------

Co-authored-by: diced <pranaco2@gmail.com>
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-02-23 16:16:11 -08:00
Jayvin Hernandez
04d8b6421a feat: devcontainers for codespaces, etc.
* experiment with devcontainer.json

* introduce a docker-compose for devcontainer

* Devcontainers!

* version pop

* Port labeling and a complimentary env variable

* see it to believe it

* Update .devcontainer/devcontainer.json

* Update .devcontainer/devcontainer.json

* Update .devcontainer/docker-compose.yml

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-02-23 16:00:59 -08:00
diced
fdcd1f3d28 fix: dates #278 2023-02-23 14:42:39 -08:00
diced
fc02dc02e8 feat: public folders 2023-02-23 14:37:22 -08:00
diced
6955d83b0c feat: better version checking 2023-02-20 23:17:04 -08:00
diced
1b3d3a867b fix: random domains 2023-02-18 14:08:49 -08:00
diced
83718d7b31 feat: override domain header 2023-02-18 11:19:50 -08:00
IThundxr
e80627a3c3 fix: entrypoint executable (#289) 2023-02-18 10:02:40 -08:00
diced
e1003d4bb6 fix: url encode password query 2023-02-17 19:45:04 -08:00
diced
2ef4a52be0 fix: set password to actual text value 2023-02-17 19:44:51 -08:00
IThundxr
93a63d3714 feat: use ENTRYPOINT in docker (#286)
* :3

* Update Dockerfile

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* Update Dockerfile

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* Update Dockerfile

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* test

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-02-17 19:09:50 -08:00
Jayvin Hernandez
a8d9d98cf2 fix: return null for no string in parser (#285) 2023-02-16 16:47:07 -08:00
Jayvin Hernandez
d70ddd1f53 feat: search+create for folder select (#283)
* feat?: Search for the folder to add.
Also you can create a folder right from the file, rather than being redirected.

* woops wrong import
2023-02-13 19:37:47 -08:00
Jayvin Hernandez
283c7c5a26 fix: use name instead of file (#281)
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-02-10 22:47:06 -08:00
diced
fb5f50d5bd feat(v3.7.0-rc3): folders for files 2023-02-10 22:32:57 -08:00
diced
06e84b41aa fix: /app -> /zipline 2023-01-28 10:28:29 -08:00
Jayvin Hernandez
e3f262322a fix: root url for upload and shorten (#255)
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-01-26 20:06:30 -08:00
diced
70c2fa8ef4 fix: optimize docker image 2023-01-26 15:58:22 -08:00
diced
9f534e18c8 fix: allow more variables on view 2023-01-26 15:58:06 -08:00
Jayvin Hernandez
55bd72aef8 fix: add a "skip" for fresh db's (#274)
* fix: add a "skip" for fresh db's

* fix: trimming

* fix: elevate logging!
2023-01-26 12:46:10 -08:00
IceToast
c1a23faf1f fix: #277 #272
* fix: 🐛 Add Menu component as parent

* refactor: popover -> menu

Co-authored-by: IceToast <>
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-01-25 17:43:16 -08:00
dicedtomato
3588c297f8 fix: sharex config 2023-01-19 04:44:37 +00:00
diced
04d03cbc8f fix: ensureDatabaseExists args 2023-01-18 09:25:54 -08:00
diced
4e27efb6a1 fix: sharex DestinationType 2023-01-18 09:05:12 -08:00
diced
59b3e5bb24 feat(v3.7.0-rc2): version 2023-01-15 21:09:51 -08:00
diced
d8eee3d81a fix: no name on dashboard 2023-01-15 21:07:11 -08:00
diced
c8926682b2 fix: type error 2023-01-15 16:58:26 -08:00
diced
9117a9d779 fix: ability to gen with original-name 2023-01-15 14:05:37 -08:00
diced
4ea1775f2c feat: keep original name #247 2023-01-15 13:57:28 -08:00
diced
a8020ecebe refactor: many columns/tables in prisma 2023-01-15 13:39:07 -08:00
Jayvin Hernandez
2ace076fce fix: cors for files (#257)
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-01-15 11:26:30 -08:00
diced
45e897d475 feat(3.7.0-rc1): version 2023-01-14 15:27:21 -08:00
diced
98676f0573 fix: docker stuff 2023-01-14 15:25:36 -08:00
diced
c966ab9a52 fix: embeds not showing up 2023-01-14 13:50:56 -08:00
diced
ebaf11ad10 fix: group icons vertically 2023-01-14 13:27:23 -08:00
diced
19c7ba03c6 fix: clean 2023-01-14 13:20:04 -08:00
diced
894b5c5c6c feat: overhaul image upload 2023-01-14 12:04:30 -08:00
diced
516e93cee2 feat: ability to insert tabs on Tab keypress 2023-01-14 11:27:59 -08:00
diced
cc0ffc6e60 fix: react hooks error 2023-01-14 11:21:56 -08:00
diced
a97ace6e73 fix: sxcu name 2023-01-14 10:30:24 -08:00
diced
6d49463dad feat: ability to generate url shorten config 2023-01-14 10:25:54 -08:00
diced
81e6e4e5f2 fix: better icons on file vie2 2023-01-13 19:40:15 -08:00
diced
2adb355183 feat: download query on /r/ 2023-01-13 17:28:38 -08:00
diced
5e6c53432b refactor: chart.js -> recharts 2023-01-13 17:20:40 -08:00
diced
873f77bc43 fix: overrides for uploading 2023-01-12 20:17:25 -08:00
diced
9bf098a93a fix: overwrite tmp ss flameshot 2023-01-11 21:44:24 -08:00
diced
388713a3c6 feat: new embed method 2023-01-11 21:33:01 -08:00
diced
e94dd58542 fix: do not mutate res #266 2023-01-10 21:49:59 -08:00
diced
d985a1c588 fix: remove esbuild 2023-01-06 15:08:18 -08:00
diced
dbac6e8918 fix: urls handle empty strings 2023-01-06 15:06:34 -08:00
diced
a481c0ee5e fix: #264 2023-01-06 15:04:43 -08:00
diced
eef6fdaeb3 feat: tsup, small fixes 2023-01-06 14:45:48 -08:00
Jayvin Hernandez
b8b1a5bba6 fix: catch hopefully the most of the edge cases (#251)
* fix: catch hopefully the most of the edge cases

* fix: invite only, fools
2022-12-29 20:39:32 -08:00
Jayvin Hernandez
f06f52fce7 Fix root url & uploader stuff (#254)
* fix: uploader route as root won't be broken

* fix: fix broken url route for when on root
2022-12-18 17:29:50 -08:00
Jayvin Hernandez
4a332bb77b fix: forgor (#253) 2022-12-18 16:07:38 -08:00
Jayvin Hernandez
eb1b202566 fix: catch null at other places (#252) 2022-12-18 15:05:54 -08:00
diced
658f3a1a09 fix: add a forgotten ? to schema 2022-12-17 14:12:44 -08:00
Jayvin Hernandez
55eba480ac hotfix: fallback oauth find (#250) 2022-12-17 13:18:15 -08:00
Jayvin Hernandez
bbeea5b0ec hotfix: make oauthid optional (#249) 2022-12-17 09:37:29 -08:00
diced
ad454a94ef fix: remove optional 2022-12-17 09:33:07 -08:00
diced
268215ff5f fix: oauthId optional 2022-12-17 09:06:00 -08:00
diced
4e70daa4d8 feat(v3.6.4): version 2022-12-15 20:13:45 -08:00
diced
bb28f49cf5 feat: default avatar stuffs 2022-12-15 20:09:02 -08:00
diced
d85211a145 feat: better nav bar stuff 2022-12-15 20:09:02 -08:00
diced
a7291d374d fix: favorite pagination num 2022-12-15 20:09:02 -08:00
Jayvin Hernandez
5c9b558ac2 chore: update the readme (#246)
feature requests are now discussion threads instead of issues

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2022-12-15 18:09:36 -08:00
diced
36ede22d45 fix: readme sheilds.io images 2022-12-15 18:02:10 -08:00
diced
6528ec4056 fix: add default exit transition to modals 2022-12-14 22:46:41 -08:00
diced
56ee494c7d fix: add wayland instructions in flameshot builder 2022-12-14 22:45:10 -08:00
diced
b21995a0b9 fix: cleanup old methods from /api/user/files 2022-12-14 22:18:50 -08:00
diced
3c00575ecd feat: new system for paged files 2022-12-13 23:32:57 -08:00
diced
27ccbcb54a chore: pg latest -> pg 15 2022-12-13 22:26:50 -08:00
diced
fecbf394c1 fix: remove comments 2022-12-11 15:37:59 -08:00
diced
91341e2d21 feat: new variables parser 2022-12-11 15:30:19 -08:00
TacticalCoderJay
6349503b00 feat: clear storage (#244) (#239)
* - fix: use oauth's user id instead of username
 - feat: add login only config for oauth

* Addresses tomato's concerns

* fix: catch same account on different user

* Add storage cleaning

Co-authored-by: dicedtomato <diced@users.noreply.github.com>

* Update src/components/pages/Manage/index.tsx

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

* Update src/components/pages/Manage/index.tsx

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>

Co-authored-by: dicedtomato <diced@users.noreply.github.com>
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2022-12-10 15:00:39 -08:00
diced
58e8c103b7 fix: change CORE_HTTPS To CORE_RETURN_HTTPS in compose file 2022-12-10 14:24:34 -08:00
diced
5d115afa71 feat: update deps & fix stuff 2022-12-10 14:19:53 -08:00
diced
d8b308a18c fix: bind 2022-12-09 19:37:40 -08:00
diced
76267c00d5 fix: attempt to fix #243 2022-12-09 19:31:35 -08:00
diced
9648856052 chore: update prisma-binaries 2022-12-09 18:09:58 -08:00
diced
d87e465a8e fix: undefined 2022-12-08 18:53:35 -08:00
diced
2c07d6719e feat: save showNonMedia checkbox value 2022-12-08 18:50:00 -08:00
diced
4c633eb60d fix: clean up 2022-12-07 23:11:19 -08:00
diced
ba6580e4ef fix: route not found for nextjs api routes 2022-12-07 23:10:43 -08:00
diced
c21d8f837e feat: built-in ssl support
- CORE_HTTPS is now CORE_RETURN_HTTPS
- SSL_(KEY/CERT/ALLOW_HTTP1)
2022-12-07 19:40:54 -08:00
diced
eadfa09570 refactor: migrate to fastify
- (maybe) faster http server
- easy to develop on
2022-12-07 19:21:26 -08:00
TacticalCoderJay
ea1a0b7fc8 fix: new oauth stuff (#240)
* - fix: use oauth's user id instead of username
 - feat: add login only config for oauth

* Addresses tomato's concerns

* fix: catch same account on different user
2022-12-06 21:40:13 -08:00
diced
9f797613d2 fix: render in files & fix typos 2022-12-06 17:19:02 -08:00
diced
b728ff33ec fix: delete URLs after serving 2022-12-06 16:35:38 -08:00
diced
7dc036c6e4 fix: order urls by desc 2022-12-06 16:30:47 -08:00
diced
78135aac02 fix: dynamically import prism languages 2022-12-05 19:57:14 -08:00
diced
950018673f feat: render tex (katex) and markdown 2022-12-05 18:16:31 -08:00
diced
cfdcf05135 feat: ability to use / for URLS_ROUTE 2022-12-03 14:31:13 -08:00
diced
ace474eb2c feat(v3.6.3): version 2022-12-03 08:37:37 -08:00
diced
285ed8d56e fix: actually fix recursive thing 2022-12-03 08:02:16 -08:00
diced
738e25feda fix: exiftool not working on docker 2022-12-02 17:35:47 -08:00
diced
6d2d071293 fix: max call stack error 2022-12-02 08:37:13 -08:00
diced
725ce50608 feat(V3.6.2): version 2022-12-01 09:47:09 -08:00
diced
78e884e97e feat: default expiration/ttl (#237) 2022-12-01 09:42:16 -08:00
diced
cb123cb575 fix: formatting 2022-12-01 09:31:29 -08:00
diced
6f3081cb8e feat: headless mode 2022-12-01 09:27:14 -08:00
dicedtomato
231f734fd5 Update README.md 2022-11-28 20:47:24 -08:00
TacticalCoderJay
fce7325a24 fix: add onDelete to all relations (#236) 2022-11-28 20:39:20 -08:00
diced
2bec45411f feat: overhaul upload frontend 2022-11-28 19:58:21 -08:00
diced
577195b578 fix: serve favicon.ico always 2022-11-27 20:06:22 -08:00
diced
a402227c4f fix: custom placeholder 2022-11-27 19:55:14 -08:00
TacticalCoderJay
a75b790654 fix: allow root route & remove swift refs (#235) (#214)
* fix:
 - readd root route for uploads only
 - catch 1 edge case for root route (/dashboard)

* fix: spelt dashboard right

* fix: include the dot for the extension

* fix: remove any possible references of swift

* fix: missed a spot

* Update .env.local.example

Co-authored-by: Jonathan <axis@axis.moe>

* Update .env.local.example

* Update validateConfig.ts

* format

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
Co-authored-by: Jonathan <axis@axis.moe>
2022-11-27 18:20:29 -08:00
TacticalCoderJay
f07cbeac52 fix: small bug & no file ext (#234)
* fix: allow empty file extensions

* fix: Add a button to show non-media files

* fix: Looks better

* Update FilePagation.tsx

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2022-11-26 16:15:09 -08:00
diced
dcfcce7803 update readme with stuff 2022-11-26 14:33:42 -08:00
diced
659868181d feat: supabase datasource & remove swift 2022-11-25 14:30:18 -08:00
diced
d76e6444e0 fix: dumb cors headers 2022-11-24 14:34:24 -08:00
diced
0dbbf4840c fix: CORS not working because of auth headers 2022-11-24 14:17:46 -08:00
diced
1b6af9fc08 fix: OPTIONS being blocked 2022-11-24 10:35:06 -08:00
karlmanait
8e1541ea56 feat: add configuration for default upload format (#232)
* feat: add configuration for default upload format

* fix: change default back to original
2022-11-22 20:36:49 -08:00
diced
fd9908833a fix: link on files 2022-11-20 00:01:31 -08:00
diced
24f8300b2c fix: delete invites that expired or are used 2022-11-19 17:16:46 -08:00
diced
8d510f5d90 fix: typo 2022-11-19 17:05:15 -08:00
diced
6457680065 feat: exif metadata & remove gps 2022-11-19 13:58:14 -08:00
diced
3175911105 feat: totp 2022-11-17 22:13:23 -08:00
dicedtomato
00f26bdc75 fix: delete suggestion issues, use discussions 2022-11-17 16:04:08 -08:00
Winter
9db95bb772 fix: README spelling errors (#224) 2022-11-17 15:30:49 -08:00
diced
e1ba96784c feat(v3.6.1): version 2022-11-16 21:35:32 -08:00
diced
f67d1d41cb feat: bunch of stuff 2022-11-13 19:34:38 -08:00
diced
bb7367615d fix: remove extra administrator in set-user 2022-11-10 20:13:40 -08:00
diced
f8be8fb583 feat: new icons 2022-11-10 19:52:08 -08:00
diced
e00393936f fix: invites 3d (#218) 2022-11-10 19:37:23 -08:00
diced
3c782de64d fix: update views after serving (#213) 2022-11-10 19:37:23 -08:00
diced
678dc9ef6b fix: remove build:server on scripts 2022-11-10 19:37:23 -08:00
diced
67bb9cd4a5 feat: list-users & set-user script 2022-11-10 19:37:23 -08:00
diced
51cfb9062a fix: notifs disappear on large files (#219) 2022-11-10 19:37:23 -08:00
TacticalCoderJay
1ecf979721 fix: housekeeping (#215)
* chore: remove console.log

* chore: update error message to env file
2022-11-06 10:29:42 -08:00
diced
642b0fdc95 fix: await uploader middleware 2022-11-06 09:24:14 -08:00
Max
bc64d6886a fix: grammar (#212)
* Grammar fix

* Grammar fix, again

* Last commit

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2022-11-03 20:45:48 -07:00
diced
81399c59f7 fix: oauth 2022-11-03 20:42:43 -07:00
diced
69d10ef429 refactor: api 2022-11-03 20:40:47 -07:00
diced
3c616f4f6f fix: views increasing on /r 2022-11-02 09:22:01 -07:00
dicedtomato
988b61e459 fix: config.yml contact_links 2022-11-01 20:52:35 -07:00
TacticalCoderJay
3d4e0b8fc0 Include the discord server for a place to get help :D (#210)
* Create config.yml

* feat: Contact link w/ Discord!

* Update config.yml

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2022-11-01 20:50:19 -07:00
diced
564fcfca61 fix: crash when clicking on images 2022-11-01 18:25:51 -07:00
diced
709e1da768 fix: invites when user_registration false 2022-11-01 18:19:07 -07:00
diced
a1f281d8b4 feat(v3.6.0): version 2022-10-31 20:02:55 -07:00
diced
d2f3999cf1 fix: expires/expired text change 2022-10-31 16:43:12 -07:00
TacticalCoderJay
87fc9f2fb9 feat: v3.6.0-rc4 (#207)
* feat: oauth reform for potential improvements

* fix: update avatars with new pfp

* fix: remove redundant include

* feat: v3.6.0-rc4

Co-authored-by: dicedtomato <diced@users.noreply.github.com>

* fix: remove console log

Co-authored-by: dicedtomato <diced@users.noreply.github.com>
2022-10-30 21:42:39 -07:00
diced
8c9064fd93 fix: rework image serving 2022-10-29 22:43:42 -07:00
diced
561849ae5b feat: ability to link existing accounts to oauth 2022-10-29 20:02:54 -07:00
diced
0847802ce4 fix: remove console.log 2022-10-29 14:59:26 -07:00
diced
d5a8b3f1fb chore: update workflows node@18 2022-10-29 10:53:21 -07:00
diced
e6cebd8c46 fix: update node@18, fix views aggregation, force update stats 2022-10-29 10:52:35 -07:00
TacticalCoderJay
f2be036bac feat: issue template (#202)
* feat: new issue templates

* fix: unique ids

* fix: typo

* fix: tabbing

* Update bug.yml

* Update suggest.yml

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2022-10-29 09:56:39 -07:00
diced
f14448d40d fix: add id to version 2022-10-28 21:14:34 -07:00
diced
bf719808f2 fix: merge changes to release 2022-10-28 21:09:41 -07:00
diced
9dd82c91d7 fix: actions outputs maybe 2022-10-28 21:08:40 -07:00
diced
535f84064a fix: actions outputs 2022-10-28 21:05:44 -07:00
diced
0c0a55d766 fix: update actions/cache 2022-10-28 21:01:49 -07:00
diced
6e3ee29eb4 fix: upgrade checkout action 2022-10-28 21:01:02 -07:00
diced
6a7a5dc7a3 fix: migrate from save-state to env 2022-10-28 20:57:01 -07:00
diced
e78d2d79d0 feat: upgrade docker actions 2022-10-28 20:53:13 -07:00
diced
451027eaf3 feat: add cache to releawes 2022-10-28 20:44:43 -07:00
diced
e4491610fb feat: add trunk-(version) to tag & test cache 2022-10-28 20:41:39 -07:00
diced
f30e10f235 feat: try adding docker caching 2022-10-28 19:37:09 -07:00
diced
f9249b1380 feat: even more for URLs 2022-10-28 17:41:46 -07:00
diced
3df94526b0 feat: add more info to cards & relative time 2022-10-28 17:28:15 -07:00
diced
b30b7b1bd3 fix: #204 2022-10-28 16:47:16 -07:00
dicedtomato
a9defd67d6 feat: add screenshots to readme 2022-10-27 21:53:12 -07:00
diced
68d346e69d fix: stuff 2022-10-27 21:26:54 -07:00
diced
e2fd27cbba feat(3.6.0-rc3): version & bump deps 2022-10-27 21:09:14 -07:00
diced
4c0532006c fix: resolve multiple el in titles 2022-10-27 21:07:44 -07:00
diced
7ac574b230 fix: support .env file 2022-10-27 19:38:54 -07:00
diced
7eb855de8f feat: new file serving method & max views for files 2022-10-27 19:34:20 -07:00
diced
d5984f4141 chore: make things work on next 13 2022-10-26 18:20:56 -07:00
diced
b7c0c85639 chore: bump nextjs 2022-10-25 17:52:04 -07:00
diced
84ba166aea feat: file chunking for large uploads 2022-10-24 18:31:49 -07:00
diced
bd79858681 feat(3.6.0-rc2): version 2022-10-24 11:39:42 -07:00
diced
0f10fa3991 fix: uniform margins 2022-10-24 11:38:38 -07:00
diced
74b1799d21 feat: user registration without oauth 2022-10-24 11:28:06 -07:00
diced
4552643ff8 feat: more options for text uploads & password protect them 2022-10-24 11:10:32 -07:00
diced
d432b388f6 feat: preview text uploads 2022-10-24 10:57:13 -07:00
diced
a8475602c7 feat: add port config opt for s3 2022-10-23 12:33:04 -07:00
diced
f58d33af9e fix: recurse for entryPoints 2022-10-23 12:16:59 -07:00
diced
0150ea5e70 fix: spacing between main dashboard elements 2022-10-23 11:57:24 -07:00
diced
3bf43f1606 fix: maybe fix exports 2022-10-23 10:36:38 -07:00
diced
b8729a6ec7 fix: save imported files to datasource 2022-10-23 10:14:29 -07:00
diced
1f44aa7e85 fix: await on prisma.image.createMany 2022-10-23 10:09:48 -07:00
diced
2bd5352fc5 feat: import-dir script 2022-10-23 10:08:21 -07:00
diced
a90130e8bf feat(v3.6.0-rc1): small fixes 2022-10-22 23:42:52 -07:00
diced
642e8796f0 feat: oauth info in user dropdown 2022-10-22 16:10:31 -07:00
diced
615cbddc89 feat: ability to edit/delete users with master administrator 2022-10-22 14:50:53 -07:00
diced
4ef82bdff4 fix: prettier 2022-10-22 14:30:50 -07:00
diced
dafde04c2c fix: config validation for discord is null 2022-10-22 14:26:34 -07:00
diced
1be61b8d89 chore: update deps & tsx -> esbuild 2022-10-22 14:23:23 -07:00
diced
c3215c7425 fix: add oauth to readme 2022-10-19 19:49:25 -07:00
Jonathan
af0cd26ea0 feat: prettier run (#200)
* feat: prettier run

* fix: whatever that was

* chore: format more files

* chore: make format command better
2022-10-19 19:43:01 -07:00
Jonathan
cb7dacd089 perf: config validation improvements (#192)
* perf: improve config validation

* chore: remove extra space in package.json

* fix: actually update file

* fix: `datasource.local` not providing a default value

* fix: small oversight in readConfig & better error

Co-authored-by: diced <pranaco2@gmail.com>
2022-10-18 22:15:15 -07:00
TacticalCoderJay
8c04971094 fix: create -> save (edit user) (#199) 2022-10-18 20:19:40 -07:00
diced
3a4802f09a fix: checks for export & refresh button 2022-10-17 17:20:54 -07:00
TacticalCoderJay
d78db306c5 fix: use os.tmpdir instead of hardcoded /tmp (#198) 2022-10-17 17:05:38 -07:00
TacticalCoderJay
3f8790ece1 fix: only display exports that are your own (#197) 2022-10-17 16:48:01 -07:00
TacticalCoderJay
f9e6158144 hotfix: change text to adjusted limit (#196) 2022-10-17 07:59:33 -07:00
TacticalCoderJay
05de3fed15 feat: ability to create many invites (#194) (#183)
* feat: Create many invites added.

* Update src/pages/api/auth/invite.ts

Co-authored-by: Jonathan <axis@axis.moe>

* fix: Lowered limit.

Co-authored-by: Jonathan <axis@axis.moe>
2022-10-16 20:18:21 -07:00
TacticalCoderJay
38cba9cb39 fix: Follow proper linebreak style. (#191) 2022-10-16 20:10:52 -07:00
diced
a4af980e11 fix: password strength not showing up on invites (#186) 2022-10-16 14:32:27 -07:00
diced
940b844857 feat: admins can't edit/delete other admins 2022-10-16 14:12:44 -07:00
diced
41b766216e feat: naming & views on files #187 #181 2022-10-16 14:06:32 -07:00
Jonathan
402987baba fix(dashboard): error when fetching stats (#193)
Handles an edge case where stats.data is a 1 length array since there's no before data
2022-10-16 13:44:46 -07:00
Winter
3cb08c73d3 feat: add S3 SSL as an env variable (#188)
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2022-10-15 12:33:37 -07:00
Derock
4cb92a7257 fix: NaN on stat card (#179)
* fix: NaN % change

* ref: format

Co-authored-by: Jonathan <axis@axis.moe>

Co-authored-by: Jonathan <axis@axis.moe>
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2022-10-15 12:22:57 -07:00
diced
a095768eae ref: change look of oauth 2022-10-13 21:47:56 -07:00
diced
1a5925d7e8 fix: add minWidth to user dropdown 2022-10-08 21:28:36 -07:00
diced
9147847710 fix: make line graph tooltips work everywhere 2022-10-08 21:24:05 -07:00
diced
05fe8bcaca fix: remove debug logs 2022-10-08 21:07:55 -07:00
diced
b0c3c6f45a feat: oauth sign up 2022-10-08 17:58:56 -07:00
diced
0f641aa852 fix: make last icon flat 2022-10-02 16:01:33 -07:00
diced
2651bbe50c fix: change readme 2022-10-02 16:01:05 -07:00
diced
d31371eb6c feat: configurable invites & disable_media_preview config 2022-10-02 15:39:59 -07:00
diced
ec0e7e5ec7 feat: edit users (admin-only) 2022-10-01 14:04:18 -07:00
diced
feb75a8a42 fix(config): add env for UPLOADER_DATE_FORMAT 2022-10-01 13:34:41 -07:00
diced
d4369d2503 refactor: redux -> recoil 2022-10-01 11:09:07 -07:00
diced
d236589644 fix: tidy up stuff 2022-10-01 11:09:07 -07:00
diced
8044b7f623 refactor: move clientUtils into utils/ 2022-10-01 11:09:07 -07:00
dicedtomato
9f0697dd34 feat: add contributing.md 2022-09-30 16:16:08 -07:00
diced
78a6f3122d fix: escape . in regexes 2022-09-28 20:45:50 -07:00
diced
b460da74dd refactor: change bool() -> boolean() 2022-09-28 20:30:24 -07:00
Wolfy0615
75a8bb7962 fix: change outdated url (#172)
* Fix outdated url

Updated to resolve merge conflicts

* Update index.tsx

Fix #2
2022-09-28 19:50:01 -07:00
diced
9ac876e30a feat: ability to view non-media views 2022-09-24 14:25:34 -07:00
diced
26cb4ea034 fix: placeholder and red buttons 2022-09-24 10:43:23 -07:00
diced
0d65ee1a32 feat: flameshot generator 2022-09-24 10:41:03 -07:00
Erik Bender
4a753376b7 feat: add password field in file upload ui (#169) 2022-09-24 10:14:36 -07:00
Derock
dc926e9f5a feat: overhaul a lot of stuff (#171)
* feat: ssr for /code/[id], fix: syntax highlighting

* feat: cache responses

* ref: eslint

* wip

* Create .gitattributes

* wip again

* redesign dashboard

* ref: use react-query for url
ref: break into components
feat: loading animation for delete
feat: no url image

* feat: use react-query mutation for files

* ref: update sizing on code input

* chore(deps): update mantine

* feat: overhaul stats page

* fix: incorrectly calculating stat % change

* fix: use latest data in stats per day

* feat: add validation on stats amount query string

* refactor: clean up imports & code

* fix: remove prettier (fixes eslint)

* ref: run eslint autofix

* ref: more eslint

* ref: replace undraw on homepage with react-feather

* refactor: remove tailwind & add responsiveness to stuff

* fix: colors on file placeholder

* fix: make actions work

* feat: new sharex configuration generator

Co-authored-by: diced <pranaco2@gmail.com>
2022-09-23 18:19:27 -07:00
diced
722372c7f6 fix: #173 2022-09-11 12:08:38 -07:00
diced
4589c6ee0a fix: #166 2022-09-05 15:06:07 -07:00
diced
67ff93e640 fix: remove useless webpack cofnig 2022-08-26 20:38:31 -07:00
diced
bd055d704b fix: domain duplication 2022-08-26 20:35:25 -07:00
diced
2e8bee931c feat: add a debug read-config script 2022-08-26 20:06:57 -07:00
diced
a454a4f4a8 feat: external links & bug fixes 2022-08-26 20:04:25 -07:00
diced
45541a3cdd feat: add version to appshell 2022-08-24 20:37:57 -07:00
diced
1d42d922bd feat: discord webhook notifs 2022-08-23 09:38:29 -07:00
diced
4f631fbd0e feat: more ways to expire 2022-08-21 22:24:56 -07:00
diced
e911db4c1a fix: image table on dashboard 2022-08-17 15:01:23 -07:00
305 changed files with 26013 additions and 15862 deletions

10
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-18
RUN usermod -l zipline node \
&& groupmod -n zipline node \
&& usermod -d /home/zipline zipline \
&& echo "zipline ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/zipline \
&& chmod 0440 /etc/sudoers.d/zipline \
&& sudo apt-get update && apt-get install gnupg2 -y
USER zipline

View File

@@ -0,0 +1,56 @@
{
"name": "Zipline Codespace",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/zipline",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"username": "zipline"
},
"ghcr.io/devcontainers/features/docker-in-docker:1": {
"dockerDashComposeVersion": "v2",
"installDockerBuildx": true
}
},
"customizations": {
"vscode": {
"settings": {
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"files.autoSave": "afterDelay",
"terminal.integrated.persistentSessionReviveProcess": "never",
"terminal.integrated.defaultProfile.linux": "zsh",
"terminal.integrated.profiles.linux": {
"zsh": {
"path": "/bin/zsh",
"env": {
"ZSH_THEME": "devcontainers"
}
}
}
},
"extensions": ["prisma.prisma", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}
},
"remoteUser": "zipline",
"updateRemoteUserUID": true,
"remoteEnv": {
"CORE_DATABASE_URL": "postgres://postgres:postgres@localhost/zip10"
},
"portsAttributes": {
"3000": {
"label": "Zipline",
"onAutoForward": "openBrowser"
},
"5432": {
"label": "Postgres"
}
},
"postCreateCommand": "sudo chown -R zipline:zipline /zipline && yarn install"
}

View File

@@ -0,0 +1,25 @@
version: '3.8'
services:
app:
build:
context: ./
dockerfile: Dockerfile
volumes:
- ../:/zipline:cached
- uploads:/zipline/uploads
- node_modules:/zipline/node_modules
command: sleep infinity
db:
image: postgres:latest
restart: unless-stopped
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DATABASE=postgres
volumes:
- pg_data:/var/lib/postgresql/data
volumes:
pg_data:
uploads:
node_modules:

View File

@@ -1,7 +1,7 @@
# every field in here is optional except, CORE_SECRET and CORE_DATABASE_URL.
# if CORE_SECRET is still "changethis" then zipline will exit and tell you to change it.
# if using s3/swift make sure to comment out the other datasources
# if using s3/supabase make sure to comment out the other datasources
CORE_HTTPS=true
CORE_SECRET="changethis"
@@ -23,16 +23,16 @@ DATASOURCE_S3_BUCKET=bucket
DATASOURCE_S3_ENDPOINT=s3.amazonaws.com
DATASOURCE_S3_REGION=us-west-2
DATASOURCE_S3_FORCE_S3_PATH=false
DATASOURCE_S3_USE_SSL=false
# or you can use swift
DATASOURCE_TYPE=swift
DATASOURCE_SWIFT_CONTAINER=container
DATASOURCE_SWIFT_AUTH_ENDPOINT="https://something/v3"
DATASOURCE_SWIFT_USERNAME=username
DATASOURCE_SWIFT_PASSWORD=password
DATASOURCE_SWIFT_PROJECT_ID=project_id
DATASOURCE_SWIFT_DOMAIN_ID=domain_id
# or supabase
DATASOURCE_TYPE=supabase
DATASOURCE_SUPABASE_KEY=xxx
# remember: no leading slash
DATASOURCE_SUPABASE_URL=https://something.supabase.co
DATASOURCE_SUPABASE_BUCKET=zipline
UPLOADER_DEFAULT_FORMAT=RANDOM
UPLOADER_ROUTE=/u
UPLOADER_LENGTH=6
UPLOADER_ADMIN_LIMIT=104900000
@@ -43,4 +43,4 @@ URLS_ROUTE=/go
URLS_LENGTH=6
RATELIMIT_USER = 5
RATELIMIT_ADMIN = 3
RATELIMIT_ADMIN = 3

7
.eslintignore Normal file
View File

@@ -0,0 +1,7 @@
node_modules
dist
.yarn
.devcontainer
.github
.next
.vscode

View File

@@ -1,36 +1,26 @@
{
"root": true,
"extends": [
"next",
"next/core-web-vitals"
"next/core-web-vitals",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended"
],
"plugins": ["unused-imports", "@typescript-eslint"],
"parser": "@typescript-eslint/parser",
"rules": {
"indent": [
"error",
2,
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
"unix"
],
"linebreak-style": ["error", "unix"],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"comma-dangle": [
"error",
"always-multiline"
],
"jsx-quotes": [
"error",
"prefer-single"
"single",
{
"avoidEscape": true
}
],
"semi": ["error", "always"],
"comma-dangle": ["error", "always-multiline"],
"jsx-quotes": ["error", "prefer-single"],
"indent": "off",
"react/prop-types": "off",
"react-hooks/rules-of-hooks": "off",
"react-hooks/exhaustive-deps": "off",
@@ -46,6 +36,14 @@
"react/style-prop-object": "warn",
"@next/next/no-img-element": "off",
"jsx-a11y/alt-text": "off",
"react/display-name": "off"
"react/display-name": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"error",
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
],
"@typescript-eslint/ban-ts-comment": "off"
}
}
}

14
.gitattributes vendored Normal file
View File

@@ -0,0 +1,14 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text eol=lf
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.c text
*.h text
# Declare files that will always have CRLF line endings on checkout.
*.sln text eol=crlf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary

52
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: Bug
description: File a bug report
title: 'Bug: '
labels: ['bug']
body:
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Provide steps to reproduce the bug, and some context.
value: 'A bug happened!'
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version of Zipline are you using?
options:
- upstream (ghcr.io/diced/zipline:trunk)
- latest (ghcr.io/diced/zipline:latest)
- other (provide version in additional info)
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browser(s) are you seeing the problem on?
multiple: true
options:
- Firefox
- Chromium-based (Chrome, Edge, Brave, Opera, mobile chrome/chromium based, etc)
- Safari
- Firefox Mobile
- Safari Mobile
- type: textarea
id: zipline-logs
attributes:
label: Zipline Logs
description: Please copy and paste any relevant log output. Not seeing anything interesting? Try adding the `DEBUG=true` environment variable to see more logs, make sure to review the output and remove any sensitive information as it can be VERY verbose at times.
render: shell
- type: textarea
id: browser-logs
attributes:
label: Browser Logs
description: Please copy and paste any relevant log output.
render: shell
- type: textarea
id: additional-info
attributes:
label: Additional Info
description: Anything else that could be used to narrow down the issue, like your config.

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Feature Request
url: https://github.com/diced/zipline/discussions/new?category=ideas&title=Your%20breif%20description%20here&labels=feature
about: Ask for a new feature
- name: Zipline Discord
url: https://discord.gg/EAhCRfGxCF
about: Ask for help with anything related to Zipline!
- name: Zipline Docs
url: https://zipline.diced.tech
about: Maybe take a look a the docs?

View File

@@ -11,16 +11,22 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16.x'
node-version: '18.x'
- name: 'Restore dependency cache'
id: cache-restore
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }}
path: |
node_modules
${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
restore-keys: |
${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }}-
- name: Install dependencies
if: steps.cache-restore.outputs.cache-hit != 'true'

View File

@@ -18,33 +18,34 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Get version
uses: sergeysova/jq-action@v2
id: version
with:
cmd: 'jq .version package.json -r'
run: |
echo "zipline_version=$(jq .version package.json -r)" >> $GITHUB_OUTPUT
- name: Setup QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Login to Github Packages
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
push: true
platforms: linux/amd64,linux/arm64
tags: |
ghcr.io/diced/zipline:latest
ghcr.io/diced/zipline:${{ steps.version.outputs.value }}
ghcr.io/diced/zipline:${{ steps.version.outputs.zipline_version }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@@ -17,26 +17,34 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Get version
id: version
run: |
echo "zipline_version=$(jq .version package.json -r)" >> $GITHUB_OUTPUT
- name: Setup QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Login to Github Packages
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
push: true
platforms: linux/amd64,linux/arm64
tags: |
ghcr.io/diced/zipline:trunk
ghcr.io/diced/zipline:trunk-${{ steps.version.outputs.zipline_version }}
cache-from: type=gha
cache-to: type=gha,mode=max

31
.github/workflows/milestone.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: 'Issue/PR Milestones'
on:
pull_request_target:
types: [opened, reopened]
issues:
types: [opened, reopened]
permissions:
issues: write
checks: write
contents: read
pull-requests: write
jobs:
set:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/github-script@v6
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const milestone = 3
github.rest.issues.update({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
milestone
})

2
.gitignore vendored
View File

@@ -41,5 +41,5 @@ yarn-error.log*
# zipline
config.toml
uploads/
uploads*/
dist/

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
package-lock=false

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
18.12.0

5
.prettierrc.json Normal file
View File

@@ -0,0 +1,5 @@
{
"singleQuote": true,
"jsxSingleQuote": true,
"printWidth": 110
}

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"editor.tabSize": 2,
"files.eol": "\n",
"typescript.tsdk": "node_modules/typescript/lib"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

823
.yarn/releases/yarn-3.3.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,11 @@
checksumBehavior: update
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
yarnPath: .yarn/releases/yarn-3.2.1.cjs
yarnPath: .yarn/releases/yarn-3.3.1.cjs

23
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,23 @@
# Contributing
## Bug reports
Create an issue on GitHub, please include the following (if one of them is not applicable to the issue then it's not needed):
- The steps to reproduce the bug
- Logs of Zipline
- The version of Zipline
- Your OS & Browser including server OS
- What you were expecting to see
## Feature requests
Create an discussion on GitHub, please include the following:
- Breif explanation of the feature in the title (very breif please)
- How it would work (detailed, but optional)
## Pull Requests (contributions to the codebase)
Create a pull request on GitHub. If your PR does not pass the action checks, then please fix the errors. If your PR was submitted before a release, and I have pushed a new release, please make sure to update your PR to reflect any changes, usually this is handled by GitHub.
Please make sure your code also reflects the style of the rest of the code.

View File

@@ -1,63 +1,76 @@
FROM ghcr.io/diced/prisma-binaries:4.1.x as prisma
# Use the Prisma binaries image as the first stage
FROM ghcr.io/diced/prisma-binaries:4.10.x as prisma
FROM alpine:3.16 AS deps
RUN mkdir -p /prisma-engines
WORKDIR /build
# Use Alpine Linux as the second stage
FROM node:18-alpine3.16 as base
COPY .yarn .yarn
COPY package.json yarn.lock .yarnrc.yml ./
RUN apk add --no-cache nodejs yarn
RUN yarn install --immutable
FROM alpine:3.16 AS builder
WORKDIR /build
COPY --from=prisma /prisma-engines /prisma-engines
ENV PRISMA_QUERY_ENGINE_BINARY=/prisma-engines/query-engine \
PRISMA_MIGRATION_ENGINE_BINARY=/prisma-engines/migration-engine \
PRISMA_INTROSPECTION_ENGINE_BINARY=/prisma-engines/introspection-engine \
PRISMA_FMT_BINARY=/prisma-engines/prisma-fmt \
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
PRISMA_CLIENT_ENGINE_TYPE=binary
RUN apk add --no-cache nodejs yarn openssl openssl-dev
COPY --from=deps /build/node_modules ./node_modules
COPY src ./src
COPY prisma ./prisma
COPY .yarn .yarn
COPY package.json yarn.lock .yarnrc.yml esbuild.config.js next.config.js next-env.d.ts zip-env.d.ts tsconfig.json mimes.json ./
ENV ZIPLINE_DOCKER_BUILD 1
ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn build
FROM alpine:3.16 AS runner
# Set the working directory
WORKDIR /zipline
# Copy the necessary files from the project
COPY prisma ./prisma
COPY src ./src
COPY next.config.js ./next.config.js
COPY tsup.config.ts ./tsup.config.ts
COPY tsconfig.json ./tsconfig.json
COPY mimes.json ./mimes.json
COPY public ./public
FROM base as builder
COPY .yarn ./.yarn
COPY package*.json ./
COPY yarn*.lock ./
COPY .yarnrc.yml ./
# Copy the prisma binaries from prisma stage
COPY --from=prisma /prisma-engines /prisma-engines
ENV PRISMA_QUERY_ENGINE_BINARY=/prisma-engines/query-engine \
PRISMA_MIGRATION_ENGINE_BINARY=/prisma-engines/migration-engine \
PRISMA_INTROSPECTION_ENGINE_BINARY=/prisma-engines/introspection-engine \
PRISMA_FMT_BINARY=/prisma-engines/prisma-fmt \
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
PRISMA_CLIENT_ENGINE_TYPE=binary
PRISMA_CLIENT_ENGINE_TYPE=binary \
ZIPLINE_DOCKER_BUILD=true \
NEXT_TELEMETRY_DISABLED=1
RUN apk add --no-cache nodejs yarn openssl openssl-dev
# Install production dependencies then temporarily save
RUN yarn workspaces focus --production --all
RUN cp -RL node_modules /tmp/node_modules
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
# Install the dependencies
RUN yarn install --immutable
COPY --from=builder /build/.next ./.next
COPY --from=builder /build/node_modules ./node_modules
# Run the build
RUN yarn build
COPY --from=builder /build/next.config.js ./next.config.js
COPY --from=builder /build/src ./src
COPY --from=builder /build/prisma ./prisma
COPY --from=builder /build/tsconfig.json ./tsconfig.json
COPY --from=builder /build/package.json ./package.json
COPY --from=builder /build/mimes.json ./mimes.json
# Use Alpine Linux as the final image
FROM base
# Install the necessary packages
RUN apk add --no-cache perl procps tini
CMD ["node_modules/.bin/tsx", "src/server"]
COPY --from=builder /prisma-engines /prisma-engines
ENV PRISMA_QUERY_ENGINE_BINARY=/prisma-engines/query-engine \
PRISMA_MIGRATION_ENGINE_BINARY=/prisma-engines/migration-engine \
PRISMA_INTROSPECTION_ENGINE_BINARY=/prisma-engines/introspection-engine \
PRISMA_FMT_BINARY=/prisma-engines/prisma-fmt \
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
PRISMA_CLIENT_ENGINE_TYPE=binary \
NEXT_TELEMETRY_DISABLED=1
# Copy only the necessary files from the previous stage
COPY --from=builder /zipline/dist ./dist
COPY --from=builder /zipline/.next ./.next
COPY --from=builder /zipline/package.json ./package.json
COPY --from=builder /zipline/node_modules ./node_modules
COPY --from=builder /zipline/node_modules/.prisma/client ./node_modules/.prisma/client
COPY --from=builder /zipline/node_modules/@prisma/client ./node_modules/@prisma/client
# Copy Startup Script
COPY docker-entrypoint.sh /zipline
# Make Startup Script Executable
RUN chmod a+x /zipline/docker-entrypoint.sh && rm -rf /zipline/src
# Set the entrypoint to the startup script
ENTRYPOINT ["tini", "--", "/zipline/docker-entrypoint.sh"]

View File

@@ -1,17 +1,21 @@
<div align="center">
<img src="https://raw.githubusercontent.com/diced/zipline/trunk/public/zipline_small.png"/>
Zipline is a ShareX/file upload server that is easy to use, packed with features and can be setup in one command!
A ShareX/file upload server that is easy to use, packed with features, and with an easy setup!
![Build](https://img.shields.io/github/workflow/status/diced/zipline/CD:%20Push%20Docker%20Images?logo=github&style=flat-square)
![Stars](https://img.shields.io/github/stars/diced/zipline?logo=github&style=flat-square)
![Version](https://img.shields.io/github/package-json/v/diced/zipline?logo=git&logoColor=white&style=flat-square)
![GitHub last commit (branch)](https://img.shields.io/github/last-commit/diced/zipline/trunk?logo=git&logoColor=white&style=flat-square)
[![Discord](https://img.shields.io/discord/729771078196527176?color=%23777ed3&label=discord&logo=discord&logoColor=white&style=flat-square)](https://discord.gg/EAhCRfGxCF)
![Stars](https://img.shields.io/github/stars/diced/zipline?logo=github&style=flat)
![Version](https://img.shields.io/github/package-json/v/diced/zipline?logo=git&logoColor=white&style=flat)
![GitHub last commit (branch)](https://img.shields.io/github/last-commit/diced/zipline/trunk?logo=git&logoColor=white&style=flat)
[![Discord](https://img.shields.io/discord/729771078196527176?color=%23777ed3&label=discord&logo=discord&logoColor=white&style=flat)](https://discord.gg/EAhCRfGxCF)
![Build](https://img.shields.io/github/actions/workflow/status/diced/zipline/build.yml?logo=github&style=flat&branch=trunk)
[![Docker Image (trunk)](https://img.shields.io/github/actions/workflow/status/diced/zipline/docker.yml?label=Docker%20%28trunk%29&logo=github&style=flat&branch=trunk)](https://github.com/diced/zipline/pkgs/container/zipline/?tag=trunk)
[![Docker Image (release)](https://img.shields.io/github/actions/workflow/status/diced/zipline/docker-release.yml?label=Docker%20%28release%29&logo=github&style=flat&branch=trunk)](https://github.com/diced/zipline/pkgs/container/zipline/?tag=latest)
</div>
## Features
- Configurable
- Fast
- Built with Next.js & React
@@ -21,30 +25,59 @@
- Password Protected Uploads
- URL shortening
- Text uploading
- URL Formats (uuid, dates, random alphanumeric, original name, zws)
- URL Formats (uuid, dates, random alphanumeric, original name, zws, gfycat -> [animals](https://assets.gfycat.com/animals) [adjectives](https://assets.gfycat.com/adjectives))
- Discord embeds (OG metadata)
- Gallery viewer, and multiple file format support
- Code highlighting
- Easy setup instructions on [docs](https://zipl.vercel.app/) (One command install `docker-compose up -d`)
- Fully customizable Discord webhook notifications
- OAuth2 registration (Discord and GitHub)
- Two-Factor authentication with Google Authenticator, Authy, etc (totp services).
- User invites
- File Chunking (for large files)
- File deletion once it reaches a certain amount of views
- Easy setup instructions on [docs](https://zipl.vercel.app/) (One command install `docker compose up -d`)
<details>
<summary>View upstream documentation</summary>
The website below provides documentation for more up-to-date features with the upstream branch. The normal documentation is for the latest release and is not updated unless a new release is made.
[https://trunk.zipline.diced.tech/](https://trunk.zipline.diced.tech/)
</details>
<details>
<summary><h2>Screenshots (click)</h2></summary>
View full album at [imgur](https://imgur.com/a/GzyowZ7)
![Login Page](https://i.imgur.com/14Er7qt.png)
![Dashboard](https://i.imgur.com/3JK5bp6.png)
![Files Page](https://i.imgur.com/grIaDs8.png)
</details>
# Usage
## Install & run with Docker
This section requires [Docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/).
This section requires [Docker](https://docs.docker.com/get-docker/) and [docker compose](https://docs.docker.com/compose/install/).
```shell
git clone https://github.com/diced/zipline
cd zipline
docker-compose up -d
docker compose up -d
```
### After installing
After installing, please edit the `docker-compose.yml` file and find the line that says `SECRET=changethis` and replace `changethis` with a random string.
Ways you could generate the string could be from a password managers generator, or you could just slam your keyboard and hope for the best.
## Building & running from source
This section requires [nodejs](https://nodejs.org), [yarn](https://yarnpkg.com/) or [npm](https://npmjs.com).
```shell
git clone https://github.com/diced/zipline
cd zipline
@@ -58,6 +91,7 @@ yarn start
```
# NGINX Proxy
This section requires [NGINX](https://nginx.org/).
```nginx
@@ -76,16 +110,31 @@ server {
```
# Website
The default port is `3000`, once you have accessed it you can see a login screen. The default credentials are "administrator" and "password". Once you login please immediately change the details to something more secure. You can do this by clicking on the top right corner where it says "administrator" with a gear icon and clicking Manage Account.
# ShareX (Windows)
This section requires [ShareX](https://www.getsharex.com/).
After navigating to Zipline, click on the top right corner where it says your username and click Manage Account. Scroll down to see "ShareX Config", select the one you would prefer using. After this you can import the .sxcu into sharex. [More information here](https://zipl.vercel.app/docs/uploaders/sharex)
After navigating to Zipline, click on the top right corner where it says your username and click Manage Account. Scroll down to see "ShareX Config", select the one you would prefer using. After this you can import the .sxcu into sharex. [More information here](https://zipl.vercel.app/docs/guides/uploaders/sharex)
# Flameshot (Linux)
This section requires [Flameshot](https://www.flameshot.org/), [jq](https://stedolan.github.io/jq/), and [xsel](https://github.com/kfish/xsel).
<details>
<summary>Wayland instructions</summary>
If using wayland you will need to have [wl-clipboard](https://github.com/bugaevc/wl-clipboard) installed, for the `wl-copy` command.
If you are not using GNOME/KDE/Qtile/Sway, and are using something like a wlroots-based compositor (ex. [Hyprland](https://github.com/hyprwm/Hyprland/), [River](https://github.com/riverwm/river), etc), you will need to set the `XDG_CURRENT_DESKTOP` environment variable to `sway`, which will just override it for this script. Adding `export XDG_CURRENT_DESKTOP=sway` to the start of the script will work.
After this, replace the `xsel -ib` with `wl-copy` in the script.
</details>
You can either use the script below, or generate one directly from Zipline (just like how you can generate a ShareX config).
To upload files using flameshot we will use a script. Replace $TOKEN and $HOST with your own values, you probably know how to do this if you use linux.
```shell
@@ -98,17 +147,22 @@ curl -H "Content-Type: multipart/form-data" -H "authorization: $TOKEN" -F file=@
# Contributing
## Bug reports
Create an issue on GitHub, please include the following (if one of them is not applicable to the issue then it's not needed):
* The steps to reproduce the bug
* Logs of Zipline
* The version of Zipline
* Your OS & Browser including server OS
* What you were expecting to see
Create an issue on GitHub and use the template, please include the following (if one of them is not applicable to the issue then it's not needed):
- The steps to reproduce the bug
- Logs of Zipline
- The version of Zipline
- Your OS & Browser including server OS
- What you were expecting to see
## Feature requests
Create an issue on GitHub, please include the following:
* Breif explanation of the feature in the title (very breif please)
* How it would work (detailed, but optional)
Create a discussion on GitHub, please include the following:
- Brief explanation of the feature in the title (very brief please)
- How it would work (Be detailed!)
## Pull Requests (contributions to the codebase)
Create a pull request on GitHub. If your PR does not pass the action checks, then please fix the errors. If your PR was submitted before a release, and I have pushed a new release, please make sure to update your PR to reflect any changes, usually this is handled by GitHub.
Create a pull request on GitHub. If your PR does not pass the action checks, then please fix the errors. If your PR was submitted before a release, and I have pushed a new release, please make sure to update your PR to reflect any changes, usually this is handled by GitHub.

View File

@@ -4,9 +4,10 @@
| Version | Supported |
| ------- | ------------------ |
| 3.4.4 | :white_check_mark: |
| 3.6.x | :white_check_mark: |
| < 3 | :x: |
| < 2 | :x: |
## Reporting a Vulnerability
Report a Vulnerability by issuing a bug report, with exact details with how the vulnerability happened, what "exploits" can happen, and possible fixes (optional). Vulnerability reports are treated with high priority and will be resolved most of the time quickly.

View File

@@ -1,8 +1,7 @@
version: '3'
services:
postgres:
image: postgres
restart: always
image: postgres:15
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
@@ -21,14 +20,8 @@ services:
dockerfile: Dockerfile
ports:
- '3000:3000'
restart: unless-stopped
environment:
- CORE_HTTPS=false
- CORE_SECRET=changethislol
- CORE_HOST=0.0.0.0
- CORE_PORT=3000
- CORE_DATABASE_URL=postgres://postgres:postgres@postgres/postgres
- CORE_LOGGER=true
env_file:
- .env.local
volumes:
- '$PWD/uploads:/zipline/uploads'
- '$PWD/public:/zipline/public'
@@ -36,4 +29,4 @@ services:
- 'postgres'
volumes:
pg_data:
pg_data:

View File

@@ -1,8 +1,8 @@
version: '3'
services:
postgres:
image: postgres
restart: always
image: postgres:15
restart: unless-stopped
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
@@ -19,9 +19,9 @@ services:
image: ghcr.io/diced/zipline
ports:
- '3000:3000'
restart: always
restart: unless-stopped
environment:
- CORE_HTTPS=false
- CORE_RETURN_HTTPS=false
- CORE_SECRET=changethis
- CORE_HOST=0.0.0.0
- CORE_PORT=3000
@@ -34,4 +34,4 @@ services:
- 'postgres'
volumes:
pg_data:
pg_data:

5
docker-entrypoint.sh Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
set -e
node --enable-source-maps dist/index.js

View File

@@ -1,43 +0,0 @@
const esbuild = require('esbuild');
const { existsSync } = require('fs');
const { rm } = require('fs/promises');
(async () => {
const watch = process.argv[2] === '--watch';
if (existsSync('./dist')) {
await rm('./dist', { recursive: true });
}
await esbuild.build({
tsconfig: 'tsconfig.json',
outdir: 'dist',
bundle: false,
platform: 'node',
treeShaking: true,
entryPoints: [
'src/server/index.ts',
'src/server/util.ts',
'src/lib/logger.ts',
'src/lib/config.ts',
'src/lib/mimes.ts',
'src/lib/exts.ts',
'src/lib/config/Config.ts',
'src/lib/config/readConfig.ts',
'src/lib/config/validateConfig.ts',
'src/lib/datasources/Datasource.ts',
'src/lib/datasources/index.ts',
'src/lib/datasources/Local.ts',
'src/lib/datasources/S3.ts',
'src/lib/datasources/Swift.ts',
'src/lib/datasource.ts',
],
format: 'cjs',
resolveExtensions: ['.ts', '.js'],
write: true,
watch,
incremental: watch,
sourcemap: true,
minify: false,
});
})();

9680
mimes.json

File diff suppressed because it is too large Load Diff

View File

@@ -2,15 +2,16 @@
* @type {import('next').NextConfig}
**/
module.exports = {
async redirects() {
return [
{
source: '/',
destination: '/dashboard',
permanent: true,
},
];
images: {
domains: [
// For sharex icon in manage user
'getsharex.com',
// For flameshot icon, and maybe in the future other stuff from github
'raw.githubusercontent.com',
// Google Icon
'madeby.google.com',
],
},
poweredByHeader: false,
reactStrictMode: true,
};
};

View File

@@ -1,75 +1,102 @@
{
"name": "zipline",
"version": "3.5.0",
"version": "3.7.0",
"license": "MIT",
"scripts": {
"dev": "REACT_EDITOR=code NODE_ENV=development tsx src/server",
"build": "npm-run-all build:schema build:next",
"dev": "npm-run-all build:server dev:run",
"dev:run": "cross-env DEBUG=true REACT_EDITOR=code NODE_ENV=development RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false node --enable-source-maps dist",
"build": "npm-run-all build:server build:schema build:next",
"build-ci": "cross-env ZIPLINE_DOCKER_BUILD=1 npm-run-all build:server build:schema build:next",
"build:server": "tsup",
"build:next": "next build",
"build:schema": "prisma generate --schema=prisma/schema.prisma",
"format": "prettier --write ./src/**/*.{ts,tsx} ./*.{md,js,json,yml}",
"migrate:dev": "prisma migrate dev --create-only",
"start": "tsx src/server",
"start": "node dist",
"lint": "next lint",
"docker:run": "docker-compose up -d",
"docker:down": "docker-compose down",
"docker:build-dev": "docker-compose --file docker-compose.dev.yml up --build"
"compose:up": "docker compose up",
"compose:down": "docker compose down",
"compose:build-dev": "docker compose --file docker-compose.dev.yml up --build",
"compose:up-dev": "docker compose --file docker-compose.dev.yml up",
"compose:down-dev": "docker compose --file docker-compose.dev.yml down",
"scripts:read-config": "node --enable-source-maps dist/scripts/read-config",
"scripts:import-dir": "node --enable-source-maps dist/scripts/import-dir",
"scripts:list-users": "node --enable-source-maps dist/scripts/list-users",
"scripts:set-user": "node --enable-source-maps dist/scripts/set-user",
"scripts:clear-zero-byte": "node --enable-source-maps dist/scripts/clear-zero-byte",
"scripts:query-size": "node --enable-source-maps dist/scripts/query-size"
},
"dependencies": {
"@emotion/react": "^11.9.3",
"@emotion/server": "^11.4.0",
"@iarna/toml": "2.2.5",
"@mantine/core": "^5.0.0",
"@mantine/dropzone": "^5.0.0",
"@mantine/form": "^5.0.0",
"@mantine/hooks": "^5.0.0",
"@mantine/modals": "^5.0.0",
"@mantine/next": "^5.0.0",
"@mantine/notifications": "^5.0.0",
"@mantine/nprogress": "^5.0.0",
"@mantine/prism": "^5.0.0",
"@prisma/client": "^4.1.0",
"@prisma/internals": "^4.1.0",
"@prisma/migrate": "^4.1.0",
"@reduxjs/toolkit": "^1.8.2",
"argon2": "^0.28.5",
"colorette": "^2.0.19",
"@emotion/react": "^11.10.6",
"@emotion/server": "^11.10.0",
"@mantine/core": "^6.0.4",
"@mantine/dropzone": "^6.0.4",
"@mantine/form": "^6.0.4",
"@mantine/hooks": "^6.0.4",
"@mantine/modals": "^6.0.4",
"@mantine/next": "^6.0.4",
"@mantine/notifications": "^6.0.4",
"@mantine/prism": "^6.0.4",
"@mantine/spotlight": "^6.0.4",
"@prisma/client": "^4.10.1",
"@prisma/internals": "^4.10.1",
"@prisma/migrate": "^4.10.1",
"@sapphire/shapeshift": "^3.8.1",
"@tabler/icons-react": "^2.11.0",
"@tanstack/react-query": "^4.28.0",
"argon2": "^0.30.3",
"cookie": "^0.5.0",
"dotenv": "^16.0.1",
"dotenv-expand": "^8.0.3",
"fecha": "^4.2.3",
"fflate": "^0.7.3",
"find-my-way": "^6.3.0",
"minio": "^7.0.28",
"dayjs": "^1.11.7",
"dotenv": "^16.0.3",
"dotenv-expand": "^10.0.0",
"exiftool-vendored": "^21.2.0",
"fastify": "^4.15.0",
"fastify-plugin": "^4.5.0",
"fflate": "^0.7.4",
"find-my-way": "^7.6.0",
"katex": "^0.16.4",
"mantine-datatable": "^2.2.6",
"minio": "^7.0.33",
"ms": "canary",
"multer": "^1.4.5-lts.1",
"next": "^12.1.6",
"prisma": "^4.1.0",
"next": "^13.2.4",
"otplib": "^12.0.1",
"prisma": "^4.10.1",
"prismjs": "^1.29.0",
"qrcode": "^1.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10",
"react-redux": "^8.0.2",
"react-table": "^7.8.0",
"redux": "^4.2.0",
"sharp": "^0.30.7",
"yup": "^0.32.11"
"react-markdown": "^8.0.6",
"recharts": "^2.5.0",
"recoil": "^0.7.7",
"remark-gfm": "^3.0.1",
"sharp": "^0.32.0"
},
"devDependencies": {
"@types/cookie": "^0.5.1",
"@types/minio": "^7.0.13",
"@types/katex": "^0.16.0",
"@types/minio": "^7.0.17",
"@types/multer": "^1.4.7",
"@types/node": "^15.12.2",
"@types/sharp": "^0.30.5",
"babel-plugin-import": "^1.13.5",
"esbuild": "^0.14.44",
"eslint": "^7.32.0",
"eslint-config-next": "12.1.6",
"@types/node": "^18.15.10",
"@types/qrcode": "^1.5.0",
"@types/react": "^18.0.29",
"@types/sharp": "^0.31.1",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"cross-env": "^7.0.3",
"eslint": "^8.36.0",
"eslint-config-next": "^13.2.4",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-unused-imports": "^2.0.0",
"npm-run-all": "^4.1.5",
"ts-node": "^10.8.1",
"tsx": "^3.8.0",
"typescript": "^4.7.3"
"prettier": "^2.8.7",
"tsup": "^6.7.0",
"typescript": "^5.0.2"
},
"repository": {
"type": "git",
"url": "https://github.com/diced/zipline.git"
},
"packageManager": "yarn@3.2.1"
}
"packageManager": "yarn@3.3.1"
}

View File

@@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "oauth" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "oauthProvider" TEXT,
ALTER COLUMN "password" DROP NOT NULL;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "oauthAccessToken" TEXT;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "superAdmin" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Image" ADD COLUMN "maxViews" INTEGER;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Url" ADD COLUMN "maxViews" INTEGER;

View File

@@ -0,0 +1,31 @@
/*
Warnings:
- You are about to drop the column `oauth` on the `User` table. All the data in the column will be lost.
- You are about to drop the column `oauthAccessToken` on the `User` table. All the data in the column will be lost.
- You are about to drop the column `oauthProvider` on the `User` table. All the data in the column will be lost.
*/
-- CreateEnum
CREATE TYPE "OauthProviders" AS ENUM ('DISCORD', 'GITHUB');
-- AlterTable
ALTER TABLE "User" DROP COLUMN "oauth",
DROP COLUMN "oauthAccessToken",
DROP COLUMN "oauthProvider";
-- CreateTable
CREATE TABLE "OAuth" (
"id" SERIAL NOT NULL,
"provider" "OauthProviders" NOT NULL,
"userId" INTEGER NOT NULL,
"token" TEXT NOT NULL,
CONSTRAINT "OAuth_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "OAuth_provider_key" ON "OAuth"("provider");
-- AddForeignKey
ALTER TABLE "OAuth" ADD CONSTRAINT "OAuth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,5 @@
-- DropIndex
DROP INDEX "OAuth_provider_key";
-- AlterTable
ALTER TABLE "OAuth" ADD COLUMN "refresh" TEXT;

View File

@@ -0,0 +1,8 @@
-- DropForeignKey
ALTER TABLE "Image" DROP CONSTRAINT "Image_userId_fkey";
-- AlterTable
ALTER TABLE "Image" ALTER COLUMN "userId" DROP NOT NULL;
-- AddForeignKey
ALTER TABLE "Image" ADD CONSTRAINT "Image_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "OauthProviders" ADD VALUE 'GOOGLE';

View File

@@ -0,0 +1,8 @@
/*
Warnings:
- Added the required column `username` to the `OAuth` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "OAuth" ADD COLUMN "username" TEXT NOT NULL;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "totpSecret" TEXT;

View File

@@ -0,0 +1,26 @@
-- DropForeignKey
ALTER TABLE "InvisibleImage" DROP CONSTRAINT "InvisibleImage_imageId_fkey";
-- DropForeignKey
ALTER TABLE "InvisibleUrl" DROP CONSTRAINT "InvisibleUrl_urlId_fkey";
-- DropForeignKey
ALTER TABLE "Invite" DROP CONSTRAINT "Invite_createdById_fkey";
-- DropForeignKey
ALTER TABLE "Url" DROP CONSTRAINT "Url_userId_fkey";
-- AlterTable
ALTER TABLE "Url" ALTER COLUMN "userId" DROP NOT NULL;
-- AddForeignKey
ALTER TABLE "InvisibleImage" ADD CONSTRAINT "InvisibleImage_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "Image"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Url" ADD CONSTRAINT "Url_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InvisibleUrl" ADD CONSTRAINT "InvisibleUrl_urlId_fkey" FOREIGN KEY ("urlId") REFERENCES "Url"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,12 @@
/*
Warnings:
- A unique constraint covering the columns `[provider,oauthId]` on the table `OAuth` will be added. If there are existing duplicate values, this will fail.
- Added the required column `oauthId` to the `OAuth` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "OAuth" ADD COLUMN "oauthId" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "OAuth_provider_oauthId_key" ON "OAuth"("provider", "oauthId");

View File

@@ -0,0 +1,13 @@
/*
Warnings:
- You are about to drop the column `embedColor` on the `User` table. All the data in the column will be lost.
- You are about to drop the column `embedSiteName` on the `User` table. All the data in the column will be lost.
- You are about to drop the column `embedTitle` on the `User` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "User" DROP COLUMN "embedColor",
DROP COLUMN "embedSiteName",
DROP COLUMN "embedTitle",
ADD COLUMN "embed" JSONB NOT NULL DEFAULT '{}';

View File

@@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "Image" RENAME COLUMN "created_at" TO "createdAt";
-- AlterTable
ALTER TABLE "Image" RENAME COLUMN "expires_at" TO "expiresAt";
-- AlterTable
ALTER TABLE "Image" RENAME COLUMN "file" TO "name";

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Url" RENAME COLUMN "created_at" TO "createdAt";

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Stats" RENAME COLUMN "created_at" TO "createdAt";

View File

@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Invite" RENAME COLUMN "created_at" TO "createdAt";
-- AlterTable
ALTER TABLE "Invite" RENAME COLUMN "expires_at" TO "expiresAt";

View File

@@ -0,0 +1,19 @@
-- AlterEnum
ALTER TYPE "ImageFormat" RENAME TO "FileNameFormat";
-- AlterTable
ALTER TABLE "Image" RENAME TO "File";
-- AlterTable
ALTER TABLE "InvisibleImage" RENAME TO "InvisibleFile";
-- AlterTable
ALTER TABLE "InvisibleFile" RENAME COLUMN "imageId" TO "fileId";
-- AlterForeignKey
ALTER TABLE "InvisibleFile" RENAME CONSTRAINT "InvisibleImage_imageId_fkey" TO "InvisibleFile_fileId_fkey";
ALTER INDEX "InvisibleImage_imageId_key" RENAME TO "InvisibleFile_fileId_key";
-- AlterForeignKey
ALTER TABLE "InvisibleFile" RENAME CONSTRAINT "InvisibleImage_pkey" TO "InvisibleFile_pkey";
ALTER TABLE "File" RENAME CONSTRAINT "Image_pkey" TO "File_pkey";

View File

@@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "File" ADD COLUMN "originalName" TEXT;
-- RenameForeignKey
ALTER TABLE "File" RENAME CONSTRAINT "Image_userId_fkey" TO "File_userId_fkey";
-- RenameIndex
ALTER INDEX "InvisibleImage_invis_key" RENAME TO "InvisibleFile_invis_key";

View File

@@ -0,0 +1,19 @@
-- AlterTable
ALTER TABLE "File" ADD COLUMN "folderId" INTEGER;
-- CreateTable
CREATE TABLE "Folder" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" INTEGER NOT NULL,
CONSTRAINT "Folder_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Folder" ADD CONSTRAINT "Folder_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "File" ADD CONSTRAINT "File_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "Folder"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Folder" ADD COLUMN "public" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "File" ADD COLUMN "size" INTEGER NOT NULL DEFAULT 0;

View File

@@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the column `format` on the `File` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "File" DROP COLUMN "format";
-- DropEnum
DROP TYPE "FileNameFormat";

View File

@@ -0,0 +1,18 @@
-- CreateEnum
CREATE TYPE "ProcessingStatus" AS ENUM ('PENDING', 'PROCESSING', 'COMPLETE');
-- CreateTable
CREATE TABLE "IncompleteFile" (
"id" SERIAL NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"status" "ProcessingStatus" NOT NULL,
"chunks" INTEGER NOT NULL,
"chunksComplete" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,
"data" JSONB NOT NULL,
CONSTRAINT "IncompleteFile_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "IncompleteFile" ADD CONSTRAINT "IncompleteFile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,26 @@
-- CreateTable
CREATE TABLE "Tag" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"color" TEXT NOT NULL,
CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_FileToTag" (
"A" INTEGER NOT NULL,
"B" TEXT NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "_FileToTag_AB_unique" ON "_FileToTag"("A", "B");
-- CreateIndex
CREATE INDEX "_FileToTag_B_index" ON "_FileToTag"("B");
-- AddForeignKey
ALTER TABLE "_FileToTag" ADD CONSTRAINT "_FileToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "File"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FileToTag" ADD CONSTRAINT "_FileToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,53 @@
/*
Warnings:
- A unique constraint covering the columns `[uuid]` on the table `User` will be added. If there are existing duplicate values, this will fail.
*/
-- PRISMA GENERATED BELOW
-- -- DropForeignKey
-- ALTER TABLE "OAuth" DROP CONSTRAINT "OAuth_userId_fkey";
--
-- -- AlterTable
-- ALTER TABLE "OAuth" ALTER COLUMN "userId" SET DATA TYPE TEXT;
--
-- -- AlterTable
-- ALTER TABLE "User" ADD COLUMN "uuid" UUID NOT NULL DEFAULT gen_random_uuid();
--
-- -- CreateIndex
-- CREATE UNIQUE INDEX "User_uuid_key" ON "User"("uuid");
--
-- -- AddForeignKey
-- ALTER TABLE "OAuth" ADD CONSTRAINT "OAuth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;
-- User made changes below
-- Rename old foreign key
ALTER TABLE "OAuth" RENAME CONSTRAINT "OAuth_userId_fkey" TO "OAuth_userId_old_fkey";
-- Rename old column
ALTER TABLE "OAuth" RENAME COLUMN "userId" TO "userId_old";
-- Add new column
ALTER TABLE "OAuth" ADD COLUMN "userId" UUID;
-- Add user uuid
ALTER TABLE "User" ADD COLUMN "uuid" UUID NOT NULL DEFAULT gen_random_uuid();
-- Update table "OAuth" with uuid
UPDATE "OAuth" SET "userId" = "User"."uuid" FROM "User" WHERE "OAuth"."userId_old" = "User"."id";
-- Alter table "OAuth" to make "userId" required
ALTER TABLE "OAuth" ALTER COLUMN "userId" SET NOT NULL;
-- Create index
CREATE UNIQUE INDEX "User_uuid_key" ON "User"("uuid");
-- Add new foreign key
ALTER TABLE "OAuth" ADD CONSTRAINT "OAuth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;
-- Drop old foreign key
ALTER TABLE "OAuth" DROP CONSTRAINT "OAuth_userId_old_fkey";
-- Drop old column
ALTER TABLE "OAuth" DROP COLUMN "userId_old";

View File

@@ -9,83 +9,152 @@ generator client {
model User {
id Int @id @default(autoincrement())
uuid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid
username String
password String
password String?
avatar String?
token String
administrator Boolean @default(false)
superAdmin Boolean @default(false)
systemTheme String @default("system")
embedTitle String?
embedColor String @default("#2f3136")
embedSiteName String? @default("{image.file} • {user.name}")
embed Json @default("{}")
ratelimit DateTime?
totpSecret String?
domains String[]
images Image[]
oauth OAuth[]
files File[]
urls Url[]
Invite Invite[]
Folder Folder[]
IncompleteFile IncompleteFile[]
}
enum ImageFormat {
UUID
DATE
RANDOM
NAME
model Folder {
id Int @id @default(autoincrement())
name String
public Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
files File[]
}
model Image {
id Int @id @default(autoincrement())
file String
mimetype String @default("image/png")
created_at DateTime @default(now())
expires_at DateTime?
views Int @default(0)
favorite Boolean @default(false)
embed Boolean @default(false)
password String?
invisible InvisibleImage?
format ImageFormat @default(RANDOM)
user User @relation(fields: [userId], references: [id])
userId Int
model File {
id Int @id @default(autoincrement())
name String
originalName String?
mimetype String @default("image/png")
createdAt DateTime @default(now())
size Int @default(0)
expiresAt DateTime?
maxViews Int?
views Int @default(0)
favorite Boolean @default(false)
embed Boolean @default(false)
password String?
invisible InvisibleFile?
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
userId Int?
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
folderId Int?
tags Tag[]
}
model InvisibleImage {
id Int @id @default(autoincrement())
invis String @unique
imageId Int @unique
image Image @relation(fields: [imageId], references: [id])
model Tag {
id String @id @default(cuid())
name String
color String
files File[]
}
model InvisibleFile {
id Int @id @default(autoincrement())
invis String @unique
fileId Int @unique
file File @relation(fields: [fileId], references: [id], onDelete: Cascade)
}
model Url {
id String @id @unique
destination String
vanity String?
created_at DateTime @default(now())
createdAt DateTime @default(now())
maxViews Int?
views Int @default(0)
invisible InvisibleUrl?
user User @relation(fields: [userId], references: [id])
userId Int
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
userId Int?
}
model InvisibleUrl {
id Int @id @default(autoincrement())
invis String @unique
urlId String @unique
url Url @relation(fields: [urlId], references: [id])
url Url @relation(fields: [urlId], references: [id], onDelete: Cascade)
}
model Stats {
id Int @id @default(autoincrement())
created_at DateTime @default(now())
data Json
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
data Json
}
model Invite {
id Int @id @default(autoincrement())
code String @unique
created_at DateTime @default(now())
expires_at DateTime?
used Boolean @default(false)
id Int @id @default(autoincrement())
code String @unique
createdAt DateTime @default(now())
expiresAt DateTime?
used Boolean @default(false)
createdBy User @relation(fields: [createdById], references: [id])
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
createdById Int
}
model OAuth {
id Int @id @default(autoincrement())
provider OauthProviders
user User @relation(fields: [userId], references: [uuid], onDelete: Cascade)
userId String
username String
oauthId String?
token String
refresh String?
@@unique([provider, oauthId])
}
enum OauthProviders {
DISCORD
GITHUB
GOOGLE
}
model IncompleteFile {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
status ProcessingStatus
chunks Int
chunksComplete Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
data Json
}
enum ProcessingStatus {
PENDING
PROCESSING
COMPLETE
}

View File

@@ -1,31 +0,0 @@
import { PrismaClient } from '@prisma/client';
import { hashPassword, createToken } from '../src/lib/util';
const prisma = new PrismaClient();
async function main() {
const user = await prisma.user.create({
data: {
username: 'administrator',
password: await hashPassword('password'),
token: createToken(),
administrator: true,
},
});
console.log(`
When logging into Zipline for the first time, use these credentials:
Username: "${user.username}"
Password: "password"
`);
}
main()
.catch(e => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

1501
public/adjectives.txt Normal file

File diff suppressed because it is too large Load Diff

1750
public/animals.txt Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,6 @@
import { Anchor } from '@mantine/core';
import Link from 'next/link';
export default function AnchorNext({ href, ...others }) {
return <Anchor component={Link} href={href} {...others} />;
}

View File

@@ -1,11 +1,10 @@
import { Card as MCard, Title } from '@mantine/core';
export default function Card({ name, children, ...other }) {
return (
<MCard p='md' shadow='sm' {...other}>
<Title order={2}>{name}</Title>
{name && <Title order={2}>{name}</Title>}
{children}
</MCard>
);
}
}

View File

@@ -1,21 +1,36 @@
import { createStyles, MantineSize, Textarea } from '@mantine/core';
import { createStyles, Textarea } from '@mantine/core';
import { useEffect } from 'react';
const useStyles = createStyles((theme, { size }: { size: MantineSize }) => ({
const useStyles = createStyles(() => ({
input: {
fontFamily: 'monospace',
fontSize: theme.fn.size({ size, sizes: theme.fontSizes }) - 2,
height: '100vh',
height: '80vh',
},
}));
export default function CodeInput({ ...props }) {
const { classes } = useStyles({ size: 'md' }, { name: 'CodeInput' });
const { classes } = useStyles(null, { name: 'CodeInput' });
return (
<Textarea
classNames={{ input: classes.input }}
autoComplete='nope'
{...props}
/>
);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Tab') {
if (document.activeElement?.tagName !== 'TEXTAREA') return;
e.preventDefault();
const target = e.target as HTMLTextAreaElement;
const start = target.selectionStart;
const end = target.selectionEnd;
target.value = `${target.value.substring(0, start)} ${target.value.substring(end)}`;
target.selectionStart = target.selectionEnd = start + 2;
target.focus();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, []);
return <Textarea classNames={{ input: classes.input }} {...props} />;
}

View File

@@ -1,116 +0,0 @@
import { Button, Card, Group, Modal, Stack, Text, Title, useMantineTheme } from '@mantine/core';
import { useClipboard } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
import useFetch from 'hooks/useFetch';
import { useState } from 'react';
import Type from './Type';
import { CalendarIcon, ClockIcon, CopyIcon, CrossIcon, DeleteIcon, FileIcon, HashIcon, ImageIcon, StarIcon } from './icons';
import MutedText from './MutedText';
import { relativeTime } from 'lib/clientUtils';
export function FileMeta({ Icon, title, subtitle }) {
return (
<Group>
<Icon size={24} />
<Stack spacing={1}>
<Text>{title}</Text>
<MutedText size='md'>{subtitle}</MutedText>
</Stack>
</Group>
);
}
export default function File({ image, updateImages }) {
const [open, setOpen] = useState(false);
const clipboard = useClipboard();
const handleDelete = async () => {
const res = await useFetch('/api/user/files', 'DELETE', { id: image.id });
if (!res.error) {
updateImages(true);
showNotification({
title: 'File Deleted',
message: '',
color: 'green',
icon: <DeleteIcon />,
});
} else {
showNotification({
title: 'Failed to delete file',
message: res.error,
color: 'red',
icon: <CrossIcon />,
});
}
setOpen(false);
};
const handleCopy = () => {
clipboard.copy(`${window.location.protocol}//${window.location.host}${image.url}`);
setOpen(false);
showNotification({
title: 'Copied to clipboard',
message: '',
icon: <CopyIcon />,
});
};
const handleFavorite = async () => {
const data = await useFetch('/api/user/files', 'PATCH', { id: image.id, favorite: !image.favorite });
if (!data.error) updateImages(true);
showNotification({
title: 'Image is now ' + (!image.favorite ? 'favorited' : 'unfavorited'),
message: '',
icon: <StarIcon />,
});
};
return (
<>
<Modal
opened={open}
onClose={() => setOpen(false)}
title={<Title>{image.file}</Title>}
size='xl'
>
<Stack>
<Type
file={image}
src={image.url}
alt={image.file}
popup
sx={{ minHeight: 200 }}
style={{ minHeight: 200 }}
/>
<Stack>
<FileMeta Icon={FileIcon} title='Name' subtitle={image.file} />
<FileMeta Icon={ImageIcon} title='Type' subtitle={image.mimetype} />
<FileMeta Icon={CalendarIcon} title='Uploaded at' subtitle={new Date(image.created_at).toLocaleString()} />
{image.expires_at && <FileMeta Icon={ClockIcon} title='Expires' subtitle={relativeTime(new Date(image.expires_at))} />}
<FileMeta Icon={HashIcon} title='ID' subtitle={image.id} />
</Stack>
</Stack>
<Group position='right' mt={22}>
<Button onClick={handleCopy}>Copy</Button>
<Button onClick={handleDelete}>Delete</Button>
<Button onClick={handleFavorite}>{image.favorite ? 'Unfavorite' : 'Favorite'}</Button>
</Group>
</Modal>
<Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md'>
<Card.Section>
<Type
file={image}
sx={{ minHeight: 200, maxHeight: 320, fontSize: 70, width: '100%', cursor: 'pointer' }}
style={{ minHeight: 200, maxHeight: 320, fontSize: 70, width: '100%', cursor: 'pointer' }}
src={image.url}
alt={image.file}
onClick={() => setOpen(true)}
/>
</Card.Section>
</Card>
</>
);
}

View File

@@ -0,0 +1,474 @@
import {
ActionIcon,
Group,
LoadingOverlay,
Modal,
MultiSelect,
Select,
SimpleGrid,
Stack,
Title,
Tooltip,
Text,
Accordion,
} from '@mantine/core';
import { useClipboard, useMediaQuery } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
import {
IconAlarm,
IconCalendarPlus,
IconClipboardCopy,
IconDeviceSdCard,
IconExternalLink,
IconEye,
IconEyeglass,
IconFile,
IconFileDownload,
IconFolderCancel,
IconFolderMinus,
IconFolderPlus,
IconFolders,
IconHash,
IconInfoCircle,
IconPhoto,
IconPhotoCancel,
IconPhotoMinus,
IconPhotoStar,
IconPlus,
IconTags,
} from '@tabler/icons-react';
import useFetch, { ApiError } from 'hooks/useFetch';
import { useFileDelete, useFileFavorite, UserFilesResponse } from 'lib/queries/files';
import { useFolders } from 'lib/queries/folders';
import { bytesToHuman } from 'lib/utils/bytes';
import { colorHash, relativeTime } from 'lib/utils/client';
import { useState } from 'react';
import { FileMeta } from '.';
import Type from '../Type';
import Tag from 'components/File/tag/Tag';
import Item from 'components/File/tag/Item';
import { useDeleteFileTags, useFileTags, useTags, useUpdateFileTags } from 'lib/queries/tags';
export default function FileModal({
open,
setOpen,
file,
loading,
refresh,
reducedActions = false,
exifEnabled,
compress,
}: {
open: boolean;
setOpen: (open: boolean) => void;
file: UserFilesResponse;
loading: boolean;
refresh: () => void;
reducedActions?: boolean;
exifEnabled?: boolean;
compress: boolean;
}) {
const deleteFile = useFileDelete();
const favoriteFile = useFileFavorite();
const folders = useFolders();
const tags = useFileTags(file.id);
const updateTags = useUpdateFileTags(file.id);
const removeTags = useDeleteFileTags(file.id);
const clipboard = useClipboard();
const allTags = useTags();
const [overrideRender, setOverrideRender] = useState(false);
const handleDelete = async () => {
deleteFile.mutate(file.id, {
onSuccess: () => {
showNotification({
title: 'File Deleted',
message: '',
color: 'green',
icon: <IconPhotoMinus size='1rem' />,
});
},
onError: (res: ApiError) => {
showNotification({
title: 'Failed to delete file',
message: res.error,
color: 'red',
icon: <IconPhotoCancel size='1rem' />,
});
},
onSettled: () => {
setOpen(false);
},
});
};
const handleCopy = () => {
clipboard.copy(`${window.location.protocol}//${window.location.host}${file.url}`);
setOpen(false);
if (!navigator.clipboard)
showNotification({
title: 'Unable to copy to clipboard',
message: 'Zipline is unable to copy to clipboard due to security reasons.',
color: 'red',
});
else
showNotification({
title: 'Copied to clipboard',
message: '',
icon: <IconClipboardCopy size='1rem' />,
});
};
const handleFavorite = async () => {
favoriteFile.mutate(
{ id: file.id, favorite: !file.favorite },
{
onSuccess: () => {
showNotification({
title: 'The file is now ' + (!file.favorite ? 'favorited' : 'unfavorited'),
message: '',
icon: <IconPhotoStar size='1rem' />,
});
},
onError: (res: { error: string }) => {
showNotification({
title: 'Failed to favorite file',
message: res.error,
color: 'red',
icon: <IconPhotoCancel size='1rem' />,
});
},
}
);
};
const inFolder = file.folderId;
const removeFromFolder = async () => {
const res = await useFetch('/api/user/folders/' + file.folderId, 'DELETE', {
file: Number(file.id),
});
refresh();
if (!res.error) {
showNotification({
title: 'Removed from folder',
message: res.name,
color: 'green',
icon: <IconFolderMinus size='1rem' />,
});
} else {
showNotification({
title: 'Failed to remove from folder',
message: res.error,
color: 'red',
icon: <IconFolderCancel size='1rem' />,
});
}
};
const addToFolder = async (t) => {
const res = await useFetch('/api/user/folders/' + t, 'POST', {
file: Number(file.id),
});
refresh();
if (!res.error) {
showNotification({
title: 'Added to folder',
message: res.name,
color: 'green',
icon: <IconFolderPlus size='1rem' />,
});
} else {
showNotification({
title: 'Failed to add to folder',
message: res.error,
color: 'red',
icon: <IconFolderCancel size='1rem' />,
});
}
};
const createFolder = (t) => {
useFetch('/api/user/folders', 'POST', {
name: t,
add: [Number(file.id)],
}).then((res) => {
refresh();
if (!res.error) {
showNotification({
title: 'Created & added to folder',
message: res.name,
color: 'green',
icon: <IconFolderPlus size='1rem' />,
});
} else {
showNotification({
title: 'Failed to create folder',
message: res.error,
color: 'red',
icon: <IconFolderCancel size='1rem' />,
});
}
});
return { value: t, label: t };
};
const handleTagsSave = () => {
console.log('should save');
};
const handleAddTags = (t: string[]) => {
// filter out existing tags from t
t = t.filter((tag) => !tags.data.find((t) => t.id === tag));
const fullTag = allTags.data.find((tag) => tag.id === t[0]);
if (!fullTag) return;
updateTags.mutate([...tags.data, fullTag], {
onSuccess: () => {
showNotification({
title: 'Added tag',
message: fullTag.name,
color: 'green',
icon: <IconTags size='1rem' />,
});
},
});
};
const handleRemoveTags = (t: string[]) => {
const fullTag = allTags.data.find((tag) => tag.id === t[0]);
removeTags.mutate(t, {
onSuccess: () =>
showNotification({
title: 'Removed tag',
message: fullTag.name,
color: 'green',
icon: <IconTags size='1rem' />,
}),
});
};
return (
<Modal
opened={open}
onClose={() => setOpen(false)}
title={<Title>{file.name}</Title>}
size='lg'
fullScreen={useMediaQuery('(max-width: 600px)')}
>
<LoadingOverlay visible={loading} />
<Stack>
<Type
file={file}
src={`/r/${encodeURI(file.name)}?compress=${compress}`}
alt={file.name}
popup
disableMediaPreview={false}
overrideRender={overrideRender}
setOverrideRender={setOverrideRender}
/>
<SimpleGrid
my='md'
cols={3}
breakpoints={[
{ maxWidth: 600, cols: 1 },
{ maxWidth: 900, cols: 2 },
{ maxWidth: 1200, cols: 3 },
]}
>
<FileMeta Icon={IconFile} title='Name' subtitle={file.name} />
<FileMeta Icon={IconPhoto} title='Type' subtitle={file.mimetype} />
<FileMeta Icon={IconDeviceSdCard} title='Size' subtitle={bytesToHuman(file.size || 0)} />
<FileMeta Icon={IconEye} title='Views' subtitle={file?.views?.toLocaleString()} />
{file.maxViews && (
<FileMeta
Icon={IconEyeglass}
title='Max views'
subtitle={file?.maxViews?.toLocaleString()}
tooltip={`This file will be deleted after being viewed ${file?.maxViews?.toLocaleString()} times.`}
/>
)}
<FileMeta
Icon={IconCalendarPlus}
title='Uploaded'
subtitle={relativeTime(new Date(file.createdAt))}
tooltip={new Date(file?.createdAt).toLocaleString()}
/>
{file.expiresAt && !reducedActions && (
<FileMeta
Icon={IconAlarm}
title='Expires'
subtitle={relativeTime(new Date(file.expiresAt))}
tooltip={new Date(file.expiresAt).toLocaleString()}
/>
)}
<FileMeta Icon={IconHash} title='ID' subtitle={file.id} />
</SimpleGrid>
</Stack>
{!reducedActions ? (
<Accordion
variant='contained'
mb='sm'
styles={(t) => ({
content: { backgroundColor: t.colorScheme === 'dark' ? t.colors.dark[7] : t.colors.gray[0] },
control: { backgroundColor: t.colorScheme === 'dark' ? t.colors.dark[7] : t.colors.gray[0] },
})}
>
<Accordion.Item value='tags'>
<Accordion.Control icon={<IconTags size='1rem' />}>Tags</Accordion.Control>
<Accordion.Panel>
<MultiSelect
value={tags.data?.map((t) => t.id) ?? []}
data={allTags.data?.map((t) => ({ value: t.id, label: t.name, color: t.color })) ?? []}
placeholder={allTags.data?.length ? 'Add tags' : 'Add tags (optional)'}
icon={<IconTags size='1rem' />}
valueComponent={Tag}
itemComponent={Item}
searchable
creatable
getCreateLabel={(t) => (
<Group>
<IconPlus size='1rem' />
<Text ml='sm' display='flex'>
Create tag{' '}
<Text ml={4} color={colorHash(t)}>
&quot;{t}&quot;
</Text>
</Text>
</Group>
)}
// onChange={(t) => (t.length === 1 ? handleRemoveTags(t) : handleAddTags(t))}
onChange={(t) => console.log(t)}
onCreate={(t) => {
const item = { value: t, label: t, color: colorHash(t) };
// setLabelTags([...labelTags, item]);
return item;
}}
onBlur={handleTagsSave}
/>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value='folders'>
<Accordion.Control icon={<IconFolders size='1rem' />}>Folders</Accordion.Control>
<Accordion.Panel>
{inFolder && !folders.isLoading ? (
<Group>
<Tooltip
label={`Remove from folder "${
folders.data.find((f) => f.id === file.folderId)?.name ?? ''
}"`}
>
<ActionIcon
color='red'
variant='filled'
onClick={removeFromFolder}
loading={folders.isLoading}
>
<IconFolderMinus size='1rem' />
</ActionIcon>
</Tooltip>
<Text display='flex' align='center'>
Currently in folder &quot;{folders.data.find((f) => f.id === file.folderId)?.name ?? ''}
&quot;
</Text>
</Group>
) : (
<Tooltip label='Add to folder'>
<Select
icon={<IconFolderPlus size='1rem' />}
onChange={addToFolder}
placeholder='Add to folder'
data={[
...(folders.data ? folders.data : []).map((folder) => ({
value: String(folder.id),
label: `${folder.id}: ${folder.name}`,
})),
]}
searchable
creatable
getCreateLabel={(query) => `Create folder "${query}"`}
onCreate={createFolder}
/>
</Tooltip>
)}
</Accordion.Panel>
</Accordion.Item>
</Accordion>
) : null}
<Group position='apart' my='md'>
<Group position='left'>
{exifEnabled && !reducedActions && (
<Tooltip label='View Metadata'>
<ActionIcon
color='blue'
variant='filled'
onClick={() => window.open(`/dashboard/metadata/${file.id}`, '_blank')}
>
<IconInfoCircle size='1rem' />
</ActionIcon>
</Tooltip>
)}
</Group>
<Group position='right'>
{reducedActions ? null : (
<>
<Tooltip label='Delete file'>
<ActionIcon color='red' variant='filled' onClick={handleDelete}>
<IconPhotoMinus size='1rem' />
</ActionIcon>
</Tooltip>
<Tooltip label={file.favorite ? 'Unfavorite' : 'Favorite'}>
<ActionIcon
color={file.favorite ? 'yellow' : 'gray'}
variant='filled'
onClick={handleFavorite}
>
<IconPhotoStar size='1rem' />
</ActionIcon>
</Tooltip>
</>
)}
<Tooltip label='Open in new tab'>
<ActionIcon color='blue' variant='filled' onClick={() => window.open(file.url, '_blank')}>
<IconExternalLink size='1rem' />
</ActionIcon>
</Tooltip>
<Tooltip label='Copy URL'>
<ActionIcon color='blue' variant='filled' onClick={handleCopy}>
<IconClipboardCopy size='1rem' />
</ActionIcon>
</Tooltip>
<Tooltip label='Download'>
<ActionIcon
color='blue'
variant='filled'
onClick={() => window.open(`/r/${encodeURI(file.name)}?download=true`, '_blank')}
>
<IconFileDownload size='1rem' />
</ActionIcon>
</Tooltip>
</Group>
</Group>
</Modal>
);
}

View File

@@ -0,0 +1,91 @@
import { Card, Group, LoadingOverlay, Stack, Text, Tooltip } from '@mantine/core';
import { useFileDelete, useFileFavorite } from 'lib/queries/files';
import { useFolders } from 'lib/queries/folders';
import { useState } from 'react';
import MutedText from '../MutedText';
import Type from '../Type';
import FileModal from './FileModal';
export function FileMeta({ Icon, title, subtitle, ...other }) {
return other.tooltip ? (
<Group>
<Icon size={24} />
<Tooltip label={other.tooltip}>
<Stack spacing={1}>
<Text>{title}</Text>
<MutedText size='md'>{subtitle}</MutedText>
</Stack>
</Tooltip>
</Group>
) : (
<Group>
<Icon size={24} />
<Stack spacing={1}>
<Text>{title}</Text>
<MutedText size='md'>{subtitle}</MutedText>
</Stack>
</Group>
);
}
export default function File({
image,
disableMediaPreview,
exifEnabled,
refreshImages,
reducedActions = false,
onDash,
}) {
const [open, setOpen] = useState(false);
const deleteFile = useFileDelete();
const favoriteFile = useFileFavorite();
const loading = deleteFile.isLoading || favoriteFile.isLoading;
const folders = useFolders();
const refresh = () => {
refreshImages();
folders.refetch();
};
return (
<>
<FileModal
open={open}
setOpen={setOpen}
file={image}
loading={loading}
refresh={refresh}
reducedActions={reducedActions}
exifEnabled={exifEnabled}
compress={onDash}
/>
<Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md' onClick={() => setOpen(true)}>
<Card.Section>
<LoadingOverlay visible={loading} />
<Type
file={image}
sx={{
minHeight: 200,
maxHeight: 320,
fontSize: 70,
width: '100%',
cursor: 'pointer',
}}
style={{
minHeight: 200,
maxHeight: 320,
fontSize: 70,
width: '100%',
cursor: 'pointer',
}}
src={`/r/${encodeURI(image.name)}?compress=${onDash}`}
alt={image.name}
disableMediaPreview={disableMediaPreview}
/>
</Card.Section>
</Card>
</>
);
}

View File

@@ -0,0 +1,17 @@
import { ComponentPropsWithoutRef, forwardRef } from 'react';
import { Group, Text } from '@mantine/core';
interface ItemProps extends ComponentPropsWithoutRef<'div'> {
color: string;
label: string;
}
const Item = forwardRef<HTMLDivElement, ItemProps>(({ color, label, ...others }: ItemProps, ref) => (
<div ref={ref} {...others}>
<Group noWrap>
<Text color={color}>{label}</Text>
</Group>
</div>
));
export default Item;

View File

@@ -0,0 +1,26 @@
import { Box, CloseButton, MultiSelectValueProps, rem } from '@mantine/core';
export default function Tag({
label,
onRemove,
color,
...others
}: MultiSelectValueProps & { color: string }) {
return (
<div {...others}>
<Box
sx={(theme) => ({
display: 'flex',
cursor: 'default',
alignItems: 'center',
backgroundColor: color,
paddingLeft: theme.spacing.xs,
borderRadius: theme.radius.sm,
})}
>
<Box sx={{ lineHeight: 1, fontSize: rem(12) }}>{label}</Box>
<CloseButton onMouseDown={onRemove} variant='transparent' size={22} iconSize={14} tabIndex={-1} />
</Box>
</div>
);
}

View File

@@ -1,168 +0,0 @@
/* eslint-disable react/jsx-key */
// Code taken from https://codesandbox.io/s/eojw8 and is modified a bit (the entire src/components/table directory)
import {
ActionIcon,
createStyles,
Divider,
Group, Image, Pagination,
Select,
Table,
Text,
useMantineTheme,
} from '@mantine/core';
import {
usePagination,
useTable,
} from 'react-table';
import { CopyIcon, DeleteIcon, EnterIcon } from './icons';
const pageSizeOptions = ['10', '25', '50'];
const useStyles = createStyles((t) => ({
root: { height: '100%', display: 'block', marginTop: 10 },
tableContainer: {
display: 'block',
overflow: 'auto',
'& > table': {
'& > thead': { backgroundColor: t.colorScheme === 'dark' ? t.colors.dark[6] : t.colors.gray[0], zIndex: 1 },
'& > thead > tr > th': { padding: t.spacing.md },
'& > tbody > tr > td': { padding: t.spacing.md },
},
borderRadius: 6,
},
stickHeader: { top: 0, position: 'sticky' },
disableSortIcon: { color: t.colors.gray[5] },
sortDirectionIcon: { transition: 'transform 200ms ease' },
}));
export function FilePreview({ url, type }) {
const Type = props => {
return {
'video': <video autoPlay controls {...props} />,
'image': <Image {...props} />,
'audio': <audio autoPlay controls {...props} />,
}[type.split('/')[0]];
};
return (
<Type
sx={{ maxWidth: '10vw', maxHeight: '100vh' }}
style={{ maxWidth: '10vw', maxHeight: '100vh' }}
mr='sm'
src={url}
alt={'Unable to preview file'}
/>
);
}
export default function ImagesTable({
columns,
data = [],
serverSideDataSource = false,
initialPageSize = 10,
initialPageIndex = 0,
pageCount = 0,
total = 0,
deleteImage, copyImage, viewImage,
}) {
const { classes } = useStyles();
const theme = useMantineTheme();
const tableOptions = useTable(
{
data,
columns,
pageCount,
initialState: { pageSize: initialPageSize, pageIndex: initialPageIndex },
},
usePagination
);
const {
getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, page, gotoPage, setPageSize, state: { pageIndex, pageSize },
} = tableOptions;
const getPageRecordInfo = () => {
const firstRowNum = pageIndex * pageSize + 1;
const totalRows = rows.length;
const currLastRowNum = (pageIndex + 1) * pageSize;
let lastRowNum = currLastRowNum < totalRows ? currLastRowNum : totalRows;
return `${firstRowNum} - ${lastRowNum} of ${totalRows}`;
};
const getPageCount = () => Math.ceil(rows.length / pageSize);
const handlePageChange = (pageNum) => gotoPage(pageNum - 1);
const renderHeader = () => headerGroups.map(hg => (
<tr {...hg.getHeaderGroupProps()}>
{hg.headers.map(column => (
<th {...column.getHeaderProps()}>
<Group noWrap position={column.align || 'apart'}>
<div>{column.render('Header')}</div>
</Group>
</th>
))}
<th>Actions</th>
</tr>
));
const renderRow = rows => rows.map(row => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => (
<td align={cell.column.align || 'left'} {...cell.getCellProps()}>
{cell.render('Cell')}
</td>
))}
<td align='right'>
<Group noWrap>
<ActionIcon color='red' variant='outline' onClick={() => deleteImage(row)}><DeleteIcon /></ActionIcon>
<ActionIcon color='primary' variant='outline' onClick={() => copyImage(row)}><CopyIcon /></ActionIcon>
<ActionIcon color='green' variant='outline' onClick={() => viewImage(row)}><EnterIcon /></ActionIcon>
</Group>
</td>
</tr>
);
});
return (
<div className={classes.root}>
<div
className={classes.tableContainer}
style={{ height: 'calc(100% - 44px)' }}
>
<Table {...getTableProps()}>
<thead style={{ backgroundColor: theme.other.hover }}>
{renderHeader()}
</thead>
<tbody {...getTableBodyProps()}>
{renderRow(page)}
</tbody>
</Table>
</div>
<Divider mb='md' variant='dotted' />
<Group position='left'>
<Text size='sm'>Rows per page: </Text>
<Select
style={{ width: '72px' }}
variant='filled'
data={pageSizeOptions}
value={pageSize + ''}
onChange={pageSize => setPageSize(Number(pageSize))} />
<Divider orientation='vertical' />
<Text size='sm'>{getPageRecordInfo()}</Text>
<Divider orientation='vertical' />
<Pagination
page={pageIndex + 1}
total={getPageCount()}
onChange={handlePageChange} />
</Group>
</div>
);
}

View File

@@ -1,243 +1,335 @@
import { AppShell, Box, Burger, Button, Divider, Header, MediaQuery, Navbar, NavLink, Paper, Popover, ScrollArea, Select, Stack, Text, Title, UnstyledButton, useMantineTheme, Group, Image } from '@mantine/core';
import { useClipboard } from '@mantine/hooks';
import {
AppShell,
Badge,
Box,
Burger,
Button,
Group,
Header,
Image,
Input,
MediaQuery,
Menu,
Navbar,
NavLink,
Paper,
rem,
ScrollArea,
Select,
Text,
Title,
Tooltip,
useMantineTheme,
} from '@mantine/core';
import { useClipboard, useMediaQuery } from '@mantine/hooks';
import { useModals } from '@mantine/modals';
import { showNotification } from '@mantine/notifications';
import {
IconBackspace,
IconBrandDiscordFilled,
IconBrandGithubFilled,
IconBrandGoogle,
IconBrush,
IconClipboardCopy,
IconExternalLink,
IconFiles,
IconFileText,
IconFileUpload,
IconFolders,
IconGraph,
IconHome,
IconLink,
IconLogout,
IconReload,
IconSettings,
IconTag,
IconUpload,
IconUser,
IconUserCog,
IconUsers,
} from '@tabler/icons-react';
import useFetch from 'hooks/useFetch';
import { updateUser } from 'lib/redux/reducers/user';
import { useStoreDispatch } from 'lib/redux/store';
import { useVersion } from 'lib/queries/version';
import { userSelector } from 'lib/recoil/user';
import { capitalize } from 'lib/utils/client';
import { UserExtended } from 'middleware/withZipline';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { ActivityIcon, CheckIcon, CopyIcon, CrossIcon, DeleteIcon, FileIcon, HomeIcon, LinkIcon, LogoutIcon, PencilIcon, SettingsIcon, TagIcon, TypeIcon, UploadIcon, UserIcon } from './icons';
import { useRecoilState } from 'recoil';
import { friendlyThemeName, themes } from './Theming';
function MenuItemLink(props) {
return (
<Link href={props.href} passHref>
<MenuItem {...props} />
</Link>
);
}
export type NavbarItems = {
icon: React.ReactNode;
text: string;
link?: string;
children?: NavbarItems[];
if?: (user: UserExtended, props: unknown) => boolean;
};
function MenuItem(props) {
return (
<UnstyledButton
sx={theme => ({
display: 'block',
width: '100%',
padding: 5,
borderRadius: theme.radius.sm,
color: props.color
? theme.fn.themeColor(props.color, theme.colorScheme === 'dark' ? 5 : 7)
: theme.colorScheme === 'dark'
? theme.colors.dark[0]
: theme.black,
'&:hover': {
backgroundColor: props.color
? theme.fn.rgba(
theme.fn.themeColor(props.color, theme.colorScheme === 'dark' ? 9 : 0),
theme.colorScheme === 'dark' ? 0.2 : 1
)
: theme.colorScheme === 'dark'
? theme.fn.rgba(theme.colors.dark[3], 0.35)
: theme.colors.gray[0],
},
})}
{...props}
>
<Group noWrap>
<Box sx={theme => ({
marginRight: theme.spacing.xs / 4,
paddingLeft: theme.spacing.xs / 2,
'& *': {
display: 'block',
},
})}>
{props.icon}
</Box>
<Text size='sm'>{props.children}</Text>
</Group>
</UnstyledButton>
);
}
const items = [
const items: NavbarItems[] = [
{
icon: <HomeIcon size={18} />,
icon: <IconHome size={18} />,
text: 'Home',
link: '/dashboard',
},
{
icon: <FileIcon size={18} />,
icon: <IconFiles size={18} />,
text: 'Files',
link: '/dashboard/files',
},
{
icon: <ActivityIcon size={18} />,
icon: <IconFolders size={18} />,
text: 'Folders',
link: '/dashboard/folders',
},
{
icon: <IconGraph size={18} />,
text: 'Stats',
link: '/dashboard/stats',
},
{
icon: <LinkIcon size={18} />,
icon: <IconLink size={18} />,
text: 'URLs',
link: '/dashboard/urls',
},
{
icon: <UploadIcon size={18} />,
icon: <IconUpload size={18} />,
text: 'Upload',
link: '/dashboard/upload',
children: [
{
icon: <IconFileUpload size={18} />,
text: 'File',
link: '/dashboard/upload/file',
},
{
icon: <IconFileText size={18} />,
text: 'Text',
link: '/dashboard/upload/text',
},
],
},
{
icon: <TypeIcon size={18} />,
text: 'Upload Text',
link: '/dashboard/text',
icon: <IconUser size={18} />,
text: 'Administration',
if: (user, _) => user.administrator as boolean,
children: [
{
icon: <IconUsers size={18} />,
text: 'Users',
link: '/dashboard/users',
if: () => true,
},
{
icon: <IconTag size={18} />,
text: 'Invites',
link: '/dashboard/invites',
if: (_, props: { invites: boolean }) => props.invites,
},
],
},
];
const admin_items = [
{
icon: <UserIcon size={18} />,
text: 'Users',
link: '/dashboard/users',
},
{
icon: <TagIcon size={18} />,
text: 'Invites',
link: '/dashboard/invites',
},
];
export default function Layout({ children, props }) {
const [user, setUser] = useRecoilState(userSelector);
const { title, oauth_providers: unparsed } = props;
const oauth_providers = JSON.parse(unparsed);
const icons = {
GitHub: IconBrandGithubFilled,
Discord: IconBrandDiscordFilled,
Google: IconBrandGoogle,
};
for (const provider of oauth_providers) {
provider.Icon = icons[provider.name];
}
const external_links = JSON.parse(props.external_links ?? '[]');
export default function Layout({ children, user, title }) {
const [token, setToken] = useState(user?.token);
const [systemTheme, setSystemTheme] = useState(user.systemTheme ?? 'system');
const [avatar, setAvatar] = useState(user.avatar ?? null);
const version = useVersion();
const [opened, setOpened] = useState(false); // navigation open
const [open, setOpen] = useState(false); // manage acc dropdown
const avatar = user?.avatar ?? null;
const router = useRouter();
const dispatch = useStoreDispatch();
const theme = useMantineTheme();
const modals = useModals();
const clipboard = useClipboard();
const handleUpdateTheme = async value => {
const handleUpdateTheme = async (value) => {
const newUser = await useFetch('/api/user', 'PATCH', {
systemTheme: value || 'dark_blue',
});
setSystemTheme(newUser.systemTheme);
dispatch(updateUser(newUser));
setUser(newUser);
router.replace(router.pathname);
showNotification({
title: `Theme changed to ${friendlyThemeName[value]}`,
message: '',
color: 'green',
icon: <PencilIcon />,
icon: <IconBrush size='1rem' />,
});
};
const openResetToken = () => modals.openConfirmModal({
title: 'Reset Token',
children: (
<Text size='sm'>
Once you reset your token, you will have to update any uploaders to use this new token.
</Text>
),
labels: { confirm: 'Reset', cancel: 'Cancel' },
onConfirm: async () => {
const a = await useFetch('/api/user/token', 'PATCH');
if (!a.success) {
setToken(a.success);
showNotification({
title: 'Token Reset Failed',
message: a.error,
color: 'red',
icon: <CrossIcon />,
});
} else {
showNotification({
title: 'Token Reset',
message: 'Your token has been reset. You will need to update any uploaders to use this new token.',
color: 'green',
icon: <CheckIcon />,
});
}
const openResetToken = () =>
modals.openConfirmModal({
title: <Title>Reset Token?</Title>,
children: (
<Text size='sm'>
Once you reset your token, you will have to update any uploaders to use this new token.
</Text>
),
labels: { confirm: 'Reset', cancel: 'Cancel' },
onConfirm: async () => {
const a = await useFetch('/api/user/token', 'PATCH');
if (!a.success) {
setToken(a.success);
showNotification({
title: 'Token Reset Failed',
message: a.error,
color: 'red',
icon: <IconReload size='1rem' />,
});
} else {
showNotification({
title: 'Token Reset',
message:
'Your token has been reset. You will need to update any uploaders to use this new token.',
color: 'green',
icon: <IconReload size='1rem' />,
});
}
modals.closeAll();
},
});
modals.closeAll();
},
});
const openCopyToken = () => modals.openConfirmModal({
title: 'Copy Token',
children: (
<Text size='sm'>
Make sure you don&apos;t share this token with anyone as they will be able to upload files on your behalf.
</Text>
),
labels: { confirm: 'Copy', cancel: 'Cancel' },
onConfirm: async () => {
clipboard.copy(token);
const openCopyToken = () =>
modals.openConfirmModal({
title: <Title>Copy Token</Title>,
children: (
<Text size='sm'>
Make sure you don&apos;t share this token with anyone as they will be able to upload files on your
behalf.
</Text>
),
labels: { confirm: 'Copy', cancel: 'Cancel' },
onConfirm: async () => {
clipboard.copy(token);
if (!navigator.clipboard)
showNotification({
title: 'Unable to copy to clipboard',
message: (
<Text size='sm'>
Zipline is unable to copy to clipboard due to security reasons. However, you can still copy
the token manually.
<br />
<Group position='left' spacing='sm'>
<Text>Your token is:</Text>
<Input size='sm' onFocus={(e) => e.target.select()} type='text' value={token} />
</Group>
</Text>
),
color: 'red',
});
else
showNotification({
title: 'Token Copied',
message: 'Your token has been copied to your clipboard.',
color: 'green',
icon: <IconClipboardCopy size='1rem' />,
});
showNotification({
title: 'Token Copied',
message: 'Your token has been copied to your clipboard.',
color: 'green',
icon: <CheckIcon />,
});
modals.closeAll();
},
});
modals.closeAll();
},
});
return (
<AppShell
navbarOffsetBreakpoint='sm'
fixed
navbar={
<Navbar
pt='sm'
hiddenBreakpoint='sm'
hidden={!opened}
width={{ sm: 200, lg: 230 }}
>
<Navbar.Section
grow
component={ScrollArea}
>
{items.map(({ icon, text, link }) => (
<Link href={link} key={text} passHref>
<NavLink
component='a'
label={text}
icon={icon}
active={router.pathname === link}
variant='light'
/>
</Link>
))}
{user.administrator && (
<NavLink
label='Administration'
icon={<SettingsIcon />}
childrenOffset={28}
defaultOpened={admin_items.map(x => x.link).includes(router.pathname)}
>
{admin_items.map(({ icon, text, link }) => (
<Link href={link} key={text} passHref>
<NavLink
component='a'
label={text}
icon={icon}
active={router.pathname === link}
variant='light'
/>
</Link>
))}
</NavLink>
)}
<Navbar pt='sm' hiddenBreakpoint='sm' hidden={!opened} width={{ sm: 200, lg: 230 }}>
<Navbar.Section grow component={ScrollArea}>
{items
.filter((x) => (x.if ? x.if(user, props) : true))
.map(({ icon, text, link, children }) =>
children ? (
<NavLink
key={text}
label={text}
icon={icon}
defaultOpened={children.map((x) => x.link).includes(router.pathname)}
>
{children
.filter((x) => (x.if ? x.if(user, props) : true))
.map(({ icon, text, link }) => (
<NavLink
key={text}
label={text}
icon={icon}
active={router.pathname === link}
variant='light'
component={Link}
href={link}
/>
))}
</NavLink>
) : (
<NavLink
key={text}
label={text}
icon={icon}
active={router.pathname === link}
variant='light'
component={Link}
href={link}
/>
)
)}
</Navbar.Section>
<Navbar.Section>
{external_links.length
? external_links.map(({ label, link }, i: number) => (
<NavLink
key={i}
label={label}
target='_blank'
variant='light'
icon={<IconExternalLink size={18} />}
component={Link}
href={link}
/>
))
: null}
</Navbar.Section>
{version.isSuccess ? (
<Navbar.Section>
<Tooltip
label={
version.data.update
? `There is a new ${version.data.updateToType} version: ${
version.data.versions[version.data.updateToType]
}`
: `You are running the latest ${version.data.isUpstream ? 'upstream' : 'stable'} version`
}
>
<Badge
m='md'
radius='md'
size='lg'
variant='dot'
color={version.data.update ? 'red' : 'primary'}
>
{version.data.versions.current}
</Badge>
</Tooltip>
</Navbar.Section>
) : null}
</Navbar>
}
header={
@@ -253,64 +345,118 @@ export default function Layout({ children, user, title }) {
</MediaQuery>
<Title ml='sm'>{title}</Title>
<Box sx={{ marginLeft: 'auto', marginRight: 0 }}>
<Popover
position='bottom-end'
opened={open}
onClose={() => setOpen(false)}
<Menu
styles={{
item: {
'@media (max-width: 768px)': {
padding: '1rem',
width: '80vw',
},
},
}}
>
<Popover.Target>
<Menu.Target>
<Button
leftIcon={avatar ? <Image src={avatar} height={32} radius='md' /> : <SettingsIcon />}
onClick={() => setOpen((o) => !o)}
sx={t => ({
backgroundColor: '#00000000',
'&:hover': {
backgroundColor: t.other.hover,
},
})}
leftIcon={
avatar ? <Image src={avatar} height={32} radius='md' /> : <IconUserCog size='1rem' />
}
variant='subtle'
color='gray'
compact
size='xl'
p='sm'
>
{user.username}
</Button>
</Popover.Target>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>
{user.username} ({user.id}){' '}
{user.administrator && user.username !== 'administrator' ? '(Administrator)' : ''}
</Menu.Label>
<Menu.Item component={Link} icon={<IconFiles size='1rem' />} href='/dashboard/files'>
Files
</Menu.Item>
<Menu.Item
component={Link}
icon={<IconFileUpload size='1rem' />}
href='/dashboard/upload/file'
>
Upload File
</Menu.Item>
<Menu.Item component={Link} icon={<IconLink size='1rem' />} href='/dashboard/urls'>
Shorten URL
</Menu.Item>
<Popover.Dropdown p={4}>
<Stack spacing={2}>
<Text sx={{
color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],
fontWeight: 500,
fontSize: theme.fontSizes.sm,
padding: `${theme.spacing.xs / 2}px ${theme.spacing.sm}px`,
cursor: 'default',
<Menu.Label>Settings</Menu.Label>
<Menu.Item component={Link} icon={<IconSettings size='1rem' />} href='/dashboard/manage'>
Manage Account
</Menu.Item>
<Menu.Item
icon={<IconClipboardCopy size='1rem' />}
onClick={() => {
openCopyToken();
}}
>
{user.username}
</Text>
<MenuItemLink icon={<SettingsIcon />} href='/dashboard/manage'>Manage Account</MenuItemLink>
<MenuItem icon={<CopyIcon />} onClick={() => {setOpen(false);openCopyToken();}}>Copy Token</MenuItem>
<MenuItem icon={<DeleteIcon />} onClick={() => {setOpen(false);openResetToken();}} color='red'>Reset Token</MenuItem>
<MenuItemLink icon={<LogoutIcon />} href='/auth/logout' color='red'>Logout</MenuItemLink>
<Divider
variant='solid'
my={theme.spacing.xs / 2}
sx={theme => ({
width: '110%',
borderTopColor: theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.colors.gray[2],
margin: `${theme.spacing.xs / 2}px -4px`,
})}
>
Copy Token
</Menu.Item>
<Menu.Item icon={<IconLogout size='1rem' />} component={Link} href='/auth/logout'>
Logout
</Menu.Item>
<Menu.Label>Danger</Menu.Label>
<Menu.Item
icon={<IconBackspace size='1rem' />}
onClick={() => {
openResetToken();
}}
color='red'
>
Reset Token
</Menu.Item>
<Menu.Divider />
<>
{oauth_providers.filter((x) =>
user.oauth?.map(({ provider }) => provider.toLowerCase()).includes(x.name.toLowerCase())
).length ? (
<Menu.Label>Connected Accounts</Menu.Label>
) : null}
{oauth_providers
.filter((x) =>
user.oauth
?.map(({ provider }) => provider.toLowerCase())
.includes(x.name.toLowerCase())
)
.map(({ name, Icon }, i) => (
<>
<Menu.Item
closeMenuOnClick={false}
key={i}
icon={<Icon size={18} colorScheme={theme.colorScheme} />}
>
Logged in with {capitalize(name)}
</Menu.Item>
</>
))}
{oauth_providers.filter((x) =>
user.oauth?.map(({ provider }) => provider.toLowerCase()).includes(x.name.toLowerCase())
).length ? (
<Menu.Divider />
) : null}
</>
<Menu.Item closeMenuOnClick={false} icon={<IconBrush size='1rem' />}>
<Select
size={useMediaQuery('(max-width: 768px)') ? 'md' : 'xs'}
data={Object.keys(themes).map((t) => ({
value: t,
label: friendlyThemeName[t],
}))}
value={systemTheme}
onChange={handleUpdateTheme}
/>
<MenuItem icon={<PencilIcon />}>
<Select
size='xs'
data={Object.keys(themes).map(t => ({ value: t, label: friendlyThemeName[t] }))}
value={systemTheme}
onChange={handleUpdateTheme}
/>
</MenuItem>
</Stack>
</Popover.Dropdown>
</Popover>
</Menu.Item>
</Menu.Dropdown>
</Menu>
</Box>
</div>
</Header>
@@ -319,13 +465,19 @@ export default function Layout({ children, user, title }) {
<Paper
withBorder
p='md'
mr='md'
mb='md'
shadow='xs'
sx={t => ({
borderColor: t.colorScheme === 'dark' ? t.colors.dark[5] : t.colors.dark[0],
sx={(theme) => ({
'&[data-with-border]': {
border: `${rem(1)} solid ${
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[0]
}`,
},
})}
>
{children}
</Paper>
</AppShell>
);
}
}

View File

@@ -1,3 +0,0 @@
import { NextLink as Link } from '@mantine/next';
export default Link;

View File

@@ -1,5 +1,9 @@
import { Text } from '@mantine/core';
export default function MutedText({ children, ...props }) {
return <Text color='dimmed' size='xl' {...props}>{children}</Text>;
}
return (
<Text color='dimmed' size='xl' {...props}>
{children}
</Text>
);
}

View File

@@ -1,18 +1,13 @@
// https://mantine.dev/core/password-input/
import { Box, PasswordInput, Popover, Progress, Text } from '@mantine/core';
import { IconCheck, IconCross } from '@tabler/icons-react';
import { useState } from 'react';
import { PasswordInput, Progress, Text, Popover, Box } from '@mantine/core';
import { CheckIcon, CrossIcon } from './icons';
function PasswordRequirement({ meets, label }: { meets: boolean; label: string }) {
return (
<Text
color={meets ? 'teal' : 'red'}
sx={{ display: 'flex', alignItems: 'center' }}
mt='sm'
size='sm'
>
{meets ? <CheckIcon /> : <CrossIcon />} <Box ml='md'>{label}</Box>
<Text color={meets ? 'teal' : 'red'} sx={{ display: 'flex', alignItems: 'center' }} mt='sm' size='sm'>
{meets ? <IconCheck size='1rem' /> : <IconCross size='1rem' />} <Box ml='md'>{label}</Box>
</Text>
);
}
@@ -49,16 +44,18 @@ export default function PasswordStrength({ value, setValue, setStrength, ...prop
return (
<Popover
opened={popoverOpened}
position='top'
position='bottom'
width='target'
withArrow
trapFocus={false}
styles={{
dropdown: {
zIndex: 999999,
},
}}
>
<Popover.Target>
<div
onFocusCapture={() => setPopoverOpened(true)}
onBlurCapture={() => setPopoverOpened(false)}
>
<div onFocusCapture={() => setPopoverOpened(true)} onBlurCapture={() => setPopoverOpened(false)}>
<PasswordInput
label='Password'
description='A strong password should include letters in lower and uppercase, at least 1 number, at least 1 special symbol'
@@ -68,7 +65,7 @@ export default function PasswordStrength({ value, setValue, setStrength, ...prop
/>
</div>
</Popover.Target>
<Popover.Dropdown sx={{ }}>
<Popover.Dropdown>
<Progress color={color} value={strength} size={7} mb='md' />
<PasswordRequirement label='Includes at least 8 characters' meets={value.length > 7} />
{checks}

View File

@@ -7,18 +7,16 @@ export function SmallTable({ rows, columns }) {
<Table highlightOnHover>
<thead>
<tr>
{columns.map(col => (
{columns.map((col) => (
<th key={randomId()}>{col.name}</th>
))}
</tr>
</thead>
<tbody>
{rows.map(row => (
{rows.map((row) => (
<tr key={randomId()}>
{columns.map(col => (
<td key={randomId()}>
{col.format ? col.format(row[col.id]) : row[col.id]}
</td>
{columns.map((col) => (
<td key={randomId()}>{col.format ? col.format(row[col.id]) : row[col.id]}</td>
))}
</tr>
))}
@@ -26,4 +24,4 @@ export function SmallTable({ rows, columns }) {
</Table>
</Box>
);
}
}

View File

@@ -0,0 +1,71 @@
import { Card, createStyles, Group, Text } from '@mantine/core';
import { IconArrowDownRight, IconArrowUpRight } from '@tabler/icons-react';
const useStyles = createStyles((theme) => ({
root: {
padding: `calc(${theme.spacing.xl} * 1.5)`,
},
value: {
fontSize: 24,
fontWeight: 700,
lineHeight: 1,
},
diff: {
lineHeight: 1,
display: 'flex',
alignItems: 'center',
},
icon: {
color: theme.colorScheme === 'dark' ? theme.colors.dark[3] : theme.colors.gray[4],
},
title: {
fontWeight: 700,
textTransform: 'uppercase',
},
}));
interface StatsGridProps {
stat: {
title: string;
icon: React.ReactNode;
value: string;
desc: string;
diff?: number;
};
}
export default function StatCard({ stat }: StatsGridProps) {
const { classes } = useStyles();
if (stat.diff) stat.diff = Math.round(stat.diff);
return (
<Card p='md' radius='md' key={stat.title}>
<Group position='apart'>
<Text size='xs' color='dimmed' className={classes.title}>
{stat.title}
</Text>
{stat.icon}
</Group>
<Group align='flex-end' spacing='xs' mt='md'>
<Text className={classes.value}>{stat.value}</Text>
{typeof stat.diff == 'number' && (
<>
<Text color={stat.diff >= 0 ? 'teal' : 'red'} size='sm' weight={500} className={classes.diff}>
<span>{stat.diff === Infinity ? '∞' : stat.diff}%</span>
{stat.diff >= 0 ? <IconArrowUpRight size={16} /> : <IconArrowDownRight size={16} />}
</Text>
</>
)}
</Group>
<Text size='xs' color='dimmed' mt='sm'>
{stat.desc}
</Text>
</Card>
);
}

View File

@@ -12,14 +12,20 @@ import matcha_dark_azul from 'lib/themes/matcha_dark_azul';
import nord from 'lib/themes/nord';
import qogir_dark from 'lib/themes/qogir_dark';
import { MantineProvider, MantineThemeOverride } from '@mantine/core';
import { createEmotionCache, MantineProvider, MantineThemeOverride } from '@mantine/core';
import { useColorScheme } from '@mantine/hooks';
import { ModalsProvider } from '@mantine/modals';
import { NotificationsProvider } from '@mantine/notifications';
import { useStoreSelector } from 'lib/redux/store';
import { Notifications } from '@mantine/notifications';
import { SpotlightProvider } from '@mantine/spotlight';
import { userSelector } from 'lib/recoil/user';
import { useRecoilValue } from 'recoil';
import { createSpotlightActions } from 'lib/spotlight';
import { useRouter } from 'next/router';
import { IconSearch } from '@tabler/icons-react';
export const themes = {
system: (colorScheme: 'dark' | 'light') => colorScheme === 'dark' ? dark_blue : light_blue,
system: (colorScheme: 'dark' | 'light') => (colorScheme === 'dark' ? dark_blue : light_blue),
dark_blue,
light_blue,
dark,
@@ -33,22 +39,25 @@ export const themes = {
};
export const friendlyThemeName = {
'system': 'System Theme',
'dark_blue': 'Dark Blue',
'light_blue': 'Light Blue',
'dark': 'Very Dark',
'ayu_dark': 'Ayu Dark',
'ayu_mirage': 'Ayu Mirage',
'ayu_light': 'Ayu Light',
'nord': 'Nord',
'dracula': 'Dracula',
'matcha_dark_azul': 'Matcha Dark Azul',
'qogir_dark': 'Qogir Dark',
system: 'System Theme',
dark_blue: 'Dark Blue',
light_blue: 'Light Blue',
dark: 'Very Dark',
ayu_dark: 'Ayu Dark',
ayu_mirage: 'Ayu Mirage',
ayu_light: 'Ayu Light',
nord: 'Nord',
dracula: 'Dracula',
matcha_dark_azul: 'Matcha Dark Azul',
qogir_dark: 'Qogir Dark',
};
const cache = createEmotionCache({ key: 'zipline' });
export default function ZiplineTheming({ Component, pageProps, ...props }) {
const user = useStoreSelector(state => state.user);
const user = useRecoilValue(userSelector);
const colorScheme = useColorScheme();
const router = useRouter();
let theme: MantineThemeOverride;
@@ -64,18 +73,24 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
<MantineProvider
withGlobalStyles
withNormalizeCSS
emotionCache={cache}
theme={{
...theme,
fontFamily: 'Ubuntu, sans-serif',
fontFamilyMonospace: 'Ubuntu Mono, monospace',
headings: {
fontFamily: 'Ubuntu, sans-serif',
},
components: {
AppShell: {
styles: t => ({
root: {
styles: (t) => ({
main: {
backgroundColor: t.other.AppShell_backgroundColor,
},
}),
},
NavLink: {
styles: t => ({
styles: (t) => ({
icon: {
paddingLeft: t.spacing.sm,
},
@@ -83,24 +98,61 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
},
Modal: {
defaultProps: {
closeButtonProps: { size: 'lg' },
centered: true,
overlayBlur: 3,
overlayColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white',
transitionProps: {
exitDuration: 100,
},
overlayProps: {
blur: 6,
color: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white',
},
},
},
Popover: {
defaultProps: {
transition: 'pop',
shadow: 'lg',
},
},
LoadingOverlay: {
defaultProps: {
overlayColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white',
overlayOpacity: 0.3,
},
},
Loader: {
defaultProps: {
variant: 'dots',
},
},
Card: {
styles: (t) => ({
root: {
backgroundColor: t.colorScheme === 'dark' ? t.colors.dark[6] : t.colors.gray[0],
},
}),
},
Image: {
styles: (t) => ({
placeholder: {
backgroundColor: t.colorScheme === 'dark' ? t.colors.dark[6] : t.colors.gray[0],
},
}),
},
},
}}
>
<ModalsProvider>
<NotificationsProvider>
<SpotlightProvider
searchIcon={<IconSearch size='1rem' />}
shortcut={['mod + k', '/']}
actions={createSpotlightActions(router)}
>
<Notifications position='top-center' style={{ marginTop: -10 }} />
{props.children ? props.children : <Component {...pageProps} />}
</NotificationsProvider>
</SpotlightProvider>
</ModalsProvider>
</MantineProvider>
);
}
}

View File

@@ -1,45 +1,177 @@
import { Group, Image, Text } from '@mantine/core';
import { Prism } from '@mantine/prism';
import {
Alert,
Box,
Button,
Card,
Center,
Group,
Image,
LoadingOverlay,
Text,
UnstyledButton,
} from '@mantine/core';
import {
IconFile,
IconFileAlert,
IconFileText,
IconFileUnknown,
IconHeadphones,
IconPhotoCancel,
IconPlayerPlay,
} from '@tabler/icons-react';
import exts from 'lib/exts';
import { useEffect, useState } from 'react';
import { AudioIcon, FileIcon, PlayIcon } from './icons';
import KaTeX from './render/KaTeX';
import Markdown from './render/Markdown';
import PrismCode from './render/PrismCode';
function Placeholder({ text, Icon, ...props }) {
function PlaceholderContent({ text, Icon }) {
return (
<Image height={200} withPlaceholder placeholder={
<Group>
<Icon size={48} />
<Text size='md'>{text}</Text>
</Group>
} {...props} />
<Group sx={(t) => ({ color: t.colors.dark[2] })}>
<Icon size={48} />
<Text size='md'>{text}</Text>
</Group>
);
}
export default function Type({ file, popup = false, ...props }){
const type = (file.type || file.mimetype).split('/')[0];
const name = (file.name || file.file);
function Placeholder({ text, Icon, ...props }) {
if (props.onClick)
return (
<UnstyledButton sx={{ height: 200 }} {...props}>
<Center sx={{ height: 200 }}>
<PlaceholderContent text={text} Icon={Icon} />
</Center>
</UnstyledButton>
);
return (
<Box sx={{ height: 320 }} {...props}>
<Center sx={{ height: 320 }}>
<PlaceholderContent text={text} Icon={Icon} />
</Center>
</Box>
);
}
export default function Type({ file, popup = false, disableMediaPreview, ...props }) {
const type =
(file.type ?? file.mimetype) === ''
? file.name.split('.').pop()
: (file.type ?? file.mimetype).split('/')[0];
const media = /^(video|audio|image|text)/.test(type);
const [text, setText] = useState('');
const shouldRenderMarkdown = file.name.endsWith('.md');
const shouldRenderTex = file.name.endsWith('.tex');
const shouldRenderCode: boolean = Object.keys(exts).includes(file.name.split('.').pop());
if (type === 'text') {
const [loading, setLoading] = useState(type === 'text' && popup);
if ((type === 'text' || shouldRenderMarkdown || shouldRenderTex || shouldRenderCode) && popup) {
useEffect(() => {
(async () => {
const res = await fetch('/r/' + name);
const res = await fetch('/r/' + file.name);
const text = await res.text();
setText(text);
setLoading(false);
})();
}, []);
}
return popup ? {
'video': <video width='100%' autoPlay controls {...props} />,
'image': <Image {...props} />,
'audio': <audio autoPlay controls {...props} style={{ width: '100%' }}/>,
'text': <Prism withLineNumbers language={name.split('.').pop()} {...props} style={{}} sx={{}}>{text}</Prism>,
}[type] : {
'video': <Placeholder Icon={PlayIcon} text={`Click to view video (${name})`} {...props} />,
'image': <Image {...props} />,
'audio': <Placeholder Icon={AudioIcon} text={`Click to view audio (${name})`} {...props}/>,
'text': <Placeholder Icon={FileIcon} text={`Click to view text file (${name})`} {...props}/>,
}[type];
};
const renderAlert = () => {
return (
<Alert color='blue' variant='outline' sx={{ width: '100%' }}>
You are{props.overrideRender ? ' not ' : ' '}viewing a rendered version of the file
<Button
mx='md'
onClick={() => props.setOverrideRender(!props.overrideRender)}
compact
variant='light'
>
View {props.overrideRender ? 'rendered' : 'raw'}
</Button>
</Alert>
);
};
if ((shouldRenderMarkdown || shouldRenderTex || shouldRenderCode) && !props.overrideRender && popup)
return (
<>
{renderAlert()}
<Card p='md' my='sm'>
{shouldRenderMarkdown && <Markdown code={text} />}
{shouldRenderTex && <KaTeX code={text} />}
{shouldRenderCode && !(shouldRenderTex || shouldRenderMarkdown) && (
<PrismCode code={text} ext={type} />
)}
</Card>
</>
);
if (media && disableMediaPreview) {
return <Placeholder Icon={IconFile} text={`Click to view file (${file.name})`} {...props} />;
}
if (file.password) {
return (
<Placeholder
Icon={IconFileAlert}
text={`This file is password protected. Click to view file (${file.name})`}
onClick={() => window.open(file.url)}
{...props}
/>
);
}
return popup ? (
media ? (
{
video: <video width='100%' autoPlay muted controls {...props} />,
image: (
<Image
styles={{
imageWrapper: {
position: 'inherit',
},
}}
placeholder={<PlaceholderContent Icon={IconPhotoCancel} text={'Image failed to load...'} />}
{...props}
/>
),
audio: <audio autoPlay muted controls {...props} style={{ width: '100%' }} />,
text: (
<>
{loading ? (
<LoadingOverlay visible={loading} />
) : (
<>
{(shouldRenderMarkdown || shouldRenderTex) && renderAlert()}
<PrismCode code={text} ext={file.name.split('.').pop()} {...props} />
</>
)}
</>
),
}[type]
) : (
<Text>Can&apos;t preview {file.type || file.mimetype}</Text>
)
) : media ? (
{
video: <Placeholder Icon={IconPlayerPlay} text={`Click to view video (${file.name})`} {...props} />,
image: (
<Image
placeholder={<PlaceholderContent Icon={IconPhotoCancel} text={'Image failed to load...'} />}
height={320}
fit='contain'
{...props}
/>
),
audio: <Placeholder Icon={IconHeadphones} text={`Click to view audio (${file.name})`} {...props} />,
text: <Placeholder Icon={IconFileText} text={`Click to view text file (${file.name})`} {...props} />,
}[type]
) : (
<Placeholder Icon={IconFileUnknown} text={`Click to view file (${file.name})`} {...props} />
);
}

View File

@@ -1,24 +1,26 @@
import React from 'react';
import { Box, Group, SimpleGrid, Text } from '@mantine/core';
import { Dropzone as MantineDropzone } from '@mantine/dropzone';
import { Group, Text, useMantineTheme } from '@mantine/core';
import { ImageIcon } from 'components/icons';
import { IconPhoto } from '@tabler/icons-react';
export default function Dropzone({ loading, onDrop, children }) {
const theme = useMantineTheme();
return (
<MantineDropzone onDrop={onDrop}>
<Group position='center' spacing='xl' style={{ minHeight: 440 }}>
<ImageIcon size={80} />
<SimpleGrid
cols={2}
breakpoints={[
{ maxWidth: 'md', cols: 1 },
{ maxWidth: 'xs', cols: 1 },
]}
>
<MantineDropzone loading={loading} onDrop={onDrop} styles={{ inner: { pointerEvents: 'none' } }}>
<Group position='center' spacing='xl' style={{ minHeight: 440 }}>
<IconPhoto size={80} />
<Text size='xl' inline>
Drag images here or click to select files
</Text>
</Group>
<div style={{ pointerEvents: 'all' }}>
{children}
</div>
</MantineDropzone>
<Text size='xl' inline>
Drag files here or click to select files
</Text>
</Group>
</MantineDropzone>
<Box>{children}</Box>
</SimpleGrid>
);
}
}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Table, Tooltip, Badge, useMantineTheme } from '@mantine/core';
import { ActionIcon, Box, Card, Group, HoverCard, Table, useMantineTheme } from '@mantine/core';
import { IconX } from '@tabler/icons-react';
import Type from 'components/Type';
export function FilePreview({ file }: { file: File }) {
@@ -11,19 +11,41 @@ export function FilePreview({ file }: { file: File }) {
style={{ maxWidth: '10vw', maxHeight: '100vh' }}
src={URL.createObjectURL(file)}
alt={file.name}
disableMediaPreview={false}
popup
/>
);
}
export default function FileDropzone({ file }: { file: File }) {
export default function FileDropzone({ file, onRemove }: { file: File; onRemove: () => void }) {
const theme = useMantineTheme();
return (
<Tooltip
position='top'
label={
<div style={{ display: 'flex', alignItems: 'center' }}>
<HoverCard shadow='md'>
<HoverCard.Target>
<Card shadow='sm' radius='sm' p='sm'>
<Group position='center' spacing='xl'>
{file.name}
</Group>
</Card>
</HoverCard.Target>
<HoverCard.Dropdown>
<Box
sx={{
position: 'absolute',
top: 0,
right: 0,
zIndex: 1,
color: theme.colorScheme === 'dark' ? 'white' : 'white',
}}
m='xs'
>
<ActionIcon onClick={onRemove} size='sm' color='red' variant='filled'>
<IconX />
</ActionIcon>
</Box>
<Group grow>
<FilePreview file={file} />
<Table sx={{ color: theme.colorScheme === 'dark' ? 'white' : 'white' }} ml='md'>
@@ -42,12 +64,8 @@ export default function FileDropzone({ file }: { file: File }) {
</tr>
</tbody>
</Table>
</div>
}
>
<Badge size='lg'>
{file.name}
</Badge>
</Tooltip>
</Group>
</HoverCard.Dropdown>
</HoverCard>
);
}
}

View File

@@ -1,5 +0,0 @@
import { Activity } from 'react-feather';
export default function ActivityIcon({ ...props }) {
return <Activity size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { Disc } from 'react-feather';
export default function AudioIcon({ ...props }) {
return <Disc size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { Calendar } from 'react-feather';
export default function CalendarIcon({ ...props }) {
return <Calendar size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { Check } from 'react-feather';
export default function CheckIcon({ ...props }) {
return <Check size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { Clock } from 'react-feather';
export default function ClockIcon({ ...props }) {
return <Clock size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { Copy } from 'react-feather';
export default function CopyIcon({ ...props }) {
return <Copy size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { X } from 'react-feather';
export default function CrossIcon({ ...props }) {
return <X size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { Delete } from 'react-feather';
export default function DeleteIcon({ ...props }) {
return <Delete size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { Download } from 'react-feather';
export default function DownloadIcon({ ...props }) {
return <Download size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { LogIn } from 'react-feather';
export default function EnterIcon({ ...props }) {
return <LogIn size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { File } from 'react-feather';
export default function FileIcon({ ...props }) {
return <File size={15} {...props} />;
}

View File

@@ -0,0 +1,15 @@
// https://github.com/flameshot-org/flameshot/blob/master/data/img/app/flameshot.svg
import Image from 'next/image';
export default function FlameshotIcon({ ...props }) {
return (
<Image
alt='flameshot'
src='https://raw.githubusercontent.com/flameshot-org/flameshot/master/data/img/app/flameshot.svg'
width={24}
height={24}
{...props}
/>
);
}

View File

@@ -1,5 +0,0 @@
import { Hash } from 'react-feather';
export default function HashIcon({ ...props }) {
return <Hash size={15} {...props} />;
}

View File

@@ -1,5 +0,0 @@
import { Home } from 'react-feather';
export default function HomeIcon({ ...props }) {
return <Home size={15} {...props} />;
}

Some files were not shown because too many files have changed in this diff Show More