mirror of
https://github.com/mollersuite/monofile.git
synced 2024-11-20 21:16:26 -08:00
this code SUUUUCKs but i dont CARE
This commit is contained in:
parent
55e10f5408
commit
b95a33e39d
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,4 +1,4 @@
|
|||
node_modules
|
||||
.env
|
||||
.data
|
||||
node_modules
|
||||
.env
|
||||
.data
|
||||
out
|
44
.vscode/tasks.json
vendored
44
.vscode/tasks.json
vendored
|
@ -1,23 +1,23 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "shell",
|
||||
"command":"tsc\nsass src/style:out/style\nrollup -c",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"label": "Build (Bot Server)"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command":"tsc\nsass src/style:out/style\nrollup -c\nnode ./out/server/index.js\ndel ./out/* -Recurse",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"label": "Build & Test"
|
||||
}
|
||||
]
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "shell",
|
||||
"command":"tsc\nsass src/style:out/style\nrollup -c",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"label": "Build (Bot Server)"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command":"tsc\nsass src/style:out/style\nrollup -c\nnode ./out/server/index.js\ndel ./out/* -Recurse",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"label": "Build & Test"
|
||||
}
|
||||
]
|
||||
}
|
46
LICENSE
46
LICENSE
|
@ -1,24 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
64
README.md
64
README.md
|
@ -1,33 +1,33 @@
|
|||
# monofile
|
||||
File sharing via Discord
|
||||
|
||||
<br>
|
||||
|
||||
## .env
|
||||
|
||||
```
|
||||
TOKEN=KILL-YOURSELF.NOW
|
||||
```
|
||||
|
||||
## versions & planned updates
|
||||
|
||||
- [X] 1.0.0 initial release
|
||||
- [X] 1.1.0 add file cloning endpoint
|
||||
- [X] 1.1.1 add file cloning webpage
|
||||
- [X] 1.1.2 fix file cloning with binary data
|
||||
- [X] 1.1.3 display current version on pages
|
||||
- [X] 1.1.4 serve /assets as static files & make /server endpoint
|
||||
- [X] 1.2.0 add file parameters section + custom ids
|
||||
- [X] 1.2.1 add file counter to main page
|
||||
- [X] 1.2.2 clean up this shitty code
|
||||
- [X] 1.2.3 bugfixes
|
||||
- [ ] 1.3.0 new ui; collections; accounts; utility endpoints; multi file uploads
|
||||
- [ ] 1.3.1 self-destructing files
|
||||
- [ ] 1.3.2 disable cloning of local ips
|
||||
- [ ] 1.4.0 admin panel
|
||||
- [ ] 2.0.0 rewrite using theUnfunny's code as a base/rewrite using monofile-core
|
||||
|
||||
also todo: monofile-core (written in eris)
|
||||
|
||||
## Disclaimer!
|
||||
# monofile
|
||||
File sharing via Discord
|
||||
|
||||
<br>
|
||||
|
||||
## .env
|
||||
|
||||
```
|
||||
TOKEN=KILL-YOURSELF.NOW
|
||||
```
|
||||
|
||||
## versions & planned updates
|
||||
|
||||
- [X] 1.0.0 initial release
|
||||
- [X] 1.1.0 add file cloning endpoint
|
||||
- [X] 1.1.1 add file cloning webpage
|
||||
- [X] 1.1.2 fix file cloning with binary data
|
||||
- [X] 1.1.3 display current version on pages
|
||||
- [X] 1.1.4 serve /assets as static files & make /server endpoint
|
||||
- [X] 1.2.0 add file parameters section + custom ids
|
||||
- [X] 1.2.1 add file counter to main page
|
||||
- [X] 1.2.2 clean up this shitty code
|
||||
- [X] 1.2.3 bugfixes
|
||||
- [ ] 1.3.0 new ui; collections; accounts; utility endpoints; multi file uploads
|
||||
- [ ] 1.3.1 self-destructing files
|
||||
- [ ] 1.3.2 disable cloning of local ips
|
||||
- [ ] 1.4.0 admin panel
|
||||
- [ ] 2.0.0 rewrite using theUnfunny's code as a base/rewrite using monofile-core
|
||||
|
||||
also todo: monofile-core (written in eris)
|
||||
|
||||
## Disclaimer!
|
||||
This project does some stuff that can be considered questionable. Discord may not like you uploading files this way, and it's a grey area in Discord's TOS. We take no responsibility if Discord locks your account for API abuse.
|
1
assets/icons/README.md
Normal file
1
assets/icons/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
Icons are part of Microsoft's Fluent icons
|
1
assets/icons/change_password.svg
Normal file
1
assets/icons/change_password.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.95 8.6a6.554 6.554 0 0 1 6.55-6.55c3.596 0 6.55 2.819 6.55 6.45a6.554 6.554 0 0 1-6.55 6.55c-.531 0-1.055-.076-1.552-.204A1.25 1.25 0 0 1 12.7 16.05h-1.75v1.75c0 .69-.56 1.25-1.25 1.25H7.95v1.25a1.75 1.75 0 0 1-1.75 1.75H3.7a1.75 1.75 0 0 1-1.75-1.75v-2.172c0-.73.29-1.429.806-1.944L8.99 9.948a.275.275 0 0 0 .07-.244A6.386 6.386 0 0 1 8.95 8.6Zm9.3-1.6a1.25 1.25 0 1 0-2.5 0 1.25 1.25 0 0 0 2.5 0Z" fill="#DDDDDD"/></svg>
|
After Width: | Height: | Size: 529 B |
1
assets/icons/delete_account.svg
Normal file
1
assets/icons/delete_account.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17.5 12a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Zm-5.478 2A6.47 6.47 0 0 0 11 17.5c0 1.644.61 3.145 1.617 4.29-.802.141-1.675.21-2.617.21-2.89 0-5.128-.656-6.691-2a3.75 3.75 0 0 1-1.305-2.843v-.907A2.25 2.25 0 0 1 4.254 14h7.768Zm3.071.966-.07.058-.057.07a.5.5 0 0 0 0 .568l.058.069 1.77 1.77-1.768 1.766-.057.07a.5.5 0 0 0 0 .568l.058.07.069.057a.5.5 0 0 0 .568 0l.07-.058 1.766-1.767 1.77 1.77.069.058a.5.5 0 0 0 .568 0l.07-.058.058-.07a.5.5 0 0 0 0-.568l-.058-.07-1.77-1.768 1.772-1.77.058-.07a.5.5 0 0 0 0-.568l-.058-.069-.069-.058a.5.5 0 0 0-.569 0l-.069.058-1.771 1.77-1.77-1.77-.07-.058a.5.5 0 0 0-.492-.043l-.076.043ZM10 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z" fill="#DDDDDD"/></svg>
|
After Width: | Height: | Size: 791 B |
1
assets/icons/logout.svg
Normal file
1
assets/icons/logout.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.25 2.75a1.5 1.5 0 0 0-1.5 1.5v15.5a1.5 1.5 0 0 0 1.5 1.5h5.94a6.5 6.5 0 0 1 7.06-10.012V4.25a1.5 1.5 0 0 0-1.5-1.5H6.25Zm2.25 10.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3Zm9 9.75a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11Zm3.5-5.5a.5.5 0 0 1-.5.5h-4.793l1.647 1.646a.5.5 0 0 1-.708.708l-2.5-2.5a.5.5 0 0 1 0-.708l2.5-2.5a.5.5 0 0 1 .708.708L15.707 17H20.5a.5.5 0 0 1 .5.5Z" fill="#DDDDDD"/></svg>
|
After Width: | Height: | Size: 494 B |
17
config.json
17
config.json
|
@ -1,7 +1,12 @@
|
|||
{
|
||||
"maxDiscordFiles": 20,
|
||||
"maxDiscordFileSize": 8388608,
|
||||
"targetGuild": "1024080490677936248",
|
||||
"targetChannel": "1024080525993971913",
|
||||
"requestTimeout":120000
|
||||
{
|
||||
"maxDiscordFiles": 20,
|
||||
"maxDiscordFileSize": 8388608,
|
||||
"targetGuild": "1024080490677936248",
|
||||
"targetChannel": "1024080525993971913",
|
||||
"requestTimeout":120000,
|
||||
|
||||
"accounts": {
|
||||
"registrationEnabled": true,
|
||||
"requiredForUpload": true
|
||||
}
|
||||
}
|
5684
package-lock.json
generated
5684
package-lock.json
generated
File diff suppressed because it is too large
Load diff
72
package.json
72
package.json
|
@ -1,35 +1,37 @@
|
|||
{
|
||||
"name": "monofile",
|
||||
"version": "1.3.0-pa",
|
||||
"description": "Discord-based file sharing",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node ./out/server/index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "nbitzz",
|
||||
"license": "Unlicense",
|
||||
"engines": {
|
||||
"node": ">=v18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/multer": "^1.4.7",
|
||||
"axios": "^0.27.2",
|
||||
"body-parser": "^1.20.0",
|
||||
"discord.js": "^14.7.1",
|
||||
"dotenv": "^16.0.2",
|
||||
"express": "^4.18.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"typescript": "^4.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"rollup": "^3.11.0",
|
||||
"rollup-plugin-svelte": "^7.1.0",
|
||||
"sass": "^1.57.1",
|
||||
"svelte": "^3.55.1"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "monofile",
|
||||
"version": "1.3.0-pa",
|
||||
"description": "Discord-based file sharing",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node ./out/server/index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "nbitzz",
|
||||
"license": "Unlicense",
|
||||
"engines": {
|
||||
"node": ">=v18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/multer": "^1.4.7",
|
||||
"axios": "^0.27.2",
|
||||
"body-parser": "^1.20.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"discord.js": "^14.7.1",
|
||||
"dotenv": "^16.0.2",
|
||||
"express": "^4.18.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"typescript": "^4.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"rollup": "^3.11.0",
|
||||
"rollup-plugin-svelte": "^7.1.0",
|
||||
"sass": "^1.57.1",
|
||||
"svelte": "^3.55.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>monofile</title>
|
||||
|
||||
<meta name="og:site_name" content="monofile $Version">
|
||||
<meta name="title" content="$CollectionName">
|
||||
<meta name="description" content="$CollectionId - $Managers manager(s), $Files file(s)">
|
||||
|
||||
<!-- downloads.css is good eenough for this -->
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/static/style/downloads.css"
|
||||
>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>monofile</title>
|
||||
|
||||
<meta name="og:site_name" content="monofile $Version">
|
||||
<meta name="title" content="$CollectionName">
|
||||
<meta name="description" content="$CollectionId - $Managers manager(s), $Files file(s)">
|
||||
|
||||
<!-- downloads.css is good eenough for this -->
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/static/style/downloads.css"
|
||||
>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,40 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>monofile</title>
|
||||
|
||||
$metaTags
|
||||
|
||||
<meta name="og:site_name" content="monofile $Version">
|
||||
<meta name="title" content="$FileName">
|
||||
<meta name="description" content="ID: $FileId">
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/static/style/downloads.css"
|
||||
>
|
||||
</head>
|
||||
<body>
|
||||
<div id="appContent">
|
||||
<div id="uploadWindow">
|
||||
<h1>
|
||||
$FileName
|
||||
</h1>
|
||||
<p style="color:#999999">
|
||||
file id <span class="number">$FileId</span>
|
||||
</p>
|
||||
|
||||
<button style="position:relative;width:100%;top:10px;">
|
||||
<a id="dlbtn" href="/file/$FileId" download="$FileName" style="position:absolute;left:0px;top:0px;height:100%;width:100%;"></a>
|
||||
download
|
||||
</button>
|
||||
|
||||
<div style="min-height:20px" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>monofile</title>
|
||||
|
||||
$metaTags
|
||||
|
||||
<meta name="og:site_name" content="monofile $Version">
|
||||
<meta name="title" content="$FileName">
|
||||
<meta name="description" content="ID: $FileId">
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/static/style/downloads.css"
|
||||
>
|
||||
</head>
|
||||
<body>
|
||||
<div id="appContent">
|
||||
<div id="uploadWindow">
|
||||
<h1>
|
||||
$FileName
|
||||
</h1>
|
||||
<p style="color:#999999">
|
||||
file id <span class="number">$FileId</span>
|
||||
</p>
|
||||
|
||||
<button style="position:relative;width:100%;top:10px;">
|
||||
<a id="dlbtn" href="/file/$FileId" download="$FileName" style="position:absolute;left:0px;top:0px;height:100%;width:100%;"></a>
|
||||
download
|
||||
</button>
|
||||
|
||||
<div style="min-height:20px" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,34 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/static/style/error.css"
|
||||
>
|
||||
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="/static/assets/monofile-circ.png"
|
||||
>
|
||||
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=0"
|
||||
>
|
||||
|
||||
<title>$code</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p class="error">
|
||||
<span class="code">$code</span>
|
||||
$text
|
||||
</p>
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/static/style/error.css"
|
||||
>
|
||||
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="/static/assets/monofile-circ.png"
|
||||
>
|
||||
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=0"
|
||||
>
|
||||
|
||||
<title>$code</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p class="error">
|
||||
<span class="code">$code</span>
|
||||
$text
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,31 +1,32 @@
|
|||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/static/style/app.css"
|
||||
>
|
||||
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="/static/assets/monofile-circ.png"
|
||||
>
|
||||
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=0"
|
||||
>
|
||||
|
||||
<script type="module" src="/static/js/index.js"></script>
|
||||
|
||||
<title>monofile</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/static/style/app.css"
|
||||
>
|
||||
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="/static/assets/monofile-circ.png"
|
||||
>
|
||||
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=0"
|
||||
>
|
||||
|
||||
<script type="module" src="/static/js/index.js"></script>
|
||||
|
||||
<title>monofile</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,29 +1,29 @@
|
|||
import svelte from 'rollup-plugin-svelte'
|
||||
import resolve from "@rollup/plugin-node-resolve"
|
||||
|
||||
export default [
|
||||
{
|
||||
input: "src/client/index.js",
|
||||
output: {
|
||||
file: 'out/client/index.js',
|
||||
format: 'esm',
|
||||
sourcemap:true
|
||||
},
|
||||
plugins: [
|
||||
resolve(),
|
||||
svelte({})
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "src/client/collection.js",
|
||||
output: {
|
||||
file: 'out/client/collection.js',
|
||||
format: 'esm',
|
||||
sourcemap:true
|
||||
},
|
||||
plugins: [
|
||||
resolve(),
|
||||
svelte({})
|
||||
]
|
||||
}
|
||||
import svelte from 'rollup-plugin-svelte'
|
||||
import resolve from "@rollup/plugin-node-resolve"
|
||||
|
||||
export default [
|
||||
{
|
||||
input: "src/client/index.js",
|
||||
output: {
|
||||
file: 'out/client/index.js',
|
||||
format: 'esm',
|
||||
sourcemap:true
|
||||
},
|
||||
plugins: [
|
||||
resolve(),
|
||||
svelte({})
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "src/client/collection.js",
|
||||
output: {
|
||||
file: 'out/client/collection.js',
|
||||
format: 'esm',
|
||||
sourcemap:true
|
||||
},
|
||||
plugins: [
|
||||
resolve(),
|
||||
svelte({})
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,5 +1,5 @@
|
|||
import App from "../svelte/Collections.svelte"
|
||||
|
||||
new App({
|
||||
target: document.body
|
||||
import App from "../svelte/Collections.svelte"
|
||||
|
||||
new App({
|
||||
target: document.body
|
||||
})
|
|
@ -1,5 +1,5 @@
|
|||
import App from "../svelte/App.svelte"
|
||||
|
||||
new App({
|
||||
target: document.body
|
||||
import App from "../svelte/App.svelte"
|
||||
|
||||
new App({
|
||||
target: document.body
|
||||
})
|
|
@ -1,165 +1,191 @@
|
|||
/*
|
||||
i really should split this up into different modules
|
||||
*/
|
||||
|
||||
import bodyParser from "body-parser"
|
||||
import multer, {memoryStorage} from "multer"
|
||||
import Discord, { IntentsBitField, Client } from "discord.js"
|
||||
import express from "express"
|
||||
import fs, { link } from "fs"
|
||||
import axios, { AxiosResponse } from "axios"
|
||||
import ServeError from "./lib/errors"
|
||||
|
||||
import Files from "./lib/files"
|
||||
require("dotenv").config()
|
||||
|
||||
const multerSetup = multer({storage:memoryStorage()})
|
||||
let pkg = require(`${process.cwd()}/package.json`)
|
||||
let app = express()
|
||||
let config = require(`${process.cwd()}/config.json`)
|
||||
|
||||
app.use("/static/assets",express.static("assets"))
|
||||
app.use("/static/style",express.static("out/style"))
|
||||
app.use("/static/js",express.static("out/client"))
|
||||
|
||||
app.use(bodyParser.text({limit:(config.maxDiscordFileSize*config.maxDiscordFiles)+1048576,type:["application/json","text/plain"]}))
|
||||
// funcs
|
||||
|
||||
// init data
|
||||
|
||||
if (!fs.existsSync(__dirname+"/../.data/")) fs.mkdirSync(__dirname+"/../.data/")
|
||||
|
||||
|
||||
|
||||
// discord
|
||||
|
||||
let client = new Client({intents:[
|
||||
IntentsBitField.Flags.GuildMessages,
|
||||
IntentsBitField.Flags.MessageContent
|
||||
],rest:{timeout:config.requestTimeout}})
|
||||
|
||||
let files = new Files(client,config)
|
||||
|
||||
// routes (could probably make these use routers)
|
||||
|
||||
// index, clone
|
||||
|
||||
app.get("/", function(req,res) {
|
||||
res.sendFile(process.cwd()+"/pages/index.html")
|
||||
})
|
||||
|
||||
// upload handlers
|
||||
|
||||
app.post("/upload",multerSetup.single('file'),async (req,res) => {
|
||||
if (req.file) {
|
||||
try {
|
||||
let prm = req.header("monofile-params")
|
||||
let params:{[key:string]:any} = {}
|
||||
if (prm) {
|
||||
params = JSON.parse(prm)
|
||||
}
|
||||
|
||||
files.uploadFile({uploadId:params.uploadId,name:req.file.originalname,mime:req.file.mimetype},req.file.buffer)
|
||||
.then((uID) => res.send(uID))
|
||||
.catch((stat) => {
|
||||
res.status(stat.status);
|
||||
res.send(`[err] ${stat.message}`)
|
||||
})
|
||||
} catch {
|
||||
res.status(400)
|
||||
res.send("[err] bad request")
|
||||
}
|
||||
} else {
|
||||
res.status(400)
|
||||
res.send("[err] bad request")
|
||||
}
|
||||
})
|
||||
|
||||
app.post("/clone",(req,res) => {
|
||||
try {
|
||||
let j = JSON.parse(req.body)
|
||||
if (!j.url) {
|
||||
res.status(400)
|
||||
res.send("[err] invalid url")
|
||||
}
|
||||
axios.get(j.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => {
|
||||
files.uploadFile({name:j.url.split("/")[req.body.split("/").length-1] || "generic",mime:data.headers["content-type"],uploadId:j.uploadId},Buffer.from(data.data))
|
||||
.then((uID) => res.send(uID))
|
||||
.catch((stat) => {
|
||||
res.status(stat.status);
|
||||
res.send(`[err] ${stat.message}`)
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
res.status(400)
|
||||
res.send(`[err] failed to fetch data`)
|
||||
})
|
||||
} catch {
|
||||
res.status(500)
|
||||
res.send("[err] an error occured")
|
||||
}
|
||||
})
|
||||
|
||||
// serve files & download page
|
||||
|
||||
app.get("/download/:fileId",(req,res) => {
|
||||
if (files.getFilePointer(req.params.fileId)) {
|
||||
let file = files.getFilePointer(req.params.fileId)
|
||||
|
||||
fs.readFile(process.cwd()+"/pages/download.html",(err,buf) => {
|
||||
if (err) {res.sendStatus(500);console.log(err);return}
|
||||
res.send(
|
||||
buf.toString()
|
||||
.replace(/\$FileId/g,req.params.fileId)
|
||||
.replace(/\$Version/g,pkg.version)
|
||||
.replace(/\$FileName/g,
|
||||
file.filename
|
||||
.replace(/\&/g,"&")
|
||||
.replace(/\</g,"<")
|
||||
.replace(/\>/g,">")
|
||||
)
|
||||
.replace(/\$metaTags/g,file.mime.startsWith("image/") ? `<meta name="og:image" content="https://${req.headers.host}/file/${req.params.fileId}" />` : "")
|
||||
)
|
||||
})
|
||||
} else {
|
||||
ServeError(res,404,"file not found")
|
||||
}
|
||||
})
|
||||
|
||||
let fgRQH = async (req:express.Request,res:express.Response) => {
|
||||
files.readFileStream(req.params.fileId).then(f => {
|
||||
res.setHeader("Content-Type",f.contentType)
|
||||
res.status(200)
|
||||
f.dataStream.pipe(res)
|
||||
}).catch((err) => {
|
||||
ServeError(res,err.status,err.message)
|
||||
})
|
||||
}
|
||||
|
||||
app.get("/server",(req,res) => {
|
||||
res.send(JSON.stringify({
|
||||
...config,
|
||||
version:pkg.version,
|
||||
files:Object.keys(files.files).length
|
||||
}))
|
||||
})
|
||||
|
||||
app.get("/file/:fileId",fgRQH)
|
||||
app.get("/:fileId",fgRQH)
|
||||
|
||||
/*
|
||||
routes should be in this order:
|
||||
|
||||
index
|
||||
api
|
||||
dl pages
|
||||
file serving
|
||||
*/
|
||||
|
||||
// listen on 3000 or MONOFILE_PORT
|
||||
|
||||
app.listen(process.env.MONOFILE_PORT || 3000,function() {
|
||||
console.log("Web OK!")
|
||||
})
|
||||
|
||||
import bodyParser from "body-parser"
|
||||
import multer, {memoryStorage} from "multer"
|
||||
import cookieParser from "cookie-parser";
|
||||
import Discord, { IntentsBitField, Client } from "discord.js"
|
||||
import express from "express"
|
||||
import fs, { link } from "fs"
|
||||
import axios, { AxiosResponse } from "axios"
|
||||
|
||||
import ServeError from "./lib/errors"
|
||||
import Files from "./lib/files"
|
||||
import * as auth from "./lib/auth"
|
||||
import * as Accounts from "./lib/accounts"
|
||||
|
||||
import { authRoutes } from "./routes/authRoutes";
|
||||
require("dotenv").config()
|
||||
|
||||
const multerSetup = multer({storage:memoryStorage()})
|
||||
let pkg = require(`${process.cwd()}/package.json`)
|
||||
let app = express()
|
||||
let config = require(`${process.cwd()}/config.json`)
|
||||
|
||||
app.use("/static/assets",express.static("assets"))
|
||||
app.use("/static/style",express.static("out/style"))
|
||||
app.use("/static/js",express.static("out/client"))
|
||||
|
||||
app.use(bodyParser.text({limit:(config.maxDiscordFileSize*config.maxDiscordFiles)+1048576,type:["application/json","text/plain"]}))
|
||||
app.use(cookieParser())
|
||||
|
||||
app.use("/auth",authRoutes)
|
||||
// funcs
|
||||
|
||||
// init data
|
||||
|
||||
if (!fs.existsSync(__dirname+"/../.data/")) fs.mkdirSync(__dirname+"/../.data/")
|
||||
|
||||
|
||||
|
||||
// discord
|
||||
|
||||
let client = new Client({intents:[
|
||||
IntentsBitField.Flags.GuildMessages,
|
||||
IntentsBitField.Flags.MessageContent
|
||||
],rest:{timeout:config.requestTimeout}})
|
||||
|
||||
let files = new Files(client,config)
|
||||
|
||||
// routes (could probably make these use routers)
|
||||
|
||||
// index, clone
|
||||
|
||||
app.get("/", function(req,res) {
|
||||
res.sendFile(process.cwd()+"/pages/index.html")
|
||||
})
|
||||
|
||||
// upload handlers
|
||||
|
||||
app.post("/upload",multerSetup.single('file'),async (req,res) => {
|
||||
if (req.file) {
|
||||
try {
|
||||
let prm = req.header("monofile-params")
|
||||
let params:{[key:string]:any} = {}
|
||||
if (prm) {
|
||||
params = JSON.parse(prm)
|
||||
}
|
||||
|
||||
files.uploadFile({
|
||||
owner: auth.validate(req.cookies.auth),
|
||||
|
||||
uploadId:params.uploadId,
|
||||
name:req.file.originalname,
|
||||
mime:req.file.mimetype
|
||||
},req.file.buffer)
|
||||
.then((uID) => res.send(uID))
|
||||
.catch((stat) => {
|
||||
res.status(stat.status);
|
||||
res.send(`[err] ${stat.message}`)
|
||||
})
|
||||
} catch {
|
||||
res.status(400)
|
||||
res.send("[err] bad request")
|
||||
}
|
||||
} else {
|
||||
res.status(400)
|
||||
res.send("[err] bad request")
|
||||
}
|
||||
})
|
||||
|
||||
app.post("/clone",(req,res) => {
|
||||
try {
|
||||
let j = JSON.parse(req.body)
|
||||
if (!j.url) {
|
||||
res.status(400)
|
||||
res.send("[err] invalid url")
|
||||
}
|
||||
axios.get(j.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => {
|
||||
|
||||
files.uploadFile({
|
||||
owner: auth.validate(req.cookies.auth),
|
||||
|
||||
name:j.url.split("/")[req.body.split("/").length-1] || "generic",
|
||||
mime:data.headers["content-type"],
|
||||
uploadId:j.uploadId
|
||||
},Buffer.from(data.data))
|
||||
.then((uID) => res.send(uID))
|
||||
.catch((stat) => {
|
||||
res.status(stat.status);
|
||||
res.send(`[err] ${stat.message}`)
|
||||
})
|
||||
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
res.status(400)
|
||||
res.send(`[err] failed to fetch data`)
|
||||
})
|
||||
} catch {
|
||||
res.status(500)
|
||||
res.send("[err] an error occured")
|
||||
}
|
||||
})
|
||||
|
||||
// serve files & download page
|
||||
|
||||
app.get("/download/:fileId",(req,res) => {
|
||||
if (files.getFilePointer(req.params.fileId)) {
|
||||
let file = files.getFilePointer(req.params.fileId)
|
||||
|
||||
fs.readFile(process.cwd()+"/pages/download.html",(err,buf) => {
|
||||
if (err) {res.sendStatus(500);console.log(err);return}
|
||||
res.send(
|
||||
buf.toString()
|
||||
.replace(/\$FileId/g,req.params.fileId)
|
||||
.replace(/\$Version/g,pkg.version)
|
||||
.replace(/\$FileName/g,
|
||||
file.filename
|
||||
.replace(/\&/g,"&")
|
||||
.replace(/\</g,"<")
|
||||
.replace(/\>/g,">")
|
||||
)
|
||||
.replace(/\$metaTags/g,
|
||||
file.mime.startsWith("image/")
|
||||
? `<meta name="og:image" content="https://${req.headers.host}/file/${req.params.fileId}" />`
|
||||
: (
|
||||
file.mime.startsWith("video/")
|
||||
? `<meta name="og:video:url" content="https://${req.headers.host}/file/${req.params.fileId}" />\n<meta name="og:video:type" content="${file.mime.replace(/\"/g,"")}">`
|
||||
: ""
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
} else {
|
||||
ServeError(res,404,"file not found")
|
||||
}
|
||||
})
|
||||
|
||||
let fgRQH = async (req:express.Request,res:express.Response) => {
|
||||
files.readFileStream(req.params.fileId).then(f => {
|
||||
res.setHeader("Content-Type",f.contentType)
|
||||
res.status(200)
|
||||
f.dataStream.pipe(res)
|
||||
}).catch((err) => {
|
||||
ServeError(res,err.status,err.message)
|
||||
})
|
||||
}
|
||||
|
||||
app.get("/server",(req,res) => {
|
||||
res.send(JSON.stringify({
|
||||
...config,
|
||||
version:pkg.version,
|
||||
files:Object.keys(files.files).length
|
||||
}))
|
||||
})
|
||||
|
||||
app.get("/file/:fileId",fgRQH)
|
||||
app.get("/:fileId",fgRQH)
|
||||
|
||||
/*
|
||||
routes should be in this order:
|
||||
|
||||
index
|
||||
api
|
||||
dl pages
|
||||
file serving
|
||||
*/
|
||||
|
||||
// listen on 3000 or MONOFILE_PORT
|
||||
|
||||
app.listen(process.env.MONOFILE_PORT || 3000,function() {
|
||||
console.log("Web OK!")
|
||||
})
|
||||
|
||||
client.login(process.env.TOKEN)
|
|
@ -1,131 +1,100 @@
|
|||
import crypto from "crypto"
|
||||
import * as auth from "./auth";
|
||||
import { readFile, writeFile } from "fs/promises"
|
||||
|
||||
// this is probably horrible
|
||||
// but i don't even care anymore
|
||||
|
||||
export let Accounts: Account[] = []
|
||||
|
||||
export interface Account {
|
||||
id : string
|
||||
username: string
|
||||
password: {
|
||||
hash: string
|
||||
salt: string
|
||||
}
|
||||
accounts: string[]
|
||||
admin : boolean
|
||||
}
|
||||
|
||||
export function create(username:string,pwd:string,admin:boolean=false) {
|
||||
let accId = crypto.randomBytes(12).toString("hex")
|
||||
|
||||
Accounts.push(
|
||||
{
|
||||
id: accId,
|
||||
username: username,
|
||||
password: password.hash(pwd),
|
||||
accounts: [],
|
||||
admin: admin
|
||||
}
|
||||
)
|
||||
|
||||
save()
|
||||
|
||||
return accId
|
||||
}
|
||||
|
||||
export function getFromUsername(username:string) {
|
||||
return Accounts.find(e => e.username == username)
|
||||
}
|
||||
|
||||
export function getFromId(id:string) {
|
||||
return Accounts.find(e => e.id == id)
|
||||
}
|
||||
|
||||
export function getFromToken(token:string) {
|
||||
let accId = auth.validate(token)
|
||||
if (!accId) return
|
||||
return getFromId(accId)
|
||||
}
|
||||
|
||||
export function deleteAccount(id:string) {
|
||||
Accounts.splice(Accounts.findIndex(e => e.id == id),1)
|
||||
save()
|
||||
}
|
||||
|
||||
export namespace password {
|
||||
export function hash(password:string,_salt?:string) {
|
||||
let salt = _salt || crypto.randomBytes(12).toString('base64')
|
||||
let hash = crypto.createHash('sha256').update(`${salt}${password}`).digest('hex')
|
||||
|
||||
return {
|
||||
salt:salt,
|
||||
hash:hash
|
||||
}
|
||||
}
|
||||
|
||||
export function set(id:string,password:string) {
|
||||
let acc = Accounts.find(e => e.id == id)
|
||||
if (!acc) return
|
||||
|
||||
acc.password = hash(password)
|
||||
save()
|
||||
}
|
||||
|
||||
export function check(id:string,password:string) {
|
||||
let acc = Accounts.find(e => e.id == id)
|
||||
if (!acc) return
|
||||
|
||||
return acc.password.hash == hash(password,acc.password.salt).hash
|
||||
}
|
||||
}
|
||||
|
||||
export namespace rbxaccounts {
|
||||
export function add(id:string,name:string) {
|
||||
let acc = getFromId(id)
|
||||
if (!acc) return
|
||||
|
||||
/* check for account that already has name */
|
||||
let idx = acc.accounts.findIndex(e=>e==name)
|
||||
if (idx > -1) return
|
||||
|
||||
acc.accounts = [...acc.accounts,name]
|
||||
save()
|
||||
return
|
||||
}
|
||||
|
||||
export function remove(id:string,name:string) {
|
||||
let acc = getFromId(id)
|
||||
if (!acc) return
|
||||
let idx = acc.accounts.findIndex(e=>e==name)
|
||||
if (idx < 0) return
|
||||
acc.accounts.splice(idx,1)
|
||||
save()
|
||||
return
|
||||
}
|
||||
|
||||
export function clear(id:string) {
|
||||
let acc = getFromId(id)
|
||||
if (!acc) return
|
||||
acc.accounts = []
|
||||
save()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export function save() {
|
||||
writeFile(`${process.cwd()}/.data/accounts.json`,JSON.stringify(Accounts))
|
||||
.catch((err) => console.error(err))
|
||||
}
|
||||
|
||||
readFile(`${process.cwd()}/.data/accounts.json`)
|
||||
.then((buf) => {
|
||||
Accounts = JSON.parse(buf.toString())
|
||||
}).catch(err => console.error(err))
|
||||
.finally(() => {
|
||||
if (!Accounts.find(e => e.admin)) {
|
||||
create("admin","admin",true)
|
||||
}
|
||||
import crypto from "crypto"
|
||||
import * as auth from "./auth";
|
||||
import { readFile, writeFile } from "fs/promises"
|
||||
|
||||
// this is probably horrible
|
||||
// but i don't even care anymore
|
||||
|
||||
export let Accounts: Account[] = []
|
||||
|
||||
export interface Account {
|
||||
id : string
|
||||
username : string
|
||||
password : {
|
||||
hash : string
|
||||
salt : string
|
||||
}
|
||||
files : string[]
|
||||
collections : string[]
|
||||
admin : boolean
|
||||
}
|
||||
|
||||
export function create(username:string,pwd:string,admin:boolean=false) {
|
||||
let accId = crypto.randomBytes(12).toString("hex")
|
||||
|
||||
Accounts.push(
|
||||
{
|
||||
id: accId,
|
||||
username: username,
|
||||
password: password.hash(pwd),
|
||||
files: [],
|
||||
collections: [],
|
||||
admin: admin
|
||||
}
|
||||
)
|
||||
|
||||
save()
|
||||
|
||||
return accId
|
||||
}
|
||||
|
||||
export function getFromUsername(username:string) {
|
||||
return Accounts.find(e => e.username == username)
|
||||
}
|
||||
|
||||
export function getFromId(id:string) {
|
||||
return Accounts.find(e => e.id == id)
|
||||
}
|
||||
|
||||
export function getFromToken(token:string) {
|
||||
let accId = auth.validate(token)
|
||||
if (!accId) return
|
||||
return getFromId(accId)
|
||||
}
|
||||
|
||||
export function deleteAccount(id:string) {
|
||||
Accounts.splice(Accounts.findIndex(e => e.id == id),1)
|
||||
save()
|
||||
}
|
||||
|
||||
export namespace password {
|
||||
export function hash(password:string,_salt?:string) {
|
||||
let salt = _salt || crypto.randomBytes(12).toString('base64')
|
||||
let hash = crypto.createHash('sha256').update(`${salt}${password}`).digest('hex')
|
||||
|
||||
return {
|
||||
salt:salt,
|
||||
hash:hash
|
||||
}
|
||||
}
|
||||
|
||||
export function set(id:string,password:string) {
|
||||
let acc = Accounts.find(e => e.id == id)
|
||||
if (!acc) return
|
||||
|
||||
acc.password = hash(password)
|
||||
save()
|
||||
}
|
||||
|
||||
export function check(id:string,password:string) {
|
||||
let acc = Accounts.find(e => e.id == id)
|
||||
if (!acc) return
|
||||
|
||||
return acc.password.hash == hash(password,acc.password.salt).hash
|
||||
}
|
||||
}
|
||||
|
||||
export function save() {
|
||||
writeFile(`${process.cwd()}/.data/accounts.json`,JSON.stringify(Accounts))
|
||||
.catch((err) => console.error(err))
|
||||
}
|
||||
|
||||
readFile(`${process.cwd()}/.data/accounts.json`)
|
||||
.then((buf) => {
|
||||
Accounts = JSON.parse(buf.toString())
|
||||
}).catch(err => console.error(err))
|
||||
.finally(() => {
|
||||
if (!Accounts.find(e => e.admin)) {
|
||||
create("admin","admin",true)
|
||||
}
|
||||
})
|
|
@ -1,58 +1,58 @@
|
|||
import crypto from "crypto"
|
||||
import { readFile, writeFile } from "fs/promises"
|
||||
export let AuthTokens: AuthToken[] = []
|
||||
export let AuthTokenTO:{[key:string]:NodeJS.Timeout} = {}
|
||||
|
||||
export interface AuthToken {
|
||||
account: string,
|
||||
token: string,
|
||||
expire: number
|
||||
}
|
||||
|
||||
export function create(id:string,expire:number=(24*60*60*1000)) {
|
||||
let token = {
|
||||
account:id,
|
||||
token:crypto.randomBytes(12).toString('hex'),
|
||||
expire:Date.now()+expire
|
||||
}
|
||||
|
||||
AuthTokens.push(token)
|
||||
tokenTimer(token)
|
||||
|
||||
save()
|
||||
|
||||
return token.token
|
||||
}
|
||||
|
||||
export function validate(token:string) {
|
||||
return AuthTokens.find(e => e.token == token && Date.now() < e.expire)?.account
|
||||
}
|
||||
|
||||
export function tokenTimer(token:AuthToken) {
|
||||
if (Date.now() >= token.expire) {
|
||||
invalidate(token.token)
|
||||
return
|
||||
}
|
||||
|
||||
AuthTokenTO[token.token] = setTimeout(() => invalidate(token.token),token.expire-Date.now())
|
||||
}
|
||||
|
||||
export function invalidate(token:string) {
|
||||
if (AuthTokenTO[token]) {
|
||||
clearTimeout(AuthTokenTO[token])
|
||||
}
|
||||
|
||||
AuthTokens.splice(AuthTokens.findIndex(e => e.token == token),1)
|
||||
save()
|
||||
}
|
||||
|
||||
export function save() {
|
||||
writeFile(`${process.cwd()}/.data/tokens.json`,JSON.stringify(AuthTokens))
|
||||
.catch((err) => console.error(err))
|
||||
}
|
||||
|
||||
readFile(`${process.cwd()}/.data/tokens.json`)
|
||||
.then((buf) => {
|
||||
AuthTokens = JSON.parse(buf.toString())
|
||||
AuthTokens.forEach(e => tokenTimer(e))
|
||||
import crypto from "crypto"
|
||||
import { readFile, writeFile } from "fs/promises"
|
||||
export let AuthTokens: AuthToken[] = []
|
||||
export let AuthTokenTO:{[key:string]:NodeJS.Timeout} = {}
|
||||
|
||||
export interface AuthToken {
|
||||
account: string,
|
||||
token: string,
|
||||
expire: number
|
||||
}
|
||||
|
||||
export function create(id:string,expire:number=(24*60*60*1000)) {
|
||||
let token = {
|
||||
account:id,
|
||||
token:crypto.randomBytes(12).toString('hex'),
|
||||
expire:Date.now()+expire
|
||||
}
|
||||
|
||||
AuthTokens.push(token)
|
||||
tokenTimer(token)
|
||||
|
||||
save()
|
||||
|
||||
return token.token
|
||||
}
|
||||
|
||||
export function validate(token:string) {
|
||||
return AuthTokens.find(e => e.token == token && Date.now() < e.expire)?.account
|
||||
}
|
||||
|
||||
export function tokenTimer(token:AuthToken) {
|
||||
if (Date.now() >= token.expire) {
|
||||
invalidate(token.token)
|
||||
return
|
||||
}
|
||||
|
||||
AuthTokenTO[token.token] = setTimeout(() => invalidate(token.token),token.expire-Date.now())
|
||||
}
|
||||
|
||||
export function invalidate(token:string) {
|
||||
if (AuthTokenTO[token]) {
|
||||
clearTimeout(AuthTokenTO[token])
|
||||
}
|
||||
|
||||
AuthTokens.splice(AuthTokens.findIndex(e => e.token == token),1)
|
||||
save()
|
||||
}
|
||||
|
||||
export function save() {
|
||||
writeFile(`${process.cwd()}/.data/tokens.json`,JSON.stringify(AuthTokens))
|
||||
.catch((err) => console.error(err))
|
||||
}
|
||||
|
||||
readFile(`${process.cwd()}/.data/tokens.json`)
|
||||
.then((buf) => {
|
||||
AuthTokens = JSON.parse(buf.toString())
|
||||
AuthTokens.forEach(e => tokenTimer(e))
|
||||
}).catch(err => console.error(err))
|
|
@ -1,35 +1,36 @@
|
|||
import { Response } from "express";
|
||||
import { readFile } from "fs/promises"
|
||||
|
||||
let errorPage:string
|
||||
|
||||
export default async function ServeError(
|
||||
res:Response,
|
||||
code:number,
|
||||
reason:string
|
||||
) {
|
||||
// fetch error page if not cached
|
||||
if (!errorPage) {
|
||||
errorPage =
|
||||
(
|
||||
await readFile(`${process.cwd()}/pages/error.html`)
|
||||
.catch((err) => console.error(err))
|
||||
|| "<pre>$code $text</pre>"
|
||||
)
|
||||
.toString()
|
||||
}
|
||||
|
||||
// serve error
|
||||
res.status(code)
|
||||
res.send(
|
||||
errorPage
|
||||
.replace(/\$code/g,code.toString())
|
||||
.replace(/\$text/g,reason)
|
||||
)
|
||||
}
|
||||
|
||||
export function Redirect(res:Response,url:string) {
|
||||
res.status(302)
|
||||
res.header("Location",url)
|
||||
res.send()
|
||||
import { Response } from "express";
|
||||
import { readFile } from "fs/promises"
|
||||
|
||||
let errorPage:string
|
||||
|
||||
export default async function ServeError(
|
||||
res:Response,
|
||||
code:number,
|
||||
reason:string
|
||||
) {
|
||||
// fetch error page if not cached
|
||||
if (!errorPage) {
|
||||
errorPage =
|
||||
(
|
||||
await readFile(`${process.cwd()}/pages/error.html`)
|
||||
.catch((err) => console.error(err))
|
||||
|| "<pre>$code $text</pre>"
|
||||
)
|
||||
.toString()
|
||||
}
|
||||
|
||||
// serve error
|
||||
res.statusMessage = reason
|
||||
res.status(code)
|
||||
res.send(
|
||||
errorPage
|
||||
.replace(/\$code/g,code.toString())
|
||||
.replace(/\$text/g,reason)
|
||||
)
|
||||
}
|
||||
|
||||
export function Redirect(res:Response,url:string) {
|
||||
res.status(302)
|
||||
res.header("Location",url)
|
||||
res.send()
|
||||
}
|
|
@ -1,256 +1,270 @@
|
|||
import axios from "axios";
|
||||
import Discord, { Client, TextBasedChannel } from "discord.js";
|
||||
import { readFile, writeFile } from "fs";
|
||||
import { Readable } from "node:stream"
|
||||
|
||||
export let id_check_regex = /[A-Za-z0-9_\-\.]+/
|
||||
export let alphanum = Array.from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
|
||||
|
||||
// bad solution but whatever
|
||||
|
||||
export function generateFileId() {
|
||||
let fid = ""
|
||||
for (let i = 0; i < 5; i++) {
|
||||
fid += alphanum[Math.floor(Math.random()*alphanum.length)]
|
||||
}
|
||||
return fid
|
||||
}
|
||||
|
||||
export interface FileUploadSettings {
|
||||
name?: string,
|
||||
mime: string,
|
||||
uploadId?: string
|
||||
}
|
||||
|
||||
export interface Configuration {
|
||||
maxDiscordFiles: number,
|
||||
maxDiscordFileSize: number,
|
||||
targetGuild: string,
|
||||
targetChannel: string,
|
||||
requestTimeout: number
|
||||
}
|
||||
|
||||
export interface FilePointer {
|
||||
filename:string,
|
||||
mime:string,
|
||||
messageids:string[]
|
||||
}
|
||||
|
||||
export interface StatusCodeError {
|
||||
status: number,
|
||||
message: string
|
||||
}
|
||||
|
||||
/* */
|
||||
|
||||
export default class Files {
|
||||
|
||||
config: Configuration
|
||||
client: Client
|
||||
files: {[key:string]:FilePointer} = {}
|
||||
uploadChannel?: TextBasedChannel
|
||||
|
||||
constructor(client: Client, config: Configuration) {
|
||||
|
||||
this.config = config;
|
||||
this.client = client;
|
||||
|
||||
client.on("ready",() => {
|
||||
console.log("Discord OK!")
|
||||
|
||||
client.guilds.fetch(config.targetGuild).then((g) => {
|
||||
g.channels.fetch(config.targetChannel).then((a) => {
|
||||
if (a?.isTextBased()) {
|
||||
this.uploadChannel = a
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
readFile(process.cwd()+"/.data/files.json",(err,buf) => {
|
||||
if (err) {console.log(err);return}
|
||||
this.files = JSON.parse(buf.toString() || "{}")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
uploadFile(settings:FileUploadSettings,fBuffer:Buffer):Promise<string|StatusCodeError> {
|
||||
return new Promise<string>(async (resolve,reject) => {
|
||||
if (!this.uploadChannel) {
|
||||
reject({status:503,message:"server is not ready - please try again later"})
|
||||
return
|
||||
}
|
||||
|
||||
if (!settings.name || !settings.mime) {
|
||||
reject({status:400,message:"missing name/mime"});
|
||||
return
|
||||
}
|
||||
|
||||
let uploadId = (settings.uploadId || generateFileId()).toString();
|
||||
|
||||
if ((uploadId.match(id_check_regex) || [])[0] != uploadId || uploadId.length > 30) {
|
||||
reject({status:400,message:"invalid id"});return
|
||||
}
|
||||
|
||||
if (this.files[uploadId]) {
|
||||
reject({status:400,message:"a file with this id already exists"});
|
||||
return
|
||||
}
|
||||
|
||||
if (settings.name.length > 128) {
|
||||
reject({status:400,message:"name too long"});
|
||||
return
|
||||
}
|
||||
|
||||
if (settings.mime.length > 128) {
|
||||
reject({status:400,message:"mime too long"});
|
||||
return
|
||||
}
|
||||
|
||||
// get buffer
|
||||
if (fBuffer.byteLength >= (this.config.maxDiscordFileSize*this.config.maxDiscordFiles)) {
|
||||
reject({status:400,message:"file too large"});
|
||||
return
|
||||
}
|
||||
|
||||
// generate buffers to upload
|
||||
let toUpload = []
|
||||
for (let i = 0; i < Math.ceil(fBuffer.byteLength/this.config.maxDiscordFileSize); i++) {
|
||||
toUpload.push(
|
||||
fBuffer.subarray(
|
||||
i*this.config.maxDiscordFileSize,
|
||||
Math.min(
|
||||
fBuffer.byteLength,
|
||||
(i+1)*this.config.maxDiscordFileSize
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// begin uploading
|
||||
let uploadTmplt:Discord.AttachmentBuilder[] = toUpload.map((e) => {
|
||||
return new Discord.AttachmentBuilder(e)
|
||||
.setName(Math.random().toString().slice(2))
|
||||
})
|
||||
let uploadGroups = []
|
||||
for (let i = 0; i < Math.ceil(uploadTmplt.length/10); i++) {
|
||||
uploadGroups.push(uploadTmplt.slice(i*10,((i+1)*10)))
|
||||
}
|
||||
|
||||
let msgIds = []
|
||||
|
||||
for (let i = 0; i < uploadGroups.length; i++) {
|
||||
|
||||
let ms = await this.uploadChannel.send({
|
||||
files:uploadGroups[i]
|
||||
}).catch((e) => {console.error(e)})
|
||||
|
||||
if (ms) {
|
||||
msgIds.push(ms.id)
|
||||
} else {
|
||||
reject({status:500,message:"please try again"}); return
|
||||
}
|
||||
}
|
||||
|
||||
// save
|
||||
|
||||
resolve(await this.writeFile(
|
||||
uploadId,
|
||||
{
|
||||
filename:settings.name,
|
||||
messageids:msgIds,
|
||||
mime:settings.mime
|
||||
}
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
// fs
|
||||
|
||||
writeFile(uploadId: string, file: FilePointer):Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this.files[uploadId] = file
|
||||
|
||||
writeFile(process.cwd()+"/.data/files.json",JSON.stringify(this.files),(err) => {
|
||||
|
||||
if (err) {
|
||||
reject({status:500,message:"please try again"});
|
||||
delete this.files[uploadId];
|
||||
return
|
||||
}
|
||||
|
||||
resolve(uploadId)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// todo: move read code here
|
||||
|
||||
readFileStream(uploadId: string):Promise<{dataStream:Readable,contentType:string}> {
|
||||
return new Promise(async (resolve,reject) => {
|
||||
if (!this.uploadChannel) {
|
||||
reject({status:503,message:"server is not ready - please try again later"})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.files[uploadId]) {
|
||||
let file = this.files[uploadId]
|
||||
|
||||
let dataStream = new Readable({
|
||||
read(){}
|
||||
})
|
||||
|
||||
resolve({
|
||||
contentType: file.mime,
|
||||
dataStream: dataStream
|
||||
})
|
||||
|
||||
for (let i = 0; i < file.messageids.length; i++) {
|
||||
let msg = await this.uploadChannel.messages.fetch(file.messageids[i]).catch(() => {return null})
|
||||
if (msg?.attachments) {
|
||||
let attach = Array.from(msg.attachments.values())
|
||||
for (let i = 0; i < attach.length; i++) {
|
||||
let d = await axios.get(attach[i].url,{responseType:"arraybuffer"}).catch((e:Error) => {console.error(e)})
|
||||
if (d) {
|
||||
dataStream.push(d.data)
|
||||
} else {
|
||||
reject({status:500,message:"internal server error"})
|
||||
dataStream.destroy(new Error("file read error"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataStream.push(null)
|
||||
|
||||
} else {
|
||||
reject({status:404,message:"not found"})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
unlink(uploadId:string):Promise<void> {
|
||||
return new Promise((resolve,reject) => {
|
||||
let tmp = this.files[uploadId];
|
||||
delete this.files[uploadId];
|
||||
writeFile(process.cwd()+"/.data/files.json",JSON.stringify(this.files),(err) => {
|
||||
if (err) {
|
||||
this.files[uploadId] = tmp
|
||||
reject()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
getFilePointer(uploadId:string):FilePointer {
|
||||
return this.files[uploadId]
|
||||
}
|
||||
|
||||
}
|
||||
import axios from "axios";
|
||||
import Discord, { Client, TextBasedChannel } from "discord.js";
|
||||
import { readFile, writeFile } from "fs";
|
||||
import { Readable } from "node:stream"
|
||||
|
||||
export let id_check_regex = /[A-Za-z0-9_\-\.]+/
|
||||
export let alphanum = Array.from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
|
||||
|
||||
// bad solution but whatever
|
||||
|
||||
export function generateFileId() {
|
||||
let fid = ""
|
||||
for (let i = 0; i < 5; i++) {
|
||||
fid += alphanum[Math.floor(Math.random()*alphanum.length)]
|
||||
}
|
||||
return fid
|
||||
}
|
||||
|
||||
export interface FileUploadSettings {
|
||||
name?: string,
|
||||
mime: string,
|
||||
uploadId?: string,
|
||||
owner?:string
|
||||
}
|
||||
|
||||
export interface Configuration {
|
||||
maxDiscordFiles: number,
|
||||
maxDiscordFileSize: number,
|
||||
targetGuild: string,
|
||||
targetChannel: string,
|
||||
requestTimeout: number,
|
||||
|
||||
accounts: {
|
||||
registrationEnabled: boolean,
|
||||
requiredForUpload: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface FilePointer {
|
||||
filename:string,
|
||||
mime:string,
|
||||
messageids:string[],
|
||||
owner?:string
|
||||
}
|
||||
|
||||
export interface StatusCodeError {
|
||||
status: number,
|
||||
message: string
|
||||
}
|
||||
|
||||
/* */
|
||||
|
||||
export default class Files {
|
||||
|
||||
config: Configuration
|
||||
client: Client
|
||||
files: {[key:string]:FilePointer} = {}
|
||||
uploadChannel?: TextBasedChannel
|
||||
|
||||
constructor(client: Client, config: Configuration) {
|
||||
|
||||
this.config = config;
|
||||
this.client = client;
|
||||
|
||||
client.on("ready",() => {
|
||||
console.log("Discord OK!")
|
||||
|
||||
client.guilds.fetch(config.targetGuild).then((g) => {
|
||||
g.channels.fetch(config.targetChannel).then((a) => {
|
||||
if (a?.isTextBased()) {
|
||||
this.uploadChannel = a
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
readFile(process.cwd()+"/.data/files.json",(err,buf) => {
|
||||
if (err) {console.log(err);return}
|
||||
this.files = JSON.parse(buf.toString() || "{}")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
uploadFile(settings:FileUploadSettings,fBuffer:Buffer):Promise<string|StatusCodeError> {
|
||||
return new Promise<string>(async (resolve,reject) => {
|
||||
if (!this.uploadChannel) {
|
||||
reject({status:503,message:"server is not ready - please try again later"})
|
||||
return
|
||||
}
|
||||
|
||||
if (!settings.name || !settings.mime) {
|
||||
reject({status:400,message:"missing name/mime"});
|
||||
return
|
||||
}
|
||||
|
||||
if (!settings.owner && this.config.accounts.requiredForUpload) {
|
||||
reject({status:401,message:"an account is required for upload"});
|
||||
return
|
||||
}
|
||||
|
||||
let uploadId = (settings.uploadId || generateFileId()).toString();
|
||||
|
||||
if ((uploadId.match(id_check_regex) || [])[0] != uploadId || uploadId.length > 30) {
|
||||
reject({status:400,message:"invalid id"});return
|
||||
}
|
||||
|
||||
if (this.files[uploadId]) {
|
||||
reject({status:400,message:"a file with this id already exists"});
|
||||
return
|
||||
}
|
||||
|
||||
if (settings.name.length > 128) {
|
||||
reject({status:400,message:"name too long"});
|
||||
return
|
||||
}
|
||||
|
||||
if (settings.mime.length > 128) {
|
||||
reject({status:400,message:"mime too long"});
|
||||
return
|
||||
}
|
||||
|
||||
// get buffer
|
||||
if (fBuffer.byteLength >= (this.config.maxDiscordFileSize*this.config.maxDiscordFiles)) {
|
||||
reject({status:400,message:"file too large"});
|
||||
return
|
||||
}
|
||||
|
||||
// generate buffers to upload
|
||||
let toUpload = []
|
||||
for (let i = 0; i < Math.ceil(fBuffer.byteLength/this.config.maxDiscordFileSize); i++) {
|
||||
toUpload.push(
|
||||
fBuffer.subarray(
|
||||
i*this.config.maxDiscordFileSize,
|
||||
Math.min(
|
||||
fBuffer.byteLength,
|
||||
(i+1)*this.config.maxDiscordFileSize
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// begin uploading
|
||||
let uploadTmplt:Discord.AttachmentBuilder[] = toUpload.map((e) => {
|
||||
return new Discord.AttachmentBuilder(e)
|
||||
.setName(Math.random().toString().slice(2))
|
||||
})
|
||||
let uploadGroups = []
|
||||
for (let i = 0; i < Math.ceil(uploadTmplt.length/10); i++) {
|
||||
uploadGroups.push(uploadTmplt.slice(i*10,((i+1)*10)))
|
||||
}
|
||||
|
||||
let msgIds = []
|
||||
|
||||
for (let i = 0; i < uploadGroups.length; i++) {
|
||||
|
||||
let ms = await this.uploadChannel.send({
|
||||
files:uploadGroups[i]
|
||||
}).catch((e) => {console.error(e)})
|
||||
|
||||
if (ms) {
|
||||
msgIds.push(ms.id)
|
||||
} else {
|
||||
reject({status:500,message:"please try again"}); return
|
||||
}
|
||||
}
|
||||
|
||||
// save
|
||||
|
||||
resolve(await this.writeFile(
|
||||
uploadId,
|
||||
{
|
||||
filename:settings.name,
|
||||
messageids:msgIds,
|
||||
mime:settings.mime,
|
||||
|
||||
owner:settings.owner
|
||||
}
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
// fs
|
||||
|
||||
writeFile(uploadId: string, file: FilePointer):Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this.files[uploadId] = file
|
||||
|
||||
writeFile(process.cwd()+"/.data/files.json",JSON.stringify(this.files),(err) => {
|
||||
|
||||
if (err) {
|
||||
reject({status:500,message:"please try again"});
|
||||
delete this.files[uploadId];
|
||||
return
|
||||
}
|
||||
|
||||
resolve(uploadId)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// todo: move read code here
|
||||
|
||||
readFileStream(uploadId: string):Promise<{dataStream:Readable,contentType:string}> {
|
||||
return new Promise(async (resolve,reject) => {
|
||||
if (!this.uploadChannel) {
|
||||
reject({status:503,message:"server is not ready - please try again later"})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.files[uploadId]) {
|
||||
let file = this.files[uploadId]
|
||||
|
||||
let dataStream = new Readable({
|
||||
read(){}
|
||||
})
|
||||
|
||||
resolve({
|
||||
contentType: file.mime,
|
||||
dataStream: dataStream
|
||||
})
|
||||
|
||||
for (let i = 0; i < file.messageids.length; i++) {
|
||||
let msg = await this.uploadChannel.messages.fetch(file.messageids[i]).catch(() => {return null})
|
||||
if (msg?.attachments) {
|
||||
let attach = Array.from(msg.attachments.values())
|
||||
for (let i = 0; i < attach.length; i++) {
|
||||
let d = await axios.get(attach[i].url,{responseType:"arraybuffer"}).catch((e:Error) => {console.error(e)})
|
||||
if (d) {
|
||||
dataStream.push(d.data)
|
||||
} else {
|
||||
reject({status:500,message:"internal server error"})
|
||||
dataStream.destroy(new Error("file read error"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataStream.push(null)
|
||||
|
||||
} else {
|
||||
reject({status:404,message:"not found"})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
unlink(uploadId:string):Promise<void> {
|
||||
return new Promise((resolve,reject) => {
|
||||
let tmp = this.files[uploadId];
|
||||
delete this.files[uploadId];
|
||||
writeFile(process.cwd()+"/.data/files.json",JSON.stringify(this.files),(err) => {
|
||||
if (err) {
|
||||
this.files[uploadId] = tmp
|
||||
reject()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
getFilePointer(uploadId:string):FilePointer {
|
||||
return this.files[uploadId]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
137
src/server/routes/authRoutes.ts
Normal file
137
src/server/routes/authRoutes.ts
Normal file
|
@ -0,0 +1,137 @@
|
|||
import bodyParser from "body-parser";
|
||||
import { Router } from "express";
|
||||
import * as Accounts from "../lib/accounts";
|
||||
import * as auth from "../lib/auth";
|
||||
|
||||
import ServeError from "../lib/errors";
|
||||
|
||||
let parser = bodyParser.json({
|
||||
type: ["text/plain","application/json"]
|
||||
})
|
||||
|
||||
export let authRoutes = Router();
|
||||
|
||||
let config = require(`${process.cwd()}/config.json`)
|
||||
|
||||
|
||||
authRoutes.post("/login", parser, (req,res) => {
|
||||
let body:{[key:string]:any}
|
||||
try {
|
||||
body = JSON.parse(req.body)
|
||||
} catch {
|
||||
ServeError(res,400,"bad request")
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof body.username != "string" || typeof body.password != "string") {
|
||||
ServeError(res,400,"please provide a username or password")
|
||||
return
|
||||
}
|
||||
|
||||
if (auth.validate(req.cookies.auth)) return
|
||||
|
||||
/*
|
||||
check if account exists
|
||||
*/
|
||||
|
||||
let acc = Accounts.getFromUsername(body.username)
|
||||
|
||||
if (!acc) {
|
||||
ServeError(res,401,"username or password incorrect")
|
||||
return
|
||||
}
|
||||
|
||||
if (!Accounts.password.check(acc.id,body.password)) {
|
||||
ServeError(res,401,"username or password incorrect")
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
assign token
|
||||
*/
|
||||
|
||||
res.cookie("auth",auth.create(acc.id,(3*24*60*60*1000)))
|
||||
res.status(200)
|
||||
res.end()
|
||||
})
|
||||
|
||||
authRoutes.post("/create", parser, (req,res) => {
|
||||
if (!config.accounts.registrationEnabled) {
|
||||
ServeError(res,403,"account registration disabled")
|
||||
return
|
||||
}
|
||||
|
||||
let body:{[key:string]:any}
|
||||
try {
|
||||
body = JSON.parse(req.body)
|
||||
} catch {
|
||||
ServeError(res,400,"bad request")
|
||||
return
|
||||
}
|
||||
|
||||
if (auth.validate(req.cookies.auth)) return
|
||||
|
||||
if (typeof body.username != "string" || typeof body.password != "string") {
|
||||
ServeError(res,400,"please provide a username or password")
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
check if account exists
|
||||
*/
|
||||
|
||||
let acc = Accounts.getFromUsername(body.username)
|
||||
|
||||
if (acc) {
|
||||
ServeError(res,400,"account with this username already exists")
|
||||
return
|
||||
}
|
||||
|
||||
if (body.username.length < 3 || body.username.length > 20) {
|
||||
ServeError(res,400,"username must be over or equal to 3 characters or under or equal to 20 characters in length")
|
||||
return
|
||||
}
|
||||
|
||||
if ((body.username.match(/[A-Za-z0-9_\-\.]+/) || [])[0] != body.username) {
|
||||
ServeError(res,400,"username contains invalid characters")
|
||||
return
|
||||
}
|
||||
|
||||
if (body.password.length < 8) {
|
||||
ServeError(res,400,"password must be 8 characters or longer")
|
||||
return
|
||||
}
|
||||
|
||||
let newAcc = Accounts.create(body.username,body.password)
|
||||
|
||||
/*
|
||||
assign token
|
||||
*/
|
||||
|
||||
res.cookie("auth",auth.create(newAcc,(3*24*60*60*1000)))
|
||||
res.status(200)
|
||||
res.end()
|
||||
})
|
||||
|
||||
authRoutes.post("/logout", (req,res) => {
|
||||
if (!auth.validate(req.cookies.auth)) {
|
||||
ServeError(res, 401, "not logged in")
|
||||
return
|
||||
}
|
||||
|
||||
auth.invalidate(req.cookies.auth)
|
||||
res.send("logged out")
|
||||
})
|
||||
|
||||
|
||||
authRoutes.get("/me", (req,res) => {
|
||||
if (!auth.validate(req.cookies.auth)) {
|
||||
ServeError(res, 401, "not logged in")
|
||||
return
|
||||
}
|
||||
|
||||
// lazy rn so
|
||||
|
||||
let acc = Accounts.getFromToken(req.cookies.auth)
|
||||
res.send(acc)
|
||||
})
|
|
@ -1,83 +1,83 @@
|
|||
/*
|
||||
could probably replace this with fonts served directly
|
||||
from the server but it's fine for now
|
||||
*/
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&family=Inconsolata&family=Source+Sans+Pro:wght@300;400;600;700;900&display=swap');
|
||||
|
||||
$FallbackFonts:
|
||||
-apple-system,
|
||||
system-ui,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
sans-serif;
|
||||
|
||||
%normal {
|
||||
font-family: "Source Sans Pro", $FallbackFonts
|
||||
}
|
||||
|
||||
/*
|
||||
everything that's not a span
|
||||
and/or has the normal class
|
||||
(it's just in case)
|
||||
*/
|
||||
|
||||
*:not(span), .normal { @extend %normal; }
|
||||
|
||||
/*
|
||||
for code blocks / terminal
|
||||
*/
|
||||
|
||||
.monospace {
|
||||
font-family: "Fira Code", monospace
|
||||
}
|
||||
|
||||
/*
|
||||
colors
|
||||
*/
|
||||
|
||||
$Background: #252525;
|
||||
/* hsl(210,12.9,24.3) */
|
||||
$darkish: rgb(54, 62, 70);
|
||||
|
||||
/*
|
||||
then other stuff
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: rgb(30, 33, 36); // this is here so that
|
||||
// pulling down to refresh
|
||||
// on mobile looks good
|
||||
}
|
||||
|
||||
#appContent {
|
||||
background-color: $Background
|
||||
}
|
||||
|
||||
/*
|
||||
scrollbars
|
||||
*/
|
||||
|
||||
* {
|
||||
/* nice scrollbars aren't needed on mobile so */
|
||||
@media screen and (min-width:500px) {
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width:5px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color:#191919;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color:#333;
|
||||
|
||||
&:hover {
|
||||
background-color:#373737;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
could probably replace this with fonts served directly
|
||||
from the server but it's fine for now
|
||||
*/
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&family=Inconsolata&family=Source+Sans+Pro:wght@300;400;600;700;900&display=swap');
|
||||
|
||||
$FallbackFonts:
|
||||
-apple-system,
|
||||
system-ui,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
sans-serif;
|
||||
|
||||
%normal {
|
||||
font-family: "Source Sans Pro", $FallbackFonts
|
||||
}
|
||||
|
||||
/*
|
||||
everything that's not a span
|
||||
and/or has the normal class
|
||||
(it's just in case)
|
||||
*/
|
||||
|
||||
*:not(span), .normal { @extend %normal; }
|
||||
|
||||
/*
|
||||
for code blocks / terminal
|
||||
*/
|
||||
|
||||
.monospace {
|
||||
font-family: "Fira Code", monospace
|
||||
}
|
||||
|
||||
/*
|
||||
colors
|
||||
*/
|
||||
|
||||
$Background: #252525;
|
||||
/* hsl(210,12.9,24.3) */
|
||||
$darkish: rgb(54, 62, 70);
|
||||
|
||||
/*
|
||||
then other stuff
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: rgb(30, 33, 36); // this is here so that
|
||||
// pulling down to refresh
|
||||
// on mobile looks good
|
||||
}
|
||||
|
||||
#appContent {
|
||||
background-color: $Background
|
||||
}
|
||||
|
||||
/*
|
||||
scrollbars
|
||||
*/
|
||||
|
||||
* {
|
||||
/* nice scrollbars aren't needed on mobile so */
|
||||
@media screen and (min-width:500px) {
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width:5px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color:#191919;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color:#333;
|
||||
|
||||
&:hover {
|
||||
background-color:#373737;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,37 +1,37 @@
|
|||
@use "base";
|
||||
@use "app/topbar";
|
||||
@use "app/pulldown";
|
||||
@use "app/uploads";
|
||||
|
||||
.menuBtn {
|
||||
text-decoration:none;
|
||||
font-size:16px;
|
||||
transition-duration: 100ms;
|
||||
|
||||
color:#555555;
|
||||
background-color: #00000000;
|
||||
border:none;
|
||||
margin:0 0 0 0;
|
||||
cursor:pointer;
|
||||
|
||||
position:relative;
|
||||
top:-1px;
|
||||
|
||||
&:hover {
|
||||
color:slategray;
|
||||
transition-duration: 100ms;
|
||||
}
|
||||
}
|
||||
|
||||
#appContent {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:40px;
|
||||
width:100%;
|
||||
height: calc( 100% - 40px );
|
||||
background-image: linear-gradient(#333,base.$Background);
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
background-image: linear-gradient(#303030,base.$Background);
|
||||
}
|
||||
@use "base";
|
||||
@use "app/topbar";
|
||||
@use "app/pulldown";
|
||||
@use "app/uploads";
|
||||
|
||||
.menuBtn {
|
||||
text-decoration:none;
|
||||
font-size:16px;
|
||||
transition-duration: 100ms;
|
||||
|
||||
color:#555555;
|
||||
background-color: #00000000;
|
||||
border:none;
|
||||
margin:0 0 0 0;
|
||||
cursor:pointer;
|
||||
|
||||
position:relative;
|
||||
top:-1px;
|
||||
|
||||
&:hover {
|
||||
color:slategray;
|
||||
transition-duration: 100ms;
|
||||
}
|
||||
}
|
||||
|
||||
#appContent {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:40px;
|
||||
width:100%;
|
||||
height: calc( 100% - 40px );
|
||||
background-image: linear-gradient(#333,base.$Background);
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
background-image: linear-gradient(#303030,base.$Background);
|
||||
}
|
||||
}
|
|
@ -1,49 +1,49 @@
|
|||
@use "../base";
|
||||
@use "pulldown/help";
|
||||
@use "pulldown/accounts";
|
||||
@use "pulldown/files";
|
||||
|
||||
#overlay {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
height: 100%;
|
||||
width:100%;
|
||||
top:0px;
|
||||
opacity:0.25;
|
||||
border:none;
|
||||
outline:none;
|
||||
background-color:#AAAAAA;
|
||||
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.pulldown {
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
background-color: #191919;
|
||||
color: #dddddd;
|
||||
|
||||
top:0px;
|
||||
left:50%;
|
||||
transform:translateX(-50%);
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
p, h1, h2 {
|
||||
margin:0px;
|
||||
}
|
||||
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.pulldown_display {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
@use "../base";
|
||||
@use "pulldown/help";
|
||||
@use "pulldown/accounts";
|
||||
@use "pulldown/files";
|
||||
|
||||
#overlay {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
height: 100%;
|
||||
width:100%;
|
||||
top:0px;
|
||||
opacity:0.25;
|
||||
border:none;
|
||||
outline:none;
|
||||
background-color:#AAAAAA;
|
||||
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.pulldown {
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
background-color: #191919;
|
||||
color: #dddddd;
|
||||
|
||||
top:0px;
|
||||
left:50%;
|
||||
transform:translateX(-50%);
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
p, h1, h2 {
|
||||
margin:0px;
|
||||
}
|
||||
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.pulldown_display {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
|
@ -1,120 +1,188 @@
|
|||
.pulldown_display[name=accounts] {
|
||||
.notLoggedIn {
|
||||
.container_div {
|
||||
position:absolute;
|
||||
top:50%;
|
||||
transform:translateY(-50%);
|
||||
width:100%;
|
||||
text-align:center;
|
||||
|
||||
h1 {
|
||||
font-weight:600;
|
||||
font-size:24px;
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
font-size:30px;
|
||||
}
|
||||
}
|
||||
|
||||
.flavor {
|
||||
font-size:14px;
|
||||
|
||||
/* good enoough */
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
font-size:16px;
|
||||
}
|
||||
|
||||
color:#999999;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor:pointer;
|
||||
background-color:#393939;
|
||||
color:#DDDDDD;
|
||||
border:none;
|
||||
outline:none;
|
||||
padding:5px;
|
||||
transition-duration: 250ms;
|
||||
/*overflow:clip;*/
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition-duration: 250ms;
|
||||
background-color:#434343;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
flex-basis:50%;
|
||||
flex-grow:1;
|
||||
}
|
||||
|
||||
input[type=text],input[type=password] {
|
||||
border:none;
|
||||
border-radius:0;
|
||||
width:100%;
|
||||
padding:5px;
|
||||
background-color:#333333;
|
||||
color:#dddddd;
|
||||
outline:none;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
}
|
||||
|
||||
.lgBtnContainer {
|
||||
display:flex;
|
||||
position:relative;
|
||||
left:20px;
|
||||
width:calc( 100% - 40px );
|
||||
gap:10px;
|
||||
overflow:clip;
|
||||
}
|
||||
|
||||
.fields {
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
position:relative;
|
||||
left:20px;
|
||||
width:calc( 100% - 40px );
|
||||
gap:5px;
|
||||
overflow:clip;
|
||||
}
|
||||
|
||||
/*
|
||||
a {
|
||||
text-decoration: none;
|
||||
color:#999999;
|
||||
font-size:14px;
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
font-size:16px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content:" ➜";
|
||||
font-size:0px;
|
||||
opacity: 0;
|
||||
transition-duration:250ms;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
font-size:13px;
|
||||
opacity: 1;
|
||||
transition-duration:250ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
.pulldown_display[name=accounts] {
|
||||
.notLoggedIn {
|
||||
.container_div {
|
||||
position:absolute;
|
||||
top:50%;
|
||||
transform:translateY(-50%);
|
||||
width:100%;
|
||||
text-align:center;
|
||||
|
||||
h1 {
|
||||
font-weight:600;
|
||||
font-size:24px;
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
font-size:30px;
|
||||
}
|
||||
}
|
||||
|
||||
.flavor {
|
||||
font-size:14px;
|
||||
|
||||
/* good enoough */
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
font-size:16px;
|
||||
}
|
||||
|
||||
color:#999999;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor:pointer;
|
||||
background-color:#393939;
|
||||
color:#DDDDDD;
|
||||
border:none;
|
||||
outline:none;
|
||||
padding:5px;
|
||||
transition-duration: 250ms;
|
||||
/*overflow:clip;*/
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition-duration: 250ms;
|
||||
background-color:#434343;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
flex-basis:50%;
|
||||
flex-grow:1;
|
||||
}
|
||||
|
||||
input[type=text],input[type=password] {
|
||||
border:none;
|
||||
border-radius:0;
|
||||
width:100%;
|
||||
padding:5px;
|
||||
background-color:#333333;
|
||||
color:#dddddd;
|
||||
outline:none;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
}
|
||||
|
||||
.pwError {
|
||||
div {
|
||||
border:none;
|
||||
border-radius:0;
|
||||
width:100%;
|
||||
padding:5px;
|
||||
background-color:#663333;
|
||||
color:#dddddd;
|
||||
outline:none;
|
||||
font-size:14px;
|
||||
text-align:left;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lgBtnContainer {
|
||||
display:flex;
|
||||
position:relative;
|
||||
left:20px;
|
||||
width:calc( 100% - 40px );
|
||||
gap:10px;
|
||||
overflow:clip;
|
||||
}
|
||||
|
||||
.fields {
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
position:relative;
|
||||
left:20px;
|
||||
width:calc( 100% - 40px );
|
||||
gap:5px;
|
||||
overflow:clip;
|
||||
}
|
||||
|
||||
/*
|
||||
a {
|
||||
text-decoration: none;
|
||||
color:#999999;
|
||||
font-size:14px;
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
font-size:16px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content:" ➜";
|
||||
font-size:0px;
|
||||
opacity: 0;
|
||||
transition-duration:250ms;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
font-size:13px;
|
||||
opacity: 1;
|
||||
transition-duration:250ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.loggedIn {
|
||||
position:absolute;
|
||||
left:10px;
|
||||
top:10px;
|
||||
width:calc( 100% - 20px );
|
||||
height:calc( 100% - 20px );
|
||||
|
||||
h1 {
|
||||
font-weight:600;
|
||||
font-size:20px;
|
||||
color: #AAAAAA;
|
||||
}
|
||||
|
||||
.accountOptions {
|
||||
button {
|
||||
position:relative;
|
||||
width:100%;
|
||||
cursor:pointer;
|
||||
height:50px;
|
||||
background-color: #191919;
|
||||
border:none;
|
||||
border-bottom:1px solid #AAAAAA;
|
||||
transition-duration:150ms;
|
||||
|
||||
img {
|
||||
position:absolute;
|
||||
left:13px;
|
||||
top:50%;
|
||||
transform:translateY(-50%);
|
||||
}
|
||||
|
||||
p {
|
||||
position:absolute;
|
||||
top:50%;
|
||||
left:50px;
|
||||
color:#DDDDDD;
|
||||
transform:translateY(-50%);
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition-duration:150ms;
|
||||
background-color: #252525;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +1,34 @@
|
|||
.pulldown_display[name=files] {
|
||||
.notLoggedIn {
|
||||
position:absolute;
|
||||
top:100%;
|
||||
transform:translateY(-100%);
|
||||
width:100%;
|
||||
text-align:center;
|
||||
background-color:#202020;
|
||||
|
||||
.flavor {
|
||||
font-size:16px;
|
||||
color:#999999;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
--col: #999999;
|
||||
|
||||
background-color: #232323;
|
||||
color:var(--col);
|
||||
font-size:14px;
|
||||
border:1px solid var(--col);
|
||||
padding:2px 20px 2px 20px;
|
||||
cursor:pointer;
|
||||
transition-duration:250ms;
|
||||
|
||||
&:hover {
|
||||
background-color:#333333;
|
||||
transition-duration:250ms;
|
||||
--col:#BBBBBB;
|
||||
}
|
||||
}
|
||||
}
|
||||
.pulldown_display[name=files] {
|
||||
.notLoggedIn {
|
||||
position:absolute;
|
||||
top:100%;
|
||||
transform:translateY(-100%);
|
||||
width:100%;
|
||||
text-align:center;
|
||||
background-color:#202020;
|
||||
|
||||
.flavor {
|
||||
font-size:16px;
|
||||
color:#999999;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
--col: #999999;
|
||||
|
||||
background-color: #232323;
|
||||
color:var(--col);
|
||||
font-size:14px;
|
||||
border:1px solid var(--col);
|
||||
padding:2px 20px 2px 20px;
|
||||
cursor:pointer;
|
||||
transition-duration:250ms;
|
||||
|
||||
&:hover {
|
||||
background-color:#333333;
|
||||
transition-duration:250ms;
|
||||
--col:#BBBBBB;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
.pulldown_display[name=help] {
|
||||
|
||||
overflow-y:auto;
|
||||
|
||||
.faqGroup {
|
||||
padding:6px 10px 4px 10px;
|
||||
|
||||
h2 {
|
||||
font-weight: 400;
|
||||
color:#DDDDDD;
|
||||
font-size:16px;
|
||||
margin:0 0 0 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color:#999999;
|
||||
font-size:16px;
|
||||
margin:0 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.pulldown_display[name=help] {
|
||||
|
||||
overflow-y:auto;
|
||||
|
||||
.faqGroup {
|
||||
padding:6px 10px 4px 10px;
|
||||
|
||||
h2 {
|
||||
font-weight: 400;
|
||||
color:#DDDDDD;
|
||||
font-size:16px;
|
||||
margin:0 0 0 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color:#999999;
|
||||
font-size:16px;
|
||||
margin:0 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +1,21 @@
|
|||
@use "../base";
|
||||
|
||||
#topbar {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
|
||||
width:100%;
|
||||
height:40px;
|
||||
|
||||
/* hsl(210,9.1,12.9) */
|
||||
background-color: rgb(30, 33, 36);
|
||||
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
column-gap:5px;
|
||||
|
||||
@use "../base";
|
||||
|
||||
#topbar {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
|
||||
width:100%;
|
||||
height:40px;
|
||||
|
||||
/* hsl(210,9.1,12.9) */
|
||||
background-color: rgb(30, 33, 36);
|
||||
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
column-gap:5px;
|
||||
|
||||
}
|
|
@ -1,115 +1,115 @@
|
|||
#uploadWindow {
|
||||
#add_new_files {
|
||||
background-color:#191919;
|
||||
border: 1px solid gray;
|
||||
padding: 0px 0px 10px 0px;
|
||||
|
||||
p {
|
||||
font-family: "Fira Code", monospace;
|
||||
text-align: left;
|
||||
margin: 0px 0px 0px 10px;
|
||||
font-size: 30px;
|
||||
|
||||
span {
|
||||
position:relative;
|
||||
|
||||
&._add_files_txt {
|
||||
font-size:16px;
|
||||
top:-4px;
|
||||
left:10px;
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
font-size:20px;
|
||||
top:-6px;
|
||||
left:10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
font-size: 40px;
|
||||
|
||||
span._add_files_txt {
|
||||
font-size:20px;
|
||||
top:-6px;
|
||||
left:10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#file_add_btns {
|
||||
width:calc( 100% - 20px );
|
||||
margin:auto;
|
||||
position:relative;
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
column-gap:10px;
|
||||
|
||||
button, input[type=text] {
|
||||
background-color:#333333;
|
||||
color:#DDDDDD;
|
||||
border:none;
|
||||
border-radius: 0px;
|
||||
outline:none;
|
||||
padding:5px;
|
||||
|
||||
flex-basis:50%;
|
||||
flex-grow:1;
|
||||
transition-duration:250ms;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
cursor:pointer;
|
||||
|
||||
&:hover {
|
||||
@media screen and (min-width: 500px) {
|
||||
transition-duration:250ms;
|
||||
flex-basis: 60%;
|
||||
}
|
||||
background-color:#393939;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.fileUpload {
|
||||
width:100%;
|
||||
height:100px;
|
||||
position:relative;
|
||||
|
||||
background-color:#262626;
|
||||
transition-duration:250ms;
|
||||
|
||||
input[type=file] {
|
||||
opacity: 0;
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
p {
|
||||
position:absolute;
|
||||
top:50%;
|
||||
transform:translateY(-50%);
|
||||
font-size:12px;
|
||||
width:100%;
|
||||
text-align:center;
|
||||
padding:0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition-duration:250ms;
|
||||
background-color:#292929;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#uploadWindow {
|
||||
#add_new_files {
|
||||
background-color:#191919;
|
||||
border: 1px solid gray;
|
||||
padding: 0px 0px 10px 0px;
|
||||
|
||||
p {
|
||||
font-family: "Fira Code", monospace;
|
||||
text-align: left;
|
||||
margin: 0px 0px 0px 10px;
|
||||
font-size: 30px;
|
||||
|
||||
span {
|
||||
position:relative;
|
||||
|
||||
&._add_files_txt {
|
||||
font-size:16px;
|
||||
top:-4px;
|
||||
left:10px;
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
font-size:20px;
|
||||
top:-6px;
|
||||
left:10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
font-size: 40px;
|
||||
|
||||
span._add_files_txt {
|
||||
font-size:20px;
|
||||
top:-6px;
|
||||
left:10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#file_add_btns {
|
||||
width:calc( 100% - 20px );
|
||||
margin:auto;
|
||||
position:relative;
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
column-gap:10px;
|
||||
|
||||
button, input[type=text] {
|
||||
background-color:#333333;
|
||||
color:#DDDDDD;
|
||||
border:none;
|
||||
border-radius: 0px;
|
||||
outline:none;
|
||||
padding:5px;
|
||||
|
||||
flex-basis:50%;
|
||||
flex-grow:1;
|
||||
transition-duration:250ms;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
cursor:pointer;
|
||||
|
||||
&:hover {
|
||||
@media screen and (min-width: 500px) {
|
||||
transition-duration:250ms;
|
||||
flex-basis: 60%;
|
||||
}
|
||||
background-color:#393939;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.fileUpload {
|
||||
width:100%;
|
||||
height:100px;
|
||||
position:relative;
|
||||
|
||||
background-color:#262626;
|
||||
transition-duration:250ms;
|
||||
|
||||
input[type=file] {
|
||||
opacity: 0;
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
p {
|
||||
position:absolute;
|
||||
top:50%;
|
||||
transform:translateY(-50%);
|
||||
font-size:12px;
|
||||
width:100%;
|
||||
text-align:center;
|
||||
padding:0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition-duration:250ms;
|
||||
background-color:#292929;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +1,59 @@
|
|||
// should probably start using mixins for thingss like this
|
||||
|
||||
#uploadWindow {
|
||||
.file {
|
||||
background-color:#191919;
|
||||
border: 1px solid gray;
|
||||
padding: 10px;
|
||||
overflow:clip;
|
||||
position:relative;
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
margin: 0px;
|
||||
font-weight:600;
|
||||
width:calc( 100% - 20px );
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
background-color:#333333;
|
||||
color:#DDDDDD;
|
||||
border:none;
|
||||
outline:none;
|
||||
padding:5px;
|
||||
position:relative;
|
||||
|
||||
width:100%;
|
||||
transition-duration:250ms;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
display:flex;
|
||||
column-gap:10px;
|
||||
|
||||
button {
|
||||
flex-basis: 50%;
|
||||
flex-grow: 1;
|
||||
padding:5px;
|
||||
}
|
||||
}
|
||||
|
||||
.uploadingContainer {
|
||||
color: #AAAAAA;
|
||||
}
|
||||
|
||||
.hitbox {
|
||||
opacity:0;
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
height:100%;
|
||||
width:100%;
|
||||
}
|
||||
}
|
||||
// should probably start using mixins for thingss like this
|
||||
|
||||
#uploadWindow {
|
||||
.file {
|
||||
background-color:#191919;
|
||||
border: 1px solid gray;
|
||||
padding: 10px;
|
||||
overflow:clip;
|
||||
position:relative;
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
margin: 0px;
|
||||
font-weight:600;
|
||||
width:calc( 100% - 20px );
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
background-color:#333333;
|
||||
color:#DDDDDD;
|
||||
border:none;
|
||||
outline:none;
|
||||
padding:5px;
|
||||
position:relative;
|
||||
|
||||
width:100%;
|
||||
transition-duration:250ms;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
display:flex;
|
||||
column-gap:10px;
|
||||
|
||||
button {
|
||||
flex-basis: 50%;
|
||||
flex-grow: 1;
|
||||
padding:5px;
|
||||
}
|
||||
}
|
||||
|
||||
.uploadingContainer {
|
||||
color: #AAAAAA;
|
||||
}
|
||||
|
||||
.hitbox {
|
||||
opacity:0;
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
height:100%;
|
||||
width:100%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,76 +1,76 @@
|
|||
@use "uploader/add_new_files";
|
||||
@use "uploader/file";
|
||||
|
||||
#uploadWindow {
|
||||
position:absolute;
|
||||
left:50%;
|
||||
top:50%;
|
||||
transform:translate(-50%,-50%);
|
||||
padding:10px 15px 10px 15px;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
|
||||
width:350px;
|
||||
@media screen and (min-width:500px) {
|
||||
max-height: calc( 100% - 80px );
|
||||
}
|
||||
|
||||
background-color:#222222;
|
||||
color:#ddd;
|
||||
|
||||
h1, p, a {
|
||||
margin: 0px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
color:#999;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight:600;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-family: "Inconsolata", monospace;
|
||||
}
|
||||
|
||||
.uploadContainer {
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor:pointer;
|
||||
background-color:#393939;
|
||||
color:#DDDDDD;
|
||||
border:none;
|
||||
outline:none;
|
||||
padding:5px;
|
||||
transition-duration: 250ms;
|
||||
/*overflow:clip;*/
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition-duration: 250ms;
|
||||
background-color:#434343;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
width: calc( 100% - 20px );
|
||||
height: calc( 100% - 20px );
|
||||
border-radius:0px;
|
||||
background-color:#00000000;
|
||||
|
||||
transform:none;
|
||||
left:10px;
|
||||
top:10px;
|
||||
padding:0px;
|
||||
}
|
||||
@use "uploader/add_new_files";
|
||||
@use "uploader/file";
|
||||
|
||||
#uploadWindow {
|
||||
position:absolute;
|
||||
left:50%;
|
||||
top:50%;
|
||||
transform:translate(-50%,-50%);
|
||||
padding:10px 15px 10px 15px;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
|
||||
width:350px;
|
||||
@media screen and (min-width:500px) {
|
||||
max-height: calc( 100% - 80px );
|
||||
}
|
||||
|
||||
background-color:#222222;
|
||||
color:#ddd;
|
||||
|
||||
h1, p, a {
|
||||
margin: 0px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
color:#999;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight:600;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-family: "Inconsolata", monospace;
|
||||
}
|
||||
|
||||
.uploadContainer {
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor:pointer;
|
||||
background-color:#393939;
|
||||
color:#DDDDDD;
|
||||
border:none;
|
||||
outline:none;
|
||||
padding:5px;
|
||||
transition-duration: 250ms;
|
||||
/*overflow:clip;*/
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size:16px;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition-duration: 250ms;
|
||||
background-color:#434343;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
width: calc( 100% - 20px );
|
||||
height: calc( 100% - 20px );
|
||||
border-radius:0px;
|
||||
background-color:#00000000;
|
||||
|
||||
transform:none;
|
||||
left:10px;
|
||||
top:10px;
|
||||
padding:0px;
|
||||
}
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
// probably dont need to import the entire
|
||||
// uploads css file
|
||||
// so i might just make a separate file with mixins
|
||||
// and import them
|
||||
|
||||
@use "app/uploads";
|
||||
@use "base";
|
||||
|
||||
#appContent {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-image: linear-gradient(#333,base.$Background);
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
background-image: linear-gradient(#303030,base.$Background);
|
||||
}
|
||||
// probably dont need to import the entire
|
||||
// uploads css file
|
||||
// so i might just make a separate file with mixins
|
||||
// and import them
|
||||
|
||||
@use "app/uploads";
|
||||
@use "base";
|
||||
|
||||
#appContent {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-image: linear-gradient(#333,base.$Background);
|
||||
|
||||
@media screen and (max-width:500px) {
|
||||
background-image: linear-gradient(#303030,base.$Background);
|
||||
}
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
@use "_base";
|
||||
|
||||
.error {
|
||||
font-size:20px;
|
||||
color: lightslategray;
|
||||
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
|
||||
text-align:center;
|
||||
|
||||
.code {
|
||||
font-size:25px;
|
||||
font-family: "Inconsolata", monospace;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
@use "_base";
|
||||
|
||||
.error {
|
||||
font-size:20px;
|
||||
color: lightslategray;
|
||||
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
|
||||
text-align:center;
|
||||
|
||||
.code {
|
||||
font-size:25px;
|
||||
font-family: "Inconsolata", monospace;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import Topbar from "./elem/Topbar.svelte";
|
||||
import PulldownManager from "./elem/PulldownManager.svelte";
|
||||
import UploadWindow from "./elem/UploadWindow.svelte";
|
||||
import { pulldownManager } from "./elem/stores.mjs";
|
||||
|
||||
/**
|
||||
* @type Topbar
|
||||
*/
|
||||
let topbar;
|
||||
|
||||
/**
|
||||
* @type PulldownManager
|
||||
*/
|
||||
let pulldown;
|
||||
|
||||
onMount(() => {
|
||||
pulldownManager.set(pulldown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<Topbar bind:this={topbar} pulldown={pulldown} />
|
||||
<div id="appContent">
|
||||
<PulldownManager bind:this={pulldown} />
|
||||
|
||||
<UploadWindow/>
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import Topbar from "./elem/Topbar.svelte";
|
||||
import PulldownManager from "./elem/PulldownManager.svelte";
|
||||
import UploadWindow from "./elem/UploadWindow.svelte";
|
||||
import { pulldownManager } from "./elem/stores.mjs";
|
||||
|
||||
/**
|
||||
* @type Topbar
|
||||
*/
|
||||
let topbar;
|
||||
|
||||
/**
|
||||
* @type PulldownManager
|
||||
*/
|
||||
let pulldown;
|
||||
|
||||
onMount(() => {
|
||||
pulldownManager.set(pulldown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<Topbar bind:this={topbar} pulldown={pulldown} />
|
||||
<div id="appContent">
|
||||
<PulldownManager bind:this={pulldown} />
|
||||
|
||||
<UploadWindow/>
|
||||
</div>
|
|
@ -1,12 +1,12 @@
|
|||
<script>
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let collection = {}
|
||||
</script>
|
||||
|
||||
<div id="appContent">
|
||||
<div id="uploadWindow">
|
||||
<h2>{collection.name}</h2>
|
||||
<p><span class="number">{collection.id}</span>  — by <strong>@{collection.owner}</strong></p>
|
||||
</div>
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let collection = {}
|
||||
</script>
|
||||
|
||||
<div id="appContent">
|
||||
<div id="uploadWindow">
|
||||
<h2>{collection.name}</h2>
|
||||
<p><span class="number">{collection.id}</span>  — by <strong>@{collection.owner}</strong></p>
|
||||
</div>
|
||||
</div>
|
|
@ -1,49 +1,49 @@
|
|||
<script context="module">
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
// can't find a better way to do this
|
||||
import Files from "./pulldowns/Files.svelte";
|
||||
import Accounts from "./pulldowns/Accounts.svelte";
|
||||
import Help from "./pulldowns/Help.svelte";
|
||||
|
||||
export let allPulldowns = new Map()
|
||||
|
||||
allPulldowns
|
||||
.set("account",Accounts)
|
||||
.set("help",Help)
|
||||
.set("files",Files)
|
||||
|
||||
export const pulldownOpen = writable(false);
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { fade, scale } from "svelte/transition";
|
||||
|
||||
export function isOpen() {
|
||||
return $pulldownOpen
|
||||
}
|
||||
|
||||
export function openPulldown(name) {
|
||||
pulldownOpen.set(name)
|
||||
}
|
||||
|
||||
export function closePulldown() {
|
||||
pulldownOpen.set(false)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
||||
})
|
||||
</script>
|
||||
{#if $pulldownOpen}
|
||||
<div class="pulldown" transition:fade={{duration:200}}>
|
||||
<svelte:component this={allPulldowns.get($pulldownOpen)} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="overlay"
|
||||
on:click={closePulldown}
|
||||
transition:fade={{duration:200}}
|
||||
/>
|
||||
<script context="module">
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
// can't find a better way to do this
|
||||
import Files from "./pulldowns/Files.svelte";
|
||||
import Accounts from "./pulldowns/Accounts.svelte";
|
||||
import Help from "./pulldowns/Help.svelte";
|
||||
|
||||
export let allPulldowns = new Map()
|
||||
|
||||
allPulldowns
|
||||
.set("account",Accounts)
|
||||
.set("help",Help)
|
||||
.set("files",Files)
|
||||
|
||||
export const pulldownOpen = writable(false);
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { fade, scale } from "svelte/transition";
|
||||
|
||||
export function isOpen() {
|
||||
return $pulldownOpen
|
||||
}
|
||||
|
||||
export function openPulldown(name) {
|
||||
pulldownOpen.set(name)
|
||||
}
|
||||
|
||||
export function closePulldown() {
|
||||
pulldownOpen.set(false)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
||||
})
|
||||
</script>
|
||||
{#if $pulldownOpen}
|
||||
<div class="pulldown" transition:fade={{duration:200}}>
|
||||
<svelte:component this={allPulldowns.get($pulldownOpen)} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="overlay"
|
||||
on:click={closePulldown}
|
||||
transition:fade={{duration:200}}
|
||||
/>
|
||||
{/if}
|
|
@ -1,30 +1,31 @@
|
|||
<script>
|
||||
import { circOut } from "svelte/easing";
|
||||
import { scale } from "svelte/transition";
|
||||
import PulldownManager, {pulldownOpen} from "./PulldownManager.svelte";
|
||||
import { _void } from "./transition/_void";
|
||||
|
||||
/**
|
||||
* @type PulldownManager
|
||||
*/
|
||||
export let pulldown;
|
||||
</script>
|
||||
|
||||
<div id="topbar">
|
||||
{#if $pulldownOpen}
|
||||
<button
|
||||
class="menuBtn"
|
||||
on:click={pulldown.closePulldown}
|
||||
transition:_void={{duration:200,prop:"width",easingFunc:circOut}}
|
||||
>close</button>
|
||||
{/if}
|
||||
|
||||
<!-- too lazy to make this better -->
|
||||
|
||||
<button class="menuBtn" on:click={() => pulldown.openPulldown("files")}>files</button>
|
||||
<button class="menuBtn" on:click={() => pulldown.openPulldown("account")}>account</button>
|
||||
<button class="menuBtn" on:click={() => pulldown.openPulldown("help")}>help</button>
|
||||
|
||||
<div /> <!-- not sure what's offcenter but something is
|
||||
so this div is here to ""fix"" that -->
|
||||
<script>
|
||||
import { circOut } from "svelte/easing";
|
||||
import { scale } from "svelte/transition";
|
||||
import PulldownManager, {pulldownOpen} from "./PulldownManager.svelte";
|
||||
import { account } from "./stores.mjs";
|
||||
import { _void } from "./transition/_void";
|
||||
|
||||
/**
|
||||
* @type PulldownManager
|
||||
*/
|
||||
export let pulldown;
|
||||
</script>
|
||||
|
||||
<div id="topbar">
|
||||
{#if $pulldownOpen}
|
||||
<button
|
||||
class="menuBtn"
|
||||
on:click={pulldown.closePulldown}
|
||||
transition:_void={{duration:200,prop:"width",easingFunc:circOut}}
|
||||
>close</button>
|
||||
{/if}
|
||||
|
||||
<!-- too lazy to make this better -->
|
||||
|
||||
<button class="menuBtn" on:click={() => pulldown.openPulldown("files")}>files</button>
|
||||
<button class="menuBtn" on:click={() => pulldown.openPulldown("account")}>{$account.username ? `@${$account.username}` : "account"}</button>
|
||||
<button class="menuBtn" on:click={() => pulldown.openPulldown("help")}>help</button>
|
||||
|
||||
<div /> <!-- not sure what's offcenter but something is
|
||||
so this div is here to ""fix"" that -->
|
||||
</div>
|
|
@ -1,217 +1,222 @@
|
|||
<script>
|
||||
import { _void } from "./transition/_void.js";
|
||||
import { padding_scaleY } from "./transition/padding_scaleY.js"
|
||||
import { fade } from "svelte/transition";
|
||||
import { circIn, circOut } from "svelte/easing";
|
||||
|
||||
import AttachmentZone from "./uploader/AttachmentZone.svelte";
|
||||
|
||||
// stats
|
||||
|
||||
let ServerStats = {}
|
||||
|
||||
let refresh_stats = () => {
|
||||
fetch("/server").then(async (data) => {
|
||||
ServerStats = await data.json()
|
||||
})
|
||||
}
|
||||
|
||||
refresh_stats()
|
||||
|
||||
// uploads
|
||||
|
||||
let attachmentZone;
|
||||
let uploads = {};
|
||||
let uploadInProgress = false;
|
||||
|
||||
let handle_file_upload = (ev) => {
|
||||
if (ev.detail.type == "clone") {
|
||||
uploads[Math.random().toString().slice(2)] = {
|
||||
type: "clone",
|
||||
name: ev.detail.url,
|
||||
url: ev.detail.url,
|
||||
|
||||
params: {
|
||||
uploadId: ""
|
||||
},
|
||||
|
||||
uploadStatus:{
|
||||
fileId: null,
|
||||
error: null,
|
||||
}
|
||||
}
|
||||
|
||||
uploads = uploads
|
||||
} else if (ev.detail.type == "upload") {
|
||||
ev.detail.files.forEach((v,x) => {
|
||||
uploads[Math.random().toString().slice(2)] = {
|
||||
type: "upload",
|
||||
name: v.name,
|
||||
file: v,
|
||||
|
||||
params: {
|
||||
uploadId: ""
|
||||
},
|
||||
|
||||
uploadStatus:{
|
||||
fileId: null,
|
||||
error: null,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
uploads = uploads
|
||||
}
|
||||
}
|
||||
|
||||
let handle_fetch_promise = (x,prom) => {
|
||||
return prom.then(async (res) => {
|
||||
let txt = await res.text()
|
||||
if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt;
|
||||
else {
|
||||
uploads[x].uploadStatus.fileId = txt;
|
||||
|
||||
refresh_stats();
|
||||
}
|
||||
}).catch((err) => {
|
||||
uploads[x].uploadStatus.error = err.toString();
|
||||
})
|
||||
}
|
||||
|
||||
let upload_files = () => {
|
||||
uploadInProgress = true
|
||||
|
||||
// go through all files
|
||||
Object.entries(uploads).forEach(([x,v]) => {
|
||||
switch(v.type) {
|
||||
case "upload":
|
||||
let fd = new FormData()
|
||||
fd.append("file",v.file)
|
||||
|
||||
handle_fetch_promise(x,fetch("/upload",{
|
||||
headers: {
|
||||
"monofile-params": JSON.stringify(v.params)
|
||||
},
|
||||
method: "POST",
|
||||
body: fd
|
||||
}))
|
||||
break
|
||||
case "clone":
|
||||
handle_fetch_promise(x,fetch("/clone",{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
url: v.url,
|
||||
...v.params
|
||||
})
|
||||
}))
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// animation
|
||||
|
||||
function fileTransition(node) {
|
||||
return {
|
||||
duration: 300,
|
||||
css: t => {
|
||||
let eased = circOut(t)
|
||||
|
||||
return `
|
||||
height: ${eased*(node.offsetHeight-20)}px;
|
||||
padding: ${eased*10}px 10px;
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div id="uploadWindow">
|
||||
<h1>monofile</h1>
|
||||
<p style:color="#999999">
|
||||
<span class="number">{ServerStats.version ? `v${ServerStats.version}` : "•••"}</span>  — Discord based file sharing
|
||||
</p>
|
||||
|
||||
<div style:min-height="10px" />
|
||||
|
||||
<!-- consider splitting the file thing into a separate element maybe -->
|
||||
|
||||
<div class="uploadContainer">
|
||||
{#each Object.entries(uploads) as upload (upload[0])}
|
||||
<!-- container to allow for animate directive -->
|
||||
<div>
|
||||
<div class="file" transition:fileTransition style:border={upload[1].uploadStatus.error ? "1px solid #BB7070" : ""}>
|
||||
<h2>{upload[1].name} <span style:color="#999999" style:font-weight="400">{upload[1].type}{@html upload[1].type == "upload" ? ` (${Math.round(upload[1].file.size/1048576)}MB)` : ""}</span></h2>
|
||||
|
||||
{#if upload[1].maximized && !uploadInProgress}
|
||||
<div transition:padding_scaleY|local>
|
||||
<div style:height="10px" />
|
||||
<input placeholder="custom id" type="text" bind:value={ uploads[upload[0]].params.uploadId }>
|
||||
<div style:height="10px" />
|
||||
<div class="buttonContainer">
|
||||
<button on:click={() => {delete uploads[upload[0]];uploads=uploads;}}>
|
||||
delete
|
||||
</button>
|
||||
<button on:click={() => uploads[upload[0]].maximized = false}>
|
||||
minimize
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else if !uploadInProgress}
|
||||
<button on:click={() => uploads[upload[0]].maximized = true} class="hitbox"></button>
|
||||
{:else}
|
||||
<div transition:padding_scaleY|local class="uploadingContainer">
|
||||
{#if !upload[1].uploadStatus.fileId}
|
||||
<p in:fade={{duration:300, delay:400, easingFunc:circOut}} out:padding_scaleY={{easingFunc:circIn,op:true}}>{upload[1].uploadStatus.error ?? "Uploading..."}</p>
|
||||
{/if}
|
||||
|
||||
{#if upload[1].uploadStatus.fileId}
|
||||
<div style:height="10px" transition:padding_scaleY />
|
||||
{#if !upload[1].viewingUrl}
|
||||
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<button on:click={() => uploads[upload[0]].viewingUrl = true}>
|
||||
view url
|
||||
</button>
|
||||
<button on:click={() => navigator.clipboard.writeText(`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`)}>
|
||||
copy url
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<input type="text" readonly value={`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`} style:flex-basis="80%">
|
||||
<button on:click={() => uploads[upload[0]].viewingUrl = false} style:flex-basis="20%">
|
||||
ok
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div style:height="10px" transition:padding_scaleY />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if uploadInProgress == false}
|
||||
<AttachmentZone bind:this={attachmentZone} on:addFiles={handle_file_upload}/>
|
||||
<div style:min-height="10px" transition:padding_scaleY />
|
||||
{#if Object.keys(uploads).length > 0}
|
||||
<button in:padding_scaleY={{easingFunc:circOut}} out:_void on:click={upload_files}>upload</button>
|
||||
<div transition:_void style:min-height="10px" />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<p style:color="#999999" style:text-align="center">
|
||||
Hosting <span class="number" style:font-weight="600">{ServerStats.files || "•••"}</span> files
|
||||
—
|
||||
Maximum filesize is <span class="number" style:font-weight="600">{((ServerStats.maxDiscordFileSize || 0)*(ServerStats.maxDiscordFiles || 0))/1048576 || "•••"}MB</span>
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<p style:color="#999999" style:text-align="center" style:font-size="12px">
|
||||
Made with {Math.floor(Math.random()*10)==0 ? "🐟" : "❤"} by <a href="https://github.com/nbitzz" style:font-size="12px">@nbitzz</a> — <a href="https://github.com/nbitzz/monofile" style:font-size="12px">source</a>
|
||||
</p>
|
||||
<div style:height="10px" />
|
||||
<script>
|
||||
import { _void } from "./transition/_void.js";
|
||||
import { padding_scaleY } from "./transition/padding_scaleY.js"
|
||||
import { fade } from "svelte/transition";
|
||||
import { circIn, circOut } from "svelte/easing";
|
||||
import { serverStats, refresh_stats, account } from "./stores.mjs";
|
||||
|
||||
import AttachmentZone from "./uploader/AttachmentZone.svelte";
|
||||
|
||||
// stats
|
||||
|
||||
refresh_stats()
|
||||
|
||||
// uploads
|
||||
|
||||
let attachmentZone;
|
||||
let uploads = {};
|
||||
let uploadInProgress = false;
|
||||
|
||||
let handle_file_upload = (ev) => {
|
||||
if (ev.detail.type == "clone") {
|
||||
uploads[Math.random().toString().slice(2)] = {
|
||||
type: "clone",
|
||||
name: ev.detail.url,
|
||||
url: ev.detail.url,
|
||||
|
||||
params: {
|
||||
uploadId: ""
|
||||
},
|
||||
|
||||
uploadStatus:{
|
||||
fileId: null,
|
||||
error: null,
|
||||
}
|
||||
}
|
||||
|
||||
uploads = uploads
|
||||
} else if (ev.detail.type == "upload") {
|
||||
ev.detail.files.forEach((v,x) => {
|
||||
uploads[Math.random().toString().slice(2)] = {
|
||||
type: "upload",
|
||||
name: v.name,
|
||||
file: v,
|
||||
|
||||
params: {
|
||||
uploadId: ""
|
||||
},
|
||||
|
||||
uploadStatus:{
|
||||
fileId: null,
|
||||
error: null,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
uploads = uploads
|
||||
}
|
||||
}
|
||||
|
||||
let handle_fetch_promise = (x,prom) => {
|
||||
return prom.then(async (res) => {
|
||||
let txt = await res.text()
|
||||
if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt;
|
||||
else {
|
||||
uploads[x].uploadStatus.fileId = txt;
|
||||
|
||||
refresh_stats();
|
||||
}
|
||||
}).catch((err) => {
|
||||
uploads[x].uploadStatus.error = err.toString();
|
||||
})
|
||||
}
|
||||
|
||||
let upload_files = () => {
|
||||
uploadInProgress = true
|
||||
|
||||
// go through all files
|
||||
Object.entries(uploads).forEach(([x,v]) => {
|
||||
switch(v.type) {
|
||||
case "upload":
|
||||
let fd = new FormData()
|
||||
fd.append("file",v.file)
|
||||
|
||||
handle_fetch_promise(x,fetch("/upload",{
|
||||
headers: {
|
||||
"monofile-params": JSON.stringify(v.params)
|
||||
},
|
||||
method: "POST",
|
||||
body: fd
|
||||
}))
|
||||
break
|
||||
case "clone":
|
||||
handle_fetch_promise(x,fetch("/clone",{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
url: v.url,
|
||||
...v.params
|
||||
})
|
||||
}))
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// animation
|
||||
|
||||
function fileTransition(node) {
|
||||
return {
|
||||
duration: 300,
|
||||
css: t => {
|
||||
let eased = circOut(t)
|
||||
|
||||
return `
|
||||
height: ${eased*(node.offsetHeight-22)}px;
|
||||
padding: ${eased*10}px 10px;
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div id="uploadWindow">
|
||||
<h1>monofile</h1>
|
||||
<p style:color="#999999">
|
||||
<span class="number">{$serverStats.version ? `v${$serverStats.version}` : "•••"}</span>  — Discord based file sharing
|
||||
</p>
|
||||
|
||||
<div style:min-height="10px" />
|
||||
|
||||
<!-- consider splitting the file thing into a separate element maybe -->
|
||||
|
||||
<div class="uploadContainer">
|
||||
{#each Object.entries(uploads) as upload (upload[0])}
|
||||
<!-- container to allow for animate directive -->
|
||||
<div>
|
||||
<div class="file" transition:fileTransition style:border={upload[1].uploadStatus.error ? "1px solid #BB7070" : ""}>
|
||||
<h2>{upload[1].name} <span style:color="#999999" style:font-weight="400">{upload[1].type}{@html upload[1].type == "upload" ? ` (${Math.round(upload[1].file.size/1048576)}MB)` : ""}</span></h2>
|
||||
|
||||
{#if upload[1].maximized && !uploadInProgress}
|
||||
<div transition:padding_scaleY|local>
|
||||
<div style:height="10px" />
|
||||
<input placeholder="custom id" type="text" bind:value={ uploads[upload[0]].params.uploadId }>
|
||||
<div style:height="10px" />
|
||||
<div class="buttonContainer">
|
||||
<button on:click={() => {delete uploads[upload[0]];uploads=uploads;}}>
|
||||
delete
|
||||
</button>
|
||||
<button on:click={() => uploads[upload[0]].maximized = false}>
|
||||
minimize
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else if !uploadInProgress}
|
||||
<button on:click={() => uploads[upload[0]].maximized = true} class="hitbox"></button>
|
||||
{:else}
|
||||
<div transition:padding_scaleY|local class="uploadingContainer">
|
||||
{#if !upload[1].uploadStatus.fileId}
|
||||
<p in:fade={{duration:300, delay:400, easingFunc:circOut}} out:padding_scaleY={{easingFunc:circIn,op:true}}>{upload[1].uploadStatus.error ?? "Uploading..."}</p>
|
||||
{/if}
|
||||
|
||||
{#if upload[1].uploadStatus.fileId}
|
||||
<div style:height="10px" transition:padding_scaleY />
|
||||
{#if !upload[1].viewingUrl}
|
||||
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<button on:click={() => uploads[upload[0]].viewingUrl = true}>
|
||||
view url
|
||||
</button>
|
||||
<button on:click={() => navigator.clipboard.writeText(`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`)}>
|
||||
copy url
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<input type="text" readonly value={`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`} style:flex-basis="80%">
|
||||
<button on:click={() => uploads[upload[0]].viewingUrl = false} style:flex-basis="20%">
|
||||
ok
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div style:height="10px" transition:padding_scaleY />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if uploadInProgress == false}
|
||||
|
||||
<!-- if required for upload, check if logged in -->
|
||||
{#if ($serverStats.accounts||{}).requiredForUpload ? !!$account.username : true}
|
||||
|
||||
<AttachmentZone bind:this={attachmentZone} on:addFiles={handle_file_upload}/>
|
||||
<div style:min-height="10px" transition:_void={{rTarg:"height",prop:"min-height"}} />
|
||||
{#if Object.keys(uploads).length > 0}
|
||||
<button in:padding_scaleY={{easingFunc:circOut}} out:_void on:click={upload_files}>upload</button>
|
||||
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" />
|
||||
{/if}
|
||||
|
||||
{:else}
|
||||
|
||||
<p transition:_void style:color="#999999" style:text-align="center">Please log in to upload files.</p>
|
||||
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" />
|
||||
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
|
||||
<p style:color="#999999" style:text-align="center">
|
||||
Hosting <span class="number" style:font-weight="600">{$serverStats.files || "•••"}</span> files
|
||||
—
|
||||
Maximum filesize is <span class="number" style:font-weight="600">{(($serverStats.maxDiscordFileSize || 0)*($serverStats.maxDiscordFiles || 0))/1048576 || "•••"}MB</span>
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<p style:color="#999999" style:text-align="center" style:font-size="12px">
|
||||
Made with {Math.floor(Math.random()*10)==0 ? "🐟" : "❤"} by <a href="https://github.com/nbitzz" style:font-size="12px">@nbitzz</a> — <a href="https://github.com/nbitzz/monofile" style:font-size="12px">source</a>
|
||||
</p>
|
||||
<div style:height="10px" />
|
||||
</div>
|
|
@ -1,33 +1,133 @@
|
|||
<script>
|
||||
import Pulldown from "./Pulldown.svelte"
|
||||
import { padding_scaleY } from "../transition/padding_scaleY"
|
||||
import { circIn,circOut } from "svelte/easing"
|
||||
|
||||
let targetAction
|
||||
</script>
|
||||
|
||||
<Pulldown name="accounts">
|
||||
<div class="notLoggedIn">
|
||||
<div class="container_div">
|
||||
<h1>monofile <span style:color="#999999">accounts</span></h1>
|
||||
<p class="flavor">Gain control of your uploads.</p>
|
||||
|
||||
{#if targetAction}
|
||||
|
||||
<div class="fields" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
|
||||
<input placeholder="username" type="text">
|
||||
<input placeholder="password" type="password">
|
||||
<button>{targetAction=="login" ? "Log in" : "Create account"}</button>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
|
||||
<div class="lgBtnContainer" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
|
||||
<button on:click={() => targetAction="login"}>Log in</button>
|
||||
<button on:click={() => targetAction="create"}>Sign up</button>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
import Pulldown from "./Pulldown.svelte"
|
||||
import { padding_scaleY } from "../transition/padding_scaleY"
|
||||
import { circIn,circOut } from "svelte/easing"
|
||||
import { account, fetchAccountData, serverStats } from "../stores.mjs";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
let targetAction
|
||||
let inProgress
|
||||
let authError
|
||||
|
||||
let pwErr
|
||||
|
||||
// lazy
|
||||
|
||||
let username
|
||||
let password
|
||||
|
||||
let execute = () => {
|
||||
if (inProgress) return
|
||||
|
||||
inProgress = true
|
||||
|
||||
fetch(`/auth/${targetAction}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
username, password
|
||||
})
|
||||
}).then(async (res) => {
|
||||
inProgress = false
|
||||
|
||||
if (res.status != 200) {
|
||||
authError = await res.json().catch(() => {
|
||||
return {
|
||||
status: res.status,
|
||||
message: res.statusText
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fetchAccountData();
|
||||
|
||||
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
$: {
|
||||
if (pwErr && authError) {
|
||||
pwErr.animate({
|
||||
backgroundColor: ["#885555","#663333"],
|
||||
easing: "ease-out"
|
||||
},650)
|
||||
}
|
||||
}
|
||||
|
||||
// actual account menu
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<Pulldown name="accounts">
|
||||
{#if Object.keys($account).length == 0}
|
||||
|
||||
<div class="notLoggedIn" transition:fade={{duration:200}}>
|
||||
<div class="container_div">
|
||||
<h1>monofile <span style:color="#999999">accounts</span></h1>
|
||||
<p class="flavor">Gain control of your uploads.</p>
|
||||
|
||||
{#if targetAction}
|
||||
|
||||
<div class="fields" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
|
||||
{#if !$serverStats.accounts.registrationEnabled && targetAction == "create"}
|
||||
<div class="pwError">
|
||||
<div style:background-color="#554C33">
|
||||
<p>Account registration has been disabled by this instance's owner</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if authError}
|
||||
<div class="pwError" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
|
||||
<div bind:this={pwErr}>
|
||||
<p><strong>{authError.status}</strong> {authError.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<input placeholder="username" type="text" bind:value={username}>
|
||||
<input placeholder="password" type="password" bind:value={password}>
|
||||
<button on:click={execute}>{ inProgress ? "• • •" : (targetAction=="login" ? "Log in" : "Create account") }</button>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
|
||||
<div class="lgBtnContainer" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
|
||||
<button on:click={() => targetAction="login"}>Log in</button>
|
||||
<button on:click={() => targetAction="create"}>Sign up</button>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
|
||||
<div class="loggedIn" transition:fade={{duration:200}}>
|
||||
<h1>
|
||||
Hey there, <span class="monospace" style:font-size="18px">@{$account.username}</span>
|
||||
</h1>
|
||||
|
||||
<div style:min-height="10px" style:border-bottom="1px solid #AAAAAA" />
|
||||
|
||||
<div class="accountOptions">
|
||||
<button>
|
||||
<img src="/static/assets/icons/change_password.svg" alt="change password">
|
||||
<p>Change password</p>
|
||||
</button>
|
||||
|
||||
<button>
|
||||
<img src="/static/assets/icons/delete_account.svg" alt="delete account">
|
||||
<p>Delete account</p>
|
||||
</button>
|
||||
|
||||
<button on:click={() => fetch(`/auth/logout`,{method:"POST"}).then(() => fetchAccountData())}>
|
||||
<img src="/static/assets/icons/logout.svg" alt="logout">
|
||||
<p>Log out</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
</Pulldown>
|
|
@ -1,31 +1,31 @@
|
|||
<script>
|
||||
import Pulldown from "./Pulldown.svelte"
|
||||
import { pulldownManager } from "../stores.mjs";
|
||||
</script>
|
||||
|
||||
<Pulldown name="files">
|
||||
|
||||
<div class="notLoggedIn">
|
||||
<div style:height="2px" style:background-color="#66AAFF" />
|
||||
<div style:height="10px" />
|
||||
<p class="flavor">Log in to view uploads & collections</p>
|
||||
<button on:click={$pulldownManager.openPulldown("account")}>OK</button>
|
||||
<div style:height="14px" />
|
||||
</div>
|
||||
|
||||
<!--
|
||||
put scrolling div containing options here
|
||||
if not logged in, most options will be hidden
|
||||
& the div containing the options will be resized
|
||||
(actually, maybe we could use flexbox for this)
|
||||
-->
|
||||
|
||||
<!--
|
||||
<div>
|
||||
<h2>Anonymous file deletion</h2>
|
||||
<p>Enter your deletion code</p>
|
||||
<input placeholder="0000 0000 0000 0000">
|
||||
</div>
|
||||
-->
|
||||
|
||||
<script>
|
||||
import Pulldown from "./Pulldown.svelte"
|
||||
import { pulldownManager } from "../stores.mjs";
|
||||
</script>
|
||||
|
||||
<Pulldown name="files">
|
||||
|
||||
<div class="notLoggedIn">
|
||||
<div style:height="2px" style:background-color="#66AAFF" />
|
||||
<div style:height="10px" />
|
||||
<p class="flavor">Log in to view uploads & collections</p>
|
||||
<button on:click={$pulldownManager.openPulldown("account")}>OK</button>
|
||||
<div style:height="14px" />
|
||||
</div>
|
||||
|
||||
<!--
|
||||
put scrolling div containing options here
|
||||
if not logged in, most options will be hidden
|
||||
& the div containing the options will be resized
|
||||
(actually, maybe we could use flexbox for this)
|
||||
-->
|
||||
|
||||
<!--
|
||||
<div>
|
||||
<h2>Anonymous file deletion</h2>
|
||||
<p>Enter your deletion code</p>
|
||||
<input placeholder="0000 0000 0000 0000">
|
||||
</div>
|
||||
-->
|
||||
|
||||
</Pulldown>
|
|
@ -1,25 +1,25 @@
|
|||
<!-- i'm lazy, could probably just use plain html here but hwatever, mgiht make this grab from the server idk -->
|
||||
|
||||
<script>
|
||||
import Pulldown from "./Pulldown.svelte"
|
||||
|
||||
let faq = [
|
||||
{
|
||||
question : "Are my files compressed on upload?",
|
||||
answer : "No. Files should stay completely unchanged on download."
|
||||
},
|
||||
{
|
||||
question : "How do I replace a file that I have previously uploaded?",
|
||||
answer : "You can modify the content of a file that is linked to a file ID by reuploading the file using the same custom ID."
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<Pulldown name="help">
|
||||
{#each faq as question}
|
||||
<div class="faqGroup">
|
||||
<h2>{question.question}</h2>
|
||||
<p>{question.answer}</p>
|
||||
</div>
|
||||
{/each}
|
||||
<!-- i'm lazy, could probably just use plain html here but hwatever, mgiht make this grab from the server idk -->
|
||||
|
||||
<script>
|
||||
import Pulldown from "./Pulldown.svelte"
|
||||
|
||||
let faq = [
|
||||
{
|
||||
question : "Are my files compressed on upload?",
|
||||
answer : "No. Files should stay completely unchanged on download."
|
||||
},
|
||||
{
|
||||
question : "How do I replace a file that I have previously uploaded?",
|
||||
answer : "You can modify the content of a file that is linked to a file ID by reuploading the file using the same custom ID."
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<Pulldown name="help">
|
||||
{#each faq as question}
|
||||
<div class="faqGroup">
|
||||
<h2>{question.question}</h2>
|
||||
<p>{question.answer}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</Pulldown>
|
|
@ -1,14 +1,14 @@
|
|||
<script>
|
||||
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
export let name;
|
||||
|
||||
</script>
|
||||
<div
|
||||
class="pulldown_display"
|
||||
name={name}
|
||||
transition:fade={{duration:150}}
|
||||
>
|
||||
<slot />
|
||||
<script>
|
||||
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
export let name;
|
||||
|
||||
</script>
|
||||
<div
|
||||
class="pulldown_display"
|
||||
name={name}
|
||||
transition:fade={{duration:150}}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
|
@ -1,3 +1,23 @@
|
|||
import { writable } from "svelte/store"
|
||||
|
||||
export let pulldownManager = writable(0)
|
||||
import { writable } from "svelte/store"
|
||||
|
||||
export let pulldownManager = writable(0)
|
||||
export let account = writable({})
|
||||
export let serverStats = writable({})
|
||||
|
||||
export let fetchAccountData = function() {
|
||||
fetch("/auth/me").then(async (response) => {
|
||||
if (response.status == 200) {
|
||||
account.set(await response.json())
|
||||
} else {
|
||||
account.set({})
|
||||
}
|
||||
}).catch((err) => { console.error(err) })
|
||||
}
|
||||
|
||||
export let refresh_stats = () => {
|
||||
fetch("/server").then(async (data) => {
|
||||
serverStats.set(await data.json())
|
||||
}).catch((err) => { console.error(err) })
|
||||
}
|
||||
|
||||
fetchAccountData()
|
|
@ -1,20 +1,20 @@
|
|||
import { circIn, circOut } from "svelte/easing"
|
||||
|
||||
export function _void(node, { duration, easingFunc, op, prop }) {
|
||||
let rect = node.getBoundingClientRect()
|
||||
|
||||
return {
|
||||
duration: duration||300,
|
||||
css: t => {
|
||||
let eased = (easingFunc || circIn)(t)
|
||||
|
||||
return `
|
||||
white-space: nowrap;
|
||||
${prop||"height"}: ${(eased)*(rect[prop||"height"])}px;
|
||||
padding: 0px;
|
||||
opacity:${eased};
|
||||
overflow: clip;
|
||||
`
|
||||
}
|
||||
}
|
||||
import { circIn, circOut } from "svelte/easing"
|
||||
|
||||
export function _void(node, { duration, easingFunc, op, prop, rTarg }) {
|
||||
let rect = node.getBoundingClientRect()
|
||||
|
||||
return {
|
||||
duration: duration||300,
|
||||
css: t => {
|
||||
let eased = (easingFunc || circIn)(t)
|
||||
|
||||
return `
|
||||
white-space: nowrap;
|
||||
${prop||"height"}: ${(eased)*(rect[rTarg||prop||"height"])}px;
|
||||
padding: 0px;
|
||||
opacity:${eased};
|
||||
overflow: clip;
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
import { circIn, circOut } from "svelte/easing"
|
||||
|
||||
export function padding_scaleY(node, { duration, easingFunc, padY, padX, op }) {
|
||||
let rect = node.getBoundingClientRect()
|
||||
|
||||
return {
|
||||
duration: duration||300,
|
||||
css: t => {
|
||||
let eased = (easingFunc || circOut)(t)
|
||||
|
||||
return `
|
||||
height: ${eased*(rect.height-(padY||0))}px;
|
||||
${padX&&padY ? `padding: ${(eased)*(padY)}px ${(padX)}px;` : ""}
|
||||
${op ? `opacity: ${eased};` : ""}
|
||||
`
|
||||
}
|
||||
}
|
||||
import { circIn, circOut } from "svelte/easing"
|
||||
|
||||
export function padding_scaleY(node, { duration, easingFunc, padY, padX, op }) {
|
||||
let rect = node.getBoundingClientRect()
|
||||
|
||||
return {
|
||||
duration: duration||300,
|
||||
css: t => {
|
||||
let eased = (easingFunc || circOut)(t)
|
||||
|
||||
return `
|
||||
height: ${eased*(rect.height-(padY||0))}px;
|
||||
${padX&&padY ? `padding: ${(eased)*(padY)}px ${(padX)}px;` : ""}
|
||||
${op ? `opacity: ${eased};` : ""}
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,93 +1,93 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { circIn, circOut } from "svelte/easing"
|
||||
import { fade } from "svelte/transition";
|
||||
import { _void } from "../transition/_void"
|
||||
|
||||
let uploadTypes = {
|
||||
files: 1,
|
||||
clone: 2
|
||||
}
|
||||
|
||||
let uploadType = undefined
|
||||
let dispatch = createEventDispatcher();
|
||||
|
||||
// file upload
|
||||
|
||||
/**
|
||||
* @type HTMLInputElement
|
||||
*/
|
||||
let fileUpload;
|
||||
|
||||
$: {
|
||||
if (fileUpload) {
|
||||
fileUpload.addEventListener("change",() => {
|
||||
dispatch("addFiles",{
|
||||
type: "upload",
|
||||
files: Array.from(fileUpload.files)
|
||||
})
|
||||
uploadType = undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// file clone
|
||||
/**
|
||||
* @type HTMLButtonElement
|
||||
*/
|
||||
let cloneButton;
|
||||
|
||||
/**
|
||||
* @type HTMLInputElement
|
||||
*/
|
||||
let cloneUrlTextbox;
|
||||
|
||||
$: {
|
||||
if (cloneButton && cloneUrlTextbox) {
|
||||
cloneButton.addEventListener("click",() => {
|
||||
if (cloneUrlTextbox.value) {
|
||||
dispatch("addFiles",{
|
||||
type: "clone",
|
||||
url: cloneUrlTextbox.value
|
||||
})
|
||||
uploadType = undefined;
|
||||
} else {
|
||||
cloneUrlTextbox.animate([
|
||||
{"transform":"translateX(0px)"},
|
||||
{"transform":"translateX(-3px)"},
|
||||
{"transform":"translateX(3px)"},
|
||||
{"transform":"translateX(0px)"}
|
||||
],100)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- there are 100% better ways to do this but idgaf, it's still easier to manage than <1.3 lmao -->
|
||||
|
||||
<div id="add_new_files" transition:_void={{duration:200}}>
|
||||
<p>
|
||||
+<span class="_add_files_txt">add files</span>
|
||||
</p>
|
||||
{#if !uploadType}
|
||||
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<button on:click={() => uploadType = uploadTypes.files} >upload files...</button>
|
||||
<button on:click={() => uploadType = uploadTypes.clone} >clone url...</button>
|
||||
</div>
|
||||
{:else}
|
||||
{#if uploadType == uploadTypes.files}
|
||||
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<div class="fileUpload">
|
||||
<p>click/tap to browse<br/>or drag files into this box</p>
|
||||
<input type="file" multiple bind:this={fileUpload}>
|
||||
</div>
|
||||
</div>
|
||||
{:else if uploadType == uploadTypes.clone}
|
||||
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<input placeholder="url" type="text" bind:this={cloneUrlTextbox}>
|
||||
<button style:flex-basis="30%" bind:this={cloneButton}>add file</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { circIn, circOut } from "svelte/easing"
|
||||
import { fade } from "svelte/transition";
|
||||
import { _void } from "../transition/_void"
|
||||
|
||||
let uploadTypes = {
|
||||
files: 1,
|
||||
clone: 2
|
||||
}
|
||||
|
||||
let uploadType = undefined
|
||||
let dispatch = createEventDispatcher();
|
||||
|
||||
// file upload
|
||||
|
||||
/**
|
||||
* @type HTMLInputElement
|
||||
*/
|
||||
let fileUpload;
|
||||
|
||||
$: {
|
||||
if (fileUpload) {
|
||||
fileUpload.addEventListener("change",() => {
|
||||
dispatch("addFiles",{
|
||||
type: "upload",
|
||||
files: Array.from(fileUpload.files)
|
||||
})
|
||||
uploadType = undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// file clone
|
||||
/**
|
||||
* @type HTMLButtonElement
|
||||
*/
|
||||
let cloneButton;
|
||||
|
||||
/**
|
||||
* @type HTMLInputElement
|
||||
*/
|
||||
let cloneUrlTextbox;
|
||||
|
||||
$: {
|
||||
if (cloneButton && cloneUrlTextbox) {
|
||||
cloneButton.addEventListener("click",() => {
|
||||
if (cloneUrlTextbox.value) {
|
||||
dispatch("addFiles",{
|
||||
type: "clone",
|
||||
url: cloneUrlTextbox.value
|
||||
})
|
||||
uploadType = undefined;
|
||||
} else {
|
||||
cloneUrlTextbox.animate([
|
||||
{"transform":"translateX(0px)"},
|
||||
{"transform":"translateX(-3px)"},
|
||||
{"transform":"translateX(3px)"},
|
||||
{"transform":"translateX(0px)"}
|
||||
],100)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- there are 100% better ways to do this but idgaf, it's still easier to manage than <1.3 lmao -->
|
||||
|
||||
<div id="add_new_files" transition:_void={{duration:200}}>
|
||||
<p>
|
||||
+<span class="_add_files_txt">add files</span>
|
||||
</p>
|
||||
{#if !uploadType}
|
||||
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<button on:click={() => uploadType = uploadTypes.files} >upload files...</button>
|
||||
<button on:click={() => uploadType = uploadTypes.clone} >clone url...</button>
|
||||
</div>
|
||||
{:else}
|
||||
{#if uploadType == uploadTypes.files}
|
||||
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<div class="fileUpload">
|
||||
<p>click/tap to browse<br/>or drag files into this box</p>
|
||||
<input type="file" multiple bind:this={fileUpload}>
|
||||
</div>
|
||||
</div>
|
||||
{:else if uploadType == uploadTypes.clone}
|
||||
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<input placeholder="url" type="text" bind:this={cloneUrlTextbox}>
|
||||
<button style:flex-basis="30%" bind:this={cloneButton}>add file</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
208
tsconfig.json
208
tsconfig.json
|
@ -1,104 +1,104 @@
|
|||
{
|
||||
"include":["src/server/**/*"],
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./src/", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./out/server", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
{
|
||||
"include":["src/server/**/*"],
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./src/", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./out/server", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue