From 486795280debdee3164d96b1f92f387d23f8a997 Mon Sep 17 00:00:00 2001 From: Joep Date: Thu, 7 Sep 2023 21:57:57 +0200 Subject: [PATCH 01/24] Initial Javascript to Typescript convert --- frontend/index.html | 2 +- frontend/package-lock.json | 145 ++++-------------- frontend/package.json | 2 + frontend/src/App.vue | 20 +-- frontend/src/index.d.ts | 1 + frontend/src/{main.js => main.ts} | 12 +- frontend/src/router/{index.js => index.ts} | 0 frontend/src/stores/{auth.js => auth.ts} | 0 .../src/stores/{clipboard.js => clipboard.ts} | 2 +- frontend/src/stores/{file.js => file.ts} | 0 frontend/src/stores/{index.js => index.ts} | 0 frontend/src/stores/{layout.js => layout.ts} | 0 frontend/src/stores/{router.js => router.ts} | 0 frontend/src/stores/{upload.js => upload.ts} | 0 frontend/src/types/global.d.ts | 7 + frontend/src/utils/{auth.js => auth.ts} | 0 frontend/src/utils/{buttons.js => buttons.ts} | 0 .../src/utils/{constants.js => constants.ts} | 0 frontend/src/utils/{cookie.js => cookie.ts} | 0 frontend/src/utils/{css.js => css.ts} | 0 frontend/src/utils/{index.js => index.ts} | 0 frontend/src/utils/{upload.js => upload.ts} | 0 frontend/src/utils/{url.js => url.ts} | 0 frontend/src/utils/{vue.js => vue.cjs} | 3 +- frontend/tsconfig.json | 30 ++++ frontend/{vite.config.js => vite.config.ts} | 2 +- 26 files changed, 95 insertions(+), 131 deletions(-) create mode 100644 frontend/src/index.d.ts rename frontend/src/{main.js => main.ts} (87%) rename frontend/src/router/{index.js => index.ts} (100%) rename frontend/src/stores/{auth.js => auth.ts} (100%) rename frontend/src/stores/{clipboard.js => clipboard.ts} (94%) rename frontend/src/stores/{file.js => file.ts} (100%) rename frontend/src/stores/{index.js => index.ts} (100%) rename frontend/src/stores/{layout.js => layout.ts} (100%) rename frontend/src/stores/{router.js => router.ts} (100%) rename frontend/src/stores/{upload.js => upload.ts} (100%) create mode 100644 frontend/src/types/global.d.ts rename frontend/src/utils/{auth.js => auth.ts} (100%) rename frontend/src/utils/{buttons.js => buttons.ts} (100%) rename frontend/src/utils/{constants.js => constants.ts} (100%) rename frontend/src/utils/{cookie.js => cookie.ts} (100%) rename frontend/src/utils/{css.js => css.ts} (100%) rename frontend/src/utils/{index.js => index.ts} (100%) rename frontend/src/utils/{upload.js => upload.ts} (100%) rename frontend/src/utils/{url.js => url.ts} (100%) rename frontend/src/utils/{vue.js => vue.cjs} (94%) create mode 100644 frontend/tsconfig.json rename frontend/{vite.config.js => vite.config.ts} (98%) diff --git a/frontend/index.html b/frontend/index.html index 7d0b8bc9..02c303ae 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -187,6 +187,6 @@ - + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a3ff1096..c5a96b80 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -35,6 +35,7 @@ }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^0.12.3", + "@types/node": "^20.5.9", "@vitejs/plugin-legacy": "^4.1.1", "@vitejs/plugin-vue": "^4.3.3", "@vue/eslint-config-prettier": "^8.0.0", @@ -46,6 +47,7 @@ "postcss": "^8.4.28", "prettier": "^3.0.2", "terser": "^5.19.2", + "typescript": "^5.2.2", "vite": "^4.4.9", "vite-plugin-compression2": "^0.10.4", "vite-plugin-rewrite-all": "^1.0.1" @@ -2490,6 +2492,12 @@ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, + "node_modules/@types/node": { + "version": "20.5.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz", + "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==", + "dev": true + }, "node_modules/@types/web-bluetooth": { "version": "0.0.17", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz", @@ -3681,15 +3689,6 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -3799,108 +3798,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -3930,6 +3827,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -6049,6 +5959,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/ufo": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8b80f7f6..2ff04e29 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,6 +41,7 @@ }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^0.12.3", + "@types/node": "^20.5.9", "@vitejs/plugin-legacy": "^4.1.1", "@vitejs/plugin-vue": "^4.3.3", "@vue/eslint-config-prettier": "^8.0.0", @@ -52,6 +53,7 @@ "postcss": "^8.4.28", "prettier": "^3.0.2", "terser": "^5.19.2", + "typescript": "^5.2.2", "vite": "^4.4.9", "vite-plugin-compression2": "^0.10.4", "vite-plugin-rewrite-all": "^1.0.1" diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 2bec8332..41b76526 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -4,20 +4,20 @@ - - - + \ No newline at end of file diff --git a/frontend/src/index.d.ts b/frontend/src/index.d.ts new file mode 100644 index 00000000..df9f4ba8 --- /dev/null +++ b/frontend/src/index.d.ts @@ -0,0 +1 @@ +declare module '*.vue'; diff --git a/frontend/src/main.js b/frontend/src/main.ts similarity index 87% rename from frontend/src/main.js rename to frontend/src/main.ts index cc019092..995b47e7 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.ts @@ -1,13 +1,14 @@ import { disableExternal } from "@/utils/constants"; -import { createApp, configureCompat } from "vue"; +import { createApp } from "vue"; import Noty from "noty"; import VueLazyload from "vue-lazyload"; import createPinia from "@/stores"; import router from "@/router"; import i18n from "@/i18n"; import App from "@/App.vue"; +import '@/css/styles.css' -configureCompat({ RENDER_FUNCTION: false }); +// configureCompat({ RENDER_FUNCTION: false }); import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; @@ -43,7 +44,7 @@ app.directive("focus", { }, }); -const notyDefault = { +const notyDefault: Noty.Options = { type: "info", layout: "bottomCenter", timeout: 1000, @@ -54,7 +55,7 @@ const notyDefault = { // new Noty(Object.assign({}, notyDefault, opts)).show(); // }); -app.provide("$showSuccess", (message) => { +app.provide("$showSuccess", (message: any) => { new Noty( Object.assign({}, notyDefault, { text: message, @@ -63,8 +64,9 @@ app.provide("$showSuccess", (message) => { ).show(); }); -app.provide("$showError", (error, displayReport = true) => { +app.provide("$showError", (error: any, displayReport = true) => { let btns = [ + // @ts-ignore Noty.button(i18n.global.t("buttons.close"), "", function () { n.close(); }), diff --git a/frontend/src/router/index.js b/frontend/src/router/index.ts similarity index 100% rename from frontend/src/router/index.js rename to frontend/src/router/index.ts diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.ts similarity index 100% rename from frontend/src/stores/auth.js rename to frontend/src/stores/auth.ts diff --git a/frontend/src/stores/clipboard.js b/frontend/src/stores/clipboard.ts similarity index 94% rename from frontend/src/stores/clipboard.js rename to frontend/src/stores/clipboard.ts index 4b016d9b..23eb54b2 100644 --- a/frontend/src/stores/clipboard.js +++ b/frontend/src/stores/clipboard.ts @@ -12,7 +12,7 @@ export const useClipboardStore = defineStore("clipboard", { }, actions: { // no context as first argument, use `this` instead - updateClipboard(value) { + updateClipboard(value: any) { this.key = value.key; this.items = value.items; this.path = value.path; diff --git a/frontend/src/stores/file.js b/frontend/src/stores/file.ts similarity index 100% rename from frontend/src/stores/file.js rename to frontend/src/stores/file.ts diff --git a/frontend/src/stores/index.js b/frontend/src/stores/index.ts similarity index 100% rename from frontend/src/stores/index.js rename to frontend/src/stores/index.ts diff --git a/frontend/src/stores/layout.js b/frontend/src/stores/layout.ts similarity index 100% rename from frontend/src/stores/layout.js rename to frontend/src/stores/layout.ts diff --git a/frontend/src/stores/router.js b/frontend/src/stores/router.ts similarity index 100% rename from frontend/src/stores/router.js rename to frontend/src/stores/router.ts diff --git a/frontend/src/stores/upload.js b/frontend/src/stores/upload.ts similarity index 100% rename from frontend/src/stores/upload.js rename to frontend/src/stores/upload.ts diff --git a/frontend/src/types/global.d.ts b/frontend/src/types/global.d.ts new file mode 100644 index 00000000..8766bd75 --- /dev/null +++ b/frontend/src/types/global.d.ts @@ -0,0 +1,7 @@ +export {}; + +declare global { + interface Window { + FileBrowser: any; + } +} \ No newline at end of file diff --git a/frontend/src/utils/auth.js b/frontend/src/utils/auth.ts similarity index 100% rename from frontend/src/utils/auth.js rename to frontend/src/utils/auth.ts diff --git a/frontend/src/utils/buttons.js b/frontend/src/utils/buttons.ts similarity index 100% rename from frontend/src/utils/buttons.js rename to frontend/src/utils/buttons.ts diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.ts similarity index 100% rename from frontend/src/utils/constants.js rename to frontend/src/utils/constants.ts diff --git a/frontend/src/utils/cookie.js b/frontend/src/utils/cookie.ts similarity index 100% rename from frontend/src/utils/cookie.js rename to frontend/src/utils/cookie.ts diff --git a/frontend/src/utils/css.js b/frontend/src/utils/css.ts similarity index 100% rename from frontend/src/utils/css.js rename to frontend/src/utils/css.ts diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.ts similarity index 100% rename from frontend/src/utils/index.js rename to frontend/src/utils/index.ts diff --git a/frontend/src/utils/upload.js b/frontend/src/utils/upload.ts similarity index 100% rename from frontend/src/utils/upload.js rename to frontend/src/utils/upload.ts diff --git a/frontend/src/utils/url.js b/frontend/src/utils/url.ts similarity index 100% rename from frontend/src/utils/url.js rename to frontend/src/utils/url.ts diff --git a/frontend/src/utils/vue.js b/frontend/src/utils/vue.cjs similarity index 94% rename from frontend/src/utils/vue.js rename to frontend/src/utils/vue.cjs index 962a7f9d..d3f2d50b 100644 --- a/frontend/src/utils/vue.js +++ b/frontend/src/utils/vue.cjs @@ -1,12 +1,11 @@ import Vue from "vue"; import Noty from "noty"; import VueLazyload from "vue-lazyload"; +// @ts-ignore import i18n from "@/i18n"; import { disableExternal } from "@/utils/constants"; -import AsyncComputed from "vue-async-computed"; Vue.use(VueLazyload); -Vue.use(AsyncComputed); Vue.config.productionTip = true; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..f182e33c --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "allowJs": true, + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "outDir": "../dist/frontend", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "types": ["vite/client"], + "typeRoots": ["./node_modules/@types", "./some-custom-lib"], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "./**/*.ts", + "src/**/*.d.ts", + "src/**/*.vue", + ], + "exclude": ["node_modules", "dist"] +} diff --git a/frontend/vite.config.js b/frontend/vite.config.ts similarity index 98% rename from frontend/vite.config.js rename to frontend/vite.config.ts index fc366c6e..9bf9bd79 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.ts @@ -15,7 +15,7 @@ const plugins = [ }, }, }), - VueI18nPlugin(), + VueI18nPlugin({}), compression({ include: /\.js$/i, deleteOriginalAssets: true }), pluginRewriteAll(), // fixes 404 error with paths containing dot in dev server ]; From 0907429e9ac2ff6cba3e235a9cae79b8b92fbd48 Mon Sep 17 00:00:00 2001 From: Joep Date: Thu, 7 Sep 2023 21:58:50 +0200 Subject: [PATCH 02/24] Ignore default.nix file to debate if need to include --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5180999f..48661e9a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ build/ /frontend/dist/* !/frontend/dist/.gitkeep + +default.nix \ No newline at end of file From 5a8ca3a50d6e216f39b51319a72318c36d6cd786 Mon Sep 17 00:00:00 2001 From: Joep Date: Fri, 8 Sep 2023 00:12:37 +0200 Subject: [PATCH 03/24] Added and changed all of the js files to typescript files, fixed errors as well --- frontend/package-lock.json | 169 ++++++++++++++++++++++++++- frontend/package.json | 5 +- frontend/public/index.html | 2 +- frontend/src/api/commands.js | 18 --- frontend/src/api/files.js | 204 --------------------------------- frontend/src/api/index.js | 9 -- frontend/src/api/pub.js | 70 ----------- frontend/src/api/search.js | 27 ----- frontend/src/api/settings.js | 12 -- frontend/src/api/share.js | 40 ------- frontend/src/api/tus.js | 91 --------------- frontend/src/api/users.js | 41 ------- frontend/src/api/utils.js | 84 -------------- frontend/src/router/index.ts | 17 +-- frontend/src/stores/auth.ts | 15 ++- frontend/src/stores/file.ts | 13 ++- frontend/src/stores/index.ts | 3 +- frontend/src/stores/layout.ts | 10 +- frontend/src/stores/upload.ts | 29 +++-- frontend/src/types/global.d.ts | 1 + frontend/src/utils/auth.ts | 15 +-- frontend/src/utils/buttons.ts | 53 +++++---- frontend/src/utils/cookie.ts | 2 +- frontend/src/utils/css.ts | 2 +- frontend/src/utils/index.ts | 2 +- frontend/src/utils/upload.ts | 19 +-- frontend/src/utils/url.ts | 6 +- 27 files changed, 287 insertions(+), 672 deletions(-) delete mode 100644 frontend/src/api/commands.js delete mode 100644 frontend/src/api/files.js delete mode 100644 frontend/src/api/index.js delete mode 100644 frontend/src/api/pub.js delete mode 100644 frontend/src/api/search.js delete mode 100644 frontend/src/api/settings.js delete mode 100644 frontend/src/api/share.js delete mode 100644 frontend/src/api/tus.js delete mode 100644 frontend/src/api/users.js delete mode 100644 frontend/src/api/utils.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c5a96b80..2cfcbd94 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -50,7 +50,8 @@ "typescript": "^5.2.2", "vite": "^4.4.9", "vite-plugin-compression2": "^0.10.4", - "vite-plugin-rewrite-all": "^1.0.1" + "vite-plugin-rewrite-all": "^1.0.1", + "vue-tsc": "^1.8.10" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2541,6 +2542,33 @@ "vue": "^3.2.25" } }, + "node_modules/@volar/language-core": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.1.tgz", + "integrity": "sha512-JnsM1mIPdfGPxmoOcK1c7HYAsL6YOv0TCJ4aW3AXPZN/Jb4R77epDyMZIVudSGjWMbvv/JfUa+rQ+dGKTmgwBA==", + "dev": true, + "dependencies": { + "@volar/source-map": "1.10.1" + } + }, + "node_modules/@volar/source-map": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.1.tgz", + "integrity": "sha512-3/S6KQbqa7pGC8CxPrg69qHLpOvkiPHGJtWPkI/1AXCsktkJ6gIk/5z4hyuMp8Anvs6eS/Kvp/GZa3ut3votKA==", + "dev": true, + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.1.tgz", + "integrity": "sha512-+iiO9yUSRHIYjlteT+QcdRq8b44qH19/eiUZtjNtuh6D9ailYM7DVR0zO2sEgJlvCaunw/CF9Ov2KooQBpR4VQ==", + "dev": true, + "dependencies": { + "@volar/language-core": "1.10.1" + } + }, "node_modules/@vue/compat": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/compat/-/compat-3.3.4.tgz", @@ -2619,6 +2647,54 @@ "prettier": ">= 3.0.0" } }, + "node_modules/@vue/language-core": { + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.10.tgz", + "integrity": "sha512-db8PtM4ZZr7SYNH30XpKxUYnUBYaTvcuJ4c2whKK04fuAjbtjAIZ2al5GzGEfUlesmvkpgdbiSviRXUxgD9Omw==", + "dev": true, + "dependencies": { + "@volar/language-core": "~1.10.0", + "@volar/source-map": "~1.10.0", + "@vue/compiler-dom": "^3.3.0", + "@vue/reactivity": "^3.3.0", + "@vue/shared": "^3.3.0", + "minimatch": "^9.0.0", + "muggle-string": "^0.3.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vue/reactivity": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", @@ -2675,6 +2751,16 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" }, + "node_modules/@vue/typescript": { + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.10.tgz", + "integrity": "sha512-vPSpTXMk4chYwvyTGjM891cKgnx2r6vtbdANOp2mRU31f4HYGyLrZBlGgiua7SaO2cLjUg8y91OipJe0t8OFhA==", + "dev": true, + "dependencies": { + "@volar/typescript": "~1.10.0", + "@vue/language-core": "1.8.10" + } + }, "node_modules/@vueuse/core": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.4.1.tgz", @@ -3301,6 +3387,12 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4194,6 +4286,15 @@ "node": ">=4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -4858,6 +4959,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true + }, "node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -6321,6 +6428,66 @@ "resolved": "https://registry.npmjs.org/vue-simple-progress/-/vue-simple-progress-1.1.1.tgz", "integrity": "sha512-ltLWYBA5eVQHWyt1NwZeGeK0VQC69JVh1oqUdro0po7r8Hc8SEMEyEfuwyCO4s27h5I3jbD99BKKkyPSQZgoZA==" }, + "node_modules/vue-template-compiler": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", + "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.10.tgz", + "integrity": "sha512-ptpTFFDoHQgkWJF7i5iERxooiQzOGtG1uKTfmAUuS3qPuSQGq+Ky/S8BFHhnFGwoOxq/PjmGN2QSZEfg1rtzQA==", + "dev": true, + "dependencies": { + "@vue/language-core": "1.8.10", + "@vue/typescript": "1.8.10", + "semver": "^7.3.8" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/vue-tsc/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vue-tsc/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vue-tsc/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2ff04e29..262e3550 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "vite dev", "serve": "vite serve", - "build": "vite build", + "build": "vue-tsc -p ./tsconfig.json --noEmit && vite build --emptyOutDir", "watch": "vite build --watch", "clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +", "lint": "eslint --ext .vue,.js src/", @@ -56,7 +56,8 @@ "typescript": "^5.2.2", "vite": "^4.4.9", "vite-plugin-compression2": "^0.10.4", - "vite-plugin-rewrite-all": "^1.0.1" + "vite-plugin-rewrite-all": "^1.0.1", + "vue-tsc": "^1.8.10" }, "browserslist": [ "> 1%", diff --git a/frontend/public/index.html b/frontend/public/index.html index 39d926d8..04135401 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -179,7 +179,7 @@ - + [{[ if .Theme -]}] conn.send(command); - conn.onmessage = onmessage; - conn.onclose = onclose; -} diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js deleted file mode 100644 index 65a61e6a..00000000 --- a/frontend/src/api/files.js +++ /dev/null @@ -1,204 +0,0 @@ -import { createURL, fetchURL, removePrefix } from "./utils"; -import { baseURL } from "@/utils/constants"; -import { useAuthStore } from "@/stores/auth"; -import { upload as postTus, useTus } from "./tus"; - -export async function fetch(url) { - url = removePrefix(url); - - const res = await fetchURL(`/api/resources${url}`, {}); - - let data = await res.json(); - data.url = `/files${url}`; - - if (data.isDir) { - if (!data.url.endsWith("/")) data.url += "/"; - data.items = data.items.map((item, index) => { - item.index = index; - item.url = `${data.url}${encodeURIComponent(item.name)}`; - - if (item.isDir) { - item.url += "/"; - } - - return item; - }); - } - - return data; -} - -async function resourceAction(url, method, content) { - url = removePrefix(url); - - let opts = { method }; - - if (content) { - opts.body = content; - } - - const res = await fetchURL(`/api/resources${url}`, opts); - - return res; -} - -export async function remove(url) { - return resourceAction(url, "DELETE"); -} - -export async function put(url, content = "") { - return resourceAction(url, "PUT", content); -} - -export function download(format, ...files) { - let url = `${baseURL}/api/raw`; - - if (files.length === 1) { - url += removePrefix(files[0]) + "?"; - } else { - let arg = ""; - - for (let file of files) { - arg += removePrefix(file) + ","; - } - - arg = arg.substring(0, arg.length - 1); - arg = encodeURIComponent(arg); - url += `/?files=${arg}&`; - } - - if (format) { - url += `algo=${format}&`; - } - - const authStore = useAuthStore(); - if (authStore.jwt) { - url += `auth=${authStore.jwt}&`; - } - - window.open(url); -} - -export async function post(url, content = "", overwrite = false, onupload) { - // Use the pre-existing API if: - const useResourcesApi = - // a folder is being created - url.endsWith("/") || - // We're not using http(s) - (content instanceof Blob && - !["http:", "https:"].includes(window.location.protocol)) || - // Tus is disabled / not applicable - !(await useTus(content)); - return useResourcesApi - ? postResources(url, content, overwrite, onupload) - : postTus(url, content, overwrite, onupload); -} - -async function postResources(url, content = "", overwrite = false, onupload) { - url = removePrefix(url); - - let bufferContent; - if ( - content instanceof Blob && - !["http:", "https:"].includes(window.location.protocol) - ) { - bufferContent = await new Response(content).arrayBuffer(); - } - - const authStore = useAuthStore(); - return new Promise((resolve, reject) => { - let request = new XMLHttpRequest(); - request.open( - "POST", - `${baseURL}/api/resources${url}?override=${overwrite}`, - true - ); - request.setRequestHeader("X-Auth", authStore.jwt); - - if (typeof onupload === "function") { - request.upload.onprogress = onupload; - } - - request.onload = () => { - if (request.status === 200) { - resolve(request.responseText); - } else if (request.status === 409) { - reject(request.status); - } else { - reject(request.responseText); - } - }; - - request.onerror = () => { - reject(new Error("001 Connection aborted")); - }; - - request.send(bufferContent || content); - }); -} - -function moveCopy(items, copy = false, overwrite = false, rename = false) { - let promises = []; - - for (let item of items) { - const from = item.from; - const to = encodeURIComponent(removePrefix(item.to)); - const url = `${from}?action=${ - copy ? "copy" : "rename" - }&destination=${to}&override=${overwrite}&rename=${rename}`; - promises.push(resourceAction(url, "PATCH")); - } - - return Promise.all(promises); -} - -export function move(items, overwrite = false, rename = false) { - return moveCopy(items, false, overwrite, rename); -} - -export function copy(items, overwrite = false, rename = false) { - return moveCopy(items, true, overwrite, rename); -} - -export async function checksum(url, algo) { - const data = await resourceAction(`${url}?checksum=${algo}`, "GET"); - return (await data.json()).checksums[algo]; -} - -export function getDownloadURL(file, inline) { - const params = { - ...(inline && { inline: "true" }), - }; - - return createURL("api/raw" + file.path, params); -} - -export function getPreviewURL(file, size) { - const params = { - inline: "true", - key: Date.parse(file.modified), - }; - - return createURL("api/preview/" + size + file.path, params); -} - -export function getSubtitlesURL(file) { - const params = { - inline: "true", - }; - - const subtitles = []; - for (const sub of file.subtitles) { - subtitles.push(createURL("api/raw" + sub, params)); - } - - return subtitles; -} - -export async function usage(url) { - url = removePrefix(url); - - const res = await fetchURL(`/api/usage${url}`, {}); - - return await res.json(); -} diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js deleted file mode 100644 index abc189dc..00000000 --- a/frontend/src/api/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as files from "./files"; -import * as share from "./share"; -import * as users from "./users"; -import * as settings from "./settings"; -import * as pub from "./pub"; -import search from "./search"; -import commands from "./commands"; - -export { files, share, users, settings, pub, commands, search }; diff --git a/frontend/src/api/pub.js b/frontend/src/api/pub.js deleted file mode 100644 index 1511143d..00000000 --- a/frontend/src/api/pub.js +++ /dev/null @@ -1,70 +0,0 @@ -import { fetchURL, removePrefix, createURL } from "./utils"; -import { baseURL } from "@/utils/constants"; - -export async function fetch(url, password = "") { - url = removePrefix(url); - - const res = await fetchURL( - `/api/public/share${url}`, - { - headers: { "X-SHARE-PASSWORD": encodeURIComponent(password) }, - }, - false - ); - - let data = await res.json(); - data.url = `/share${url}`; - - if (data.isDir) { - if (!data.url.endsWith("/")) data.url += "/"; - data.items = data.items.map((item, index) => { - item.index = index; - item.url = `${data.url}${encodeURIComponent(item.name)}`; - - if (item.isDir) { - item.url += "/"; - } - - return item; - }); - } - - return data; -} - -export function download(format, hash, token, ...files) { - let url = `${baseURL}/api/public/dl/${hash}`; - - if (files.length === 1) { - url += encodeURIComponent(files[0]) + "?"; - } else { - let arg = ""; - - for (let file of files) { - arg += encodeURIComponent(file) + ","; - } - - arg = arg.substring(0, arg.length - 1); - arg = encodeURIComponent(arg); - url += `/?files=${arg}&`; - } - - if (format) { - url += `algo=${format}&`; - } - - if (token) { - url += `token=${token}&`; - } - - window.open(url); -} - -export function getDownloadURL(share, inline = false) { - const params = { - ...(inline && { inline: "true" }), - ...(share.token && { token: share.token }), - }; - - return createURL("api/public/dl/" + share.hash + share.path, params, false); -} diff --git a/frontend/src/api/search.js b/frontend/src/api/search.js deleted file mode 100644 index 42846880..00000000 --- a/frontend/src/api/search.js +++ /dev/null @@ -1,27 +0,0 @@ -import { fetchURL, removePrefix } from "./utils"; -import url from "../utils/url"; - -export default async function search(base, query) { - base = removePrefix(base); - query = encodeURIComponent(query); - - if (!base.endsWith("/")) { - base += "/"; - } - - let res = await fetchURL(`/api/search${base}?query=${query}`, {}); - - let data = await res.json(); - - data = data.map((item) => { - item.url = `/files${base}` + url.encodePath(item.path); - - if (item.dir) { - item.url += "/"; - } - - return item; - }); - - return data; -} diff --git a/frontend/src/api/settings.js b/frontend/src/api/settings.js deleted file mode 100644 index e03b0db1..00000000 --- a/frontend/src/api/settings.js +++ /dev/null @@ -1,12 +0,0 @@ -import { fetchURL, fetchJSON } from "./utils"; - -export function get() { - return fetchJSON(`/api/settings`, {}); -} - -export async function update(settings) { - await fetchURL(`/api/settings`, { - method: "PUT", - body: JSON.stringify(settings), - }); -} diff --git a/frontend/src/api/share.js b/frontend/src/api/share.js deleted file mode 100644 index 28d550ea..00000000 --- a/frontend/src/api/share.js +++ /dev/null @@ -1,40 +0,0 @@ -import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils"; - -export async function list() { - return fetchJSON("/api/shares"); -} - -export async function get(url) { - url = removePrefix(url); - return fetchJSON(`/api/share${url}`); -} - -export async function remove(hash) { - await fetchURL(`/api/share/${hash}`, { - method: "DELETE", - }); -} - -export async function create(url, password = "", expires = "", unit = "hours") { - url = removePrefix(url); - url = `/api/share${url}`; - if (expires !== "") { - url += `?expires=${expires}&unit=${unit}`; - } - let body = "{}"; - if (password != "" || expires !== "" || unit !== "hours") { - body = JSON.stringify({ - password: password, - expires: expires.toString(), // backend expects string not number - unit: unit, - }); - } - return fetchJSON(url, { - method: "POST", - body: body, - }); -} - -export function getShareURL(share) { - return createURL("share/" + share.hash, {}, false); -} diff --git a/frontend/src/api/tus.js b/frontend/src/api/tus.js deleted file mode 100644 index 83cb607a..00000000 --- a/frontend/src/api/tus.js +++ /dev/null @@ -1,91 +0,0 @@ -import * as tus from "tus-js-client"; -import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants"; -import { useAuthStore } from "@/stores/auth"; -import { removePrefix } from "@/api/utils"; -import { fetchURL } from "./utils"; - -const RETRY_BASE_DELAY = 1000; -const RETRY_MAX_DELAY = 20000; - -export async function upload( - filePath, - content = "", - overwrite = false, - onupload -) { - if (!tusSettings) { - // Shouldn't happen as we check for tus support before calling this function - throw new Error("Tus.io settings are not defined"); - } - - filePath = removePrefix(filePath); - let resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`; - - await createUpload(resourcePath); - - const authStore = useAuthStore(); - return new Promise((resolve, reject) => { - let upload = new tus.Upload(content, { - uploadUrl: `${baseURL}${resourcePath}`, - chunkSize: tusSettings.chunkSize, - retryDelays: computeRetryDelays(tusSettings), - parallelUploads: 1, - storeFingerprintForResuming: false, - headers: { - "X-Auth": authStore.jwt, - }, - onError: function (error) { - reject("Upload failed: " + error); - }, - onProgress: function (bytesUploaded) { - // Emulate ProgressEvent.loaded which is used by calling functions - // loaded is specified in bytes (https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent/loaded) - if (typeof onupload === "function") { - onupload({ loaded: bytesUploaded }); - } - }, - onSuccess: function () { - resolve(); - }, - }); - upload.start(); - }); -} - -async function createUpload(resourcePath) { - let headResp = await fetchURL(resourcePath, { - method: "POST", - }); - if (headResp.status !== 201) { - throw new Error( - `Failed to create an upload: ${headResp.status} ${headResp.statusText}` - ); - } -} - -function computeRetryDelays(tusSettings) { - if (!tusSettings.retryCount || tusSettings.retryCount < 1) { - // Disable retries altogether - return null; - } - // The tus client expects our retries as an array with computed backoffs - // E.g.: [0, 3000, 5000, 10000, 20000] - const retryDelays = []; - let delay = 0; - - for (let i = 0; i < tusSettings.retryCount; i++) { - retryDelays.push(Math.min(delay, RETRY_MAX_DELAY)); - delay = - delay === 0 ? RETRY_BASE_DELAY : Math.min(delay * 2, RETRY_MAX_DELAY); - } - - return retryDelays; -} - -export async function useTus(content) { - return isTusSupported() && content instanceof Blob; -} - -function isTusSupported() { - return tus.isSupported === true; -} diff --git a/frontend/src/api/users.js b/frontend/src/api/users.js deleted file mode 100644 index 105d6cc0..00000000 --- a/frontend/src/api/users.js +++ /dev/null @@ -1,41 +0,0 @@ -import { fetchURL, fetchJSON } from "./utils"; - -export async function getAll() { - return fetchJSON(`/api/users`, {}); -} - -export async function get(id) { - return fetchJSON(`/api/users/${id}`, {}); -} - -export async function create(user) { - const res = await fetchURL(`/api/users`, { - method: "POST", - body: JSON.stringify({ - what: "user", - which: [], - data: user, - }), - }); - - if (res.status === 201) { - return res.headers.get("Location"); - } -} - -export async function update(user, which = ["all"]) { - await fetchURL(`/api/users/${user.id}`, { - method: "PUT", - body: JSON.stringify({ - what: "user", - which: which, - data: user, - }), - }); -} - -export async function remove(id) { - await fetchURL(`/api/users/${id}`, { - method: "DELETE", - }); -} diff --git a/frontend/src/api/utils.js b/frontend/src/api/utils.js deleted file mode 100644 index 26b62d4f..00000000 --- a/frontend/src/api/utils.js +++ /dev/null @@ -1,84 +0,0 @@ -import { useAuthStore } from "@/stores/auth"; -import { renew, logout } from "@/utils/auth"; -import { baseURL } from "@/utils/constants"; -import { encodePath } from "@/utils/url"; - -export async function fetchURL(url, opts, auth = true) { - const authStore = useAuthStore(); - - opts = opts || {}; - opts.headers = opts.headers || {}; - - let { headers, ...rest } = opts; - let res; - try { - res = await fetch(`${baseURL}${url}`, { - headers: { - "X-Auth": authStore.jwt, - ...headers, - }, - ...rest, - }); - } catch { - const error = new Error("000 No connection"); - error.status = 0; - - throw error; - } - - if (auth && res.headers.get("X-Renew-Token") === "true") { - await renew(authStore.jwt); - } - - if (res.status < 200 || res.status > 299) { - const error = new Error(await res.text()); - error.status = res.status; - - if (auth && res.status == 401) { - logout(); - } - - throw error; - } - - return res; -} - -export async function fetchJSON(url, opts) { - const res = await fetchURL(url, opts); - - if (res.status === 200) { - return res.json(); - } else { - throw new Error(res.status); - } -} - -export function removePrefix(url) { - url = url.split("/").splice(2).join("/"); - - if (url === "") url = "/"; - if (url[0] !== "/") url = "/" + url; - return url; -} - -export function createURL(endpoint, params = {}, auth = true) { - const authStore = useAuthStore(); - - let prefix = baseURL; - if (!prefix.endsWith("/")) { - prefix = prefix + "/"; - } - const url = new URL(prefix + encodePath(endpoint), origin); - - const searchParams = { - ...(auth && { auth: authStore.jwt }), - ...params, - }; - - for (const key in searchParams) { - url.searchParams.set(key, searchParams[key]); - } - - return url.toString(); -} diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 31035b49..098b1be6 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,4 +1,4 @@ -import { createRouter, createWebHistory } from "vue-router"; +import { RouteLocation, createRouter, createWebHistory } from "vue-router"; import Login from "@/views/Login.vue"; import Layout from "@/views/Layout.vue"; import Files from "@/views/Files.vue"; @@ -144,7 +144,7 @@ const routes = [ }, { path: "/:catchAll(.*)*", - redirect: (to) => `/files/${[...to.params.catchAll].join("/")}`, + redirect: (to: RouteLocation) => `/files/${[...to.params.catchAll].join("/")}`, }, ]; @@ -156,7 +156,7 @@ async function initAuth() { } if (recaptcha) { - await new Promise((resolve) => { + await new Promise((resolve) => { const check = () => { if (typeof window.grecaptcha === "undefined") { setTimeout(check, 100); @@ -175,10 +175,11 @@ const router = createRouter({ routes, }); -router.beforeResolve(async (to, from, next) => { +router.beforeResolve(async (to: RouteLocation, from, next) => { let title; try { // this should not fail after we finished the migration + // @ts-ignore title = i18n.global.t(titles[to.name]); } catch (error) { console.error(error); @@ -187,14 +188,14 @@ router.beforeResolve(async (to, from, next) => { document.title = title + " - " + name; /*** RTL related settings per route ****/ - const rtlSet = document.querySelector("body").classList.contains("rtl"); + const rtlSet = document.querySelector("body")?.classList.contains("rtl"); const shouldSetRtl = rtlLanguages.includes(i18n.global.locale); switch (true) { case shouldSetRtl && !rtlSet: - document.querySelector("body").classList.add("rtl"); + document.querySelector("body")?.classList.add("rtl"); break; case !shouldSetRtl && rtlSet: - document.querySelector("body").classList.remove("rtl"); + document.querySelector("body")?.classList.remove("rtl"); break; } @@ -225,7 +226,7 @@ router.beforeResolve(async (to, from, next) => { } if (to.matched.some((record) => record.meta.requiresAdmin)) { - if (!authStore.user.perm.admin) { + if (authStore.user === null || !authStore.user.perm.admin) { next({ path: "/403" }); return; } diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index 7b57ef27..985010b3 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -5,7 +5,10 @@ import { cloneDeep } from "lodash-es"; export const useAuthStore = defineStore("auth", { // convert to a function - state: () => ({ + state: (): { + user: user | null, + jwt: string + } => ({ user: null, jwt: "", }), @@ -15,7 +18,7 @@ export const useAuthStore = defineStore("auth", { }, actions: { // no context as first argument, use `this` instead - setUser(value) { + setUser(value: user) { if (value === null) { this.user = null; return; @@ -23,19 +26,23 @@ export const useAuthStore = defineStore("auth", { const locale = value.locale || detectLocale(); dayjs.locale(locale); + // @ts-ignore Don't know how to fix this yet i18n.global.locale.value = locale; this.user = value; }, - updateUser(value) { + updateUser(value: user) { if (typeof value !== "object") return; - for (let field in value) { + let field: userKey + for (field in value) { if (field === "locale") { const locale = value[field]; dayjs.locale(locale); + // @ts-ignore Don't know how to fix this yet i18n.global.locale.value = locale; } + // @ts-ignore to fix this.user[field] = cloneDeep(value[field]); } }, diff --git a/frontend/src/stores/file.ts b/frontend/src/stores/file.ts index 893f6948..045f6e2c 100644 --- a/frontend/src/stores/file.ts +++ b/frontend/src/stores/file.ts @@ -2,7 +2,14 @@ import { defineStore } from "pinia"; export const useFileStore = defineStore("file", { // convert to a function - state: () => ({ + state: (): { + req: req, + oldReq: req, + reload: boolean, + selected: any[], + multiple: boolean, + isFiles: boolean + } => ({ req: {}, oldReq: {}, reload: false, @@ -29,11 +36,11 @@ export const useFileStore = defineStore("file", { toggleMultiple() { this.multiple = !this.multiple; }, - updateRequest(value) { + updateRequest(value: req) { this.oldReq = this.req; this.req = value; }, - removeSelected(value) { + removeSelected(value: any) { let i = this.selected.indexOf(value); if (i === -1) return; this.selected.splice(i, 1); diff --git a/frontend/src/stores/index.ts b/frontend/src/stores/index.ts index 3ab005bc..7f52aa28 100644 --- a/frontend/src/stores/index.ts +++ b/frontend/src/stores/index.ts @@ -1,7 +1,8 @@ import { createPinia as _createPinia } from "pinia"; import { markRaw } from "vue"; +import { Router } from "vue-router"; -export default function createPinia(router) { +export default function createPinia(router: Router) { const pinia = _createPinia(); pinia.use(({ store }) => { store.router = markRaw(router); diff --git a/frontend/src/stores/layout.ts b/frontend/src/stores/layout.ts index 267a27e9..1ca27243 100644 --- a/frontend/src/stores/layout.ts +++ b/frontend/src/stores/layout.ts @@ -4,7 +4,13 @@ import { defineStore } from "pinia"; export const useLayoutStore = defineStore("layout", { // convert to a function - state: () => ({ + state: (): { + loading: boolean, + show: string | null | boolean, + showConfirm: boolean | null, + showAction: boolean | null, + showShell: boolean | null + } => ({ loading: false, show: null, showConfirm: null, @@ -19,7 +25,7 @@ export const useLayoutStore = defineStore("layout", { toggleShell() { this.showShell = !this.showShell; }, - showHover(value) { + showHover(value: LayoutValue) { if (typeof value !== "object") { this.show = value; return; diff --git a/frontend/src/stores/upload.ts b/frontend/src/stores/upload.ts index 95eb4dbc..fe3d8d7c 100644 --- a/frontend/src/stores/upload.ts +++ b/frontend/src/stores/upload.ts @@ -6,14 +6,21 @@ import buttons from "@/utils/buttons"; const UPLOADS_LIMIT = 5; -const beforeUnload = (event) => { +const beforeUnload = (event: Event) => { event.preventDefault(); - event.returnValue = ""; + // To remove >> is deprecated + // event.returnValue = ""; }; export const useUploadStore = defineStore("upload", { // convert to a function - state: () => ({ + state: (): { + id: number, + sizes: any[], + progress: any[], + queue: any[], + uploads: uploads + } => ({ id: 0, sizes: [], progress: [], @@ -29,7 +36,8 @@ export const useUploadStore = defineStore("upload", { const totalSize = state.sizes.reduce((a, b) => a + b, 0); - const sum = state.progress.reduce((acc, val) => acc + val); + // @ts-ignore + const sum: number = state.progress.reduce((acc, val) => acc + val); return Math.ceil((sum / totalSize) * 100); }, filesInUploadCount: (state) => { @@ -64,8 +72,9 @@ export const useUploadStore = defineStore("upload", { }, actions: { // no context as first argument, use `this` instead - setProgress({ id, loaded }) { + setProgress(obj: { id: number, loaded: boolean }) { // Vue.set(this.progress, id, loaded); + const { id, loaded } = obj this.progress[id] = loaded; }, reset() { @@ -73,7 +82,7 @@ export const useUploadStore = defineStore("upload", { this.sizes = []; this.progress = []; }, - addJob(item) { + addJob(item: item) { this.queue.push(item); this.sizes[this.id] = item.file.size; this.id++; @@ -84,11 +93,11 @@ export const useUploadStore = defineStore("upload", { // Vue.set(this.uploads, item.id, item); this.uploads[item.id] = item; }, - removeJob(id) { + removeJob(id: number) { // Vue.delete(this.uploads, id); delete this.uploads[id]; }, - upload(item) { + upload(item: item) { let uploadsCount = Object.keys(this.uploads).length; let isQueueEmpty = this.queue.length == 0; @@ -102,8 +111,8 @@ export const useUploadStore = defineStore("upload", { this.addJob(item); this.processUploads(); }, - finishUpload(item) { - this.setProgress({ id: item.id, loaded: item.file.size }); + finishUpload(item: item) { + this.setProgress({ id: item.id, loaded: (item.file.size > 0) }); this.removeJob(item.id); this.processUploads(); }, diff --git a/frontend/src/types/global.d.ts b/frontend/src/types/global.d.ts index 8766bd75..8f8d3240 100644 --- a/frontend/src/types/global.d.ts +++ b/frontend/src/types/global.d.ts @@ -3,5 +3,6 @@ export {}; declare global { interface Window { FileBrowser: any; + grecaptcha: any } } \ No newline at end of file diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts index 8e5fbcba..3715bfd1 100644 --- a/frontend/src/utils/auth.ts +++ b/frontend/src/utils/auth.ts @@ -3,9 +3,9 @@ import router from "@/router"; import jwt_decode from "jwt-decode"; import { baseURL } from "./constants"; -export function parseToken(token) { +export function parseToken(token: string) { // falsy or malformed jwt will throw InvalidTokenError - const data = jwt_decode(token); + const data = jwt_decode<{[key:string]: any, user: user}>(token); document.cookie = `auth=${token}; Path=/; SameSite=Strict;`; @@ -19,7 +19,7 @@ export function parseToken(token) { export async function validateLogin() { try { if (localStorage.getItem("jwt")) { - await renew(localStorage.getItem("jwt")); + await renew(localStorage.getItem("jwt")); } } catch (error) { console.warn("Invalid JWT token in storage"); // eslint-disable-line @@ -27,7 +27,7 @@ export async function validateLogin() { } } -export async function login(username, password, recaptcha) { +export async function login(username: string, password: string, recaptcha: string) { const data = { username, password, recaptcha }; const res = await fetch(`${baseURL}/api/login`, { @@ -47,7 +47,7 @@ export async function login(username, password, recaptcha) { } } -export async function renew(jwt) { +export async function renew(jwt: string) { const res = await fetch(`${baseURL}/api/renew`, { method: "POST", headers: { @@ -64,7 +64,7 @@ export async function renew(jwt) { } } -export async function signup(username, password) { +export async function signup(username: string, password: string) { const data = { username, password }; const res = await fetch(`${baseURL}/api/signup`, { @@ -76,6 +76,7 @@ export async function signup(username, password) { }); if (res.status !== 200) { + // @ts-ignore still need to fix these errors throw new Error(res.status); } } @@ -86,6 +87,6 @@ export function logout() { const authStore = useAuthStore(); authStore.clearUser(); - localStorage.setItem("jwt", null); + localStorage.setItem("jwt", ''); router.push({ path: "/login" }); } diff --git a/frontend/src/utils/buttons.ts b/frontend/src/utils/buttons.ts index 1c6bdeee..e115c8c1 100644 --- a/frontend/src/utils/buttons.ts +++ b/frontend/src/utils/buttons.ts @@ -1,5 +1,5 @@ -function loading(button) { - let el = document.querySelector(`#${button}-button > i`); +function loading(button: string) { + let el: HTMLButtonElement | null = document.querySelector(`#${button}-button > i`); if (el === undefined || el === null) { console.log("Error getting button " + button); // eslint-disable-line @@ -11,53 +11,62 @@ function loading(button) { } el.dataset.icon = el.innerHTML; - el.style.opacity = 0; + el.style.opacity = "0"; setTimeout(() => { - el.classList.add("spin"); - el.innerHTML = "autorenew"; - el.style.opacity = 1; + if(el) { + el.classList.add("spin"); + el.innerHTML = "autorenew"; + el.style.opacity = "1"; + } }, 100); } -function done(button) { - let el = document.querySelector(`#${button}-button > i`); +function done(button: string) { + let el: HTMLButtonElement | null = document.querySelector(`#${button}-button > i`); if (el === undefined || el === null) { console.log("Error getting button " + button); // eslint-disable-line return; } - el.style.opacity = 0; + el.style.opacity = "0"; setTimeout(() => { - el.classList.remove("spin"); - el.innerHTML = el.dataset.icon; - el.style.opacity = 1; + if(el !== null) { + el.classList.remove("spin"); + el.innerHTML = el?.dataset?.icon || ""; + el.style.opacity = "1"; + } + + }, 100); } -function success(button) { - let el = document.querySelector(`#${button}-button > i`); +function success(button: string) { + let el: HTMLButtonElement | null = document.querySelector(`#${button}-button > i`); if (el === undefined || el === null) { console.log("Error getting button " + button); // eslint-disable-line return; } - el.style.opacity = 0; + el.style.opacity = "0"; setTimeout(() => { - el.classList.remove("spin"); - el.innerHTML = "done"; - el.style.opacity = 1; - + if(el !== null) { + el.classList.remove("spin"); + el.innerHTML = "done"; + el.style.opacity = "1"; + } setTimeout(() => { - el.style.opacity = 0; + if(el) el.style.opacity = "0"; setTimeout(() => { - el.innerHTML = el.dataset.icon; - el.style.opacity = 1; + if(el !== null) { + el.innerHTML = el?.dataset?.icon || ""; + el.style.opacity = "1"; + } }, 100); }, 500); }, 100); diff --git a/frontend/src/utils/cookie.ts b/frontend/src/utils/cookie.ts index 72d59be4..7feb4265 100644 --- a/frontend/src/utils/cookie.ts +++ b/frontend/src/utils/cookie.ts @@ -1,4 +1,4 @@ -export default function (name) { +export default function (name: string) { let re = new RegExp( "(?:(?:^|.*;\\s*)" + name + "\\s*\\=\\s*([^;]*).*$)|^.*$" ); diff --git a/frontend/src/utils/css.ts b/frontend/src/utils/css.ts index 405c0dd2..7877f57d 100644 --- a/frontend/src/utils/css.ts +++ b/frontend/src/utils/css.ts @@ -1,4 +1,4 @@ -export default function getRule(rules) { +export default function getRule(rules: any) { for (let i = 0; i < rules.length; i++) { rules[i] = rules[i].toLowerCase(); } diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 0b6cd571..5646c03d 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -1 +1 @@ -export * from "./funcs"; +// export * from "./funcs"; diff --git a/frontend/src/utils/upload.ts b/frontend/src/utils/upload.ts index 88a08b7b..a4d216db 100644 --- a/frontend/src/utils/upload.ts +++ b/frontend/src/utils/upload.ts @@ -1,7 +1,7 @@ import { useUploadStore } from "@/stores/upload"; import url from "@/utils/url"; -export function checkConflict(files, items) { +export function checkConflict(files: file[], items: item[]) { if (typeof items === "undefined" || items === null) { items = []; } @@ -21,6 +21,7 @@ export function checkConflict(files, items) { } let res = items.findIndex(function hasConflict(element) { + // @ts-ignore Don't know what this does return element.name === this; }, name); @@ -33,10 +34,10 @@ export function checkConflict(files, items) { return conflict; } -export function scanFiles(dt) { +export function scanFiles(dt: {[key: string]: any, item: item}) { return new Promise((resolve) => { let reading = 0; - const contents = []; + const contents: any[] = []; if (dt.items !== undefined) { for (let item of dt.items) { @@ -52,10 +53,10 @@ export function scanFiles(dt) { resolve(dt.files); } - function readEntry(entry, directory = "") { + function readEntry(entry: any, directory = "") { if (entry.isFile) { reading++; - entry.file((file) => { + entry.file((file: file) => { reading--; file.fullPath = `${directory}${file.name}`; @@ -79,10 +80,10 @@ export function scanFiles(dt) { } } - function readReaderContent(reader, directory) { + function readReaderContent(reader: any, directory: string) { reading++; - reader.readEntries(function (entries) { + reader.readEntries(function (entries: any[]) { reading--; if (entries.length > 0) { for (const entry of entries) { @@ -100,7 +101,7 @@ export function scanFiles(dt) { }); } -function detectType(mimetype) { +function detectType(mimetype: string): uploadType { if (mimetype.startsWith("video")) return "video"; if (mimetype.startsWith("audio")) return "audio"; if (mimetype.startsWith("image")) return "image"; @@ -109,7 +110,7 @@ function detectType(mimetype) { return "blob"; } -export function handleFiles(files, base, overwrite = false) { +export function handleFiles(files: file[], base: string, overwrite = false) { const uploadStore = useUploadStore(); for (let i = 0; i < files.length; i++) { diff --git a/frontend/src/utils/url.ts b/frontend/src/utils/url.ts index bf30a17c..bcd8609f 100644 --- a/frontend/src/utils/url.ts +++ b/frontend/src/utils/url.ts @@ -1,4 +1,4 @@ -export function removeLastDir(url) { +export function removeLastDir(url: string) { var arr = url.split("/"); if (arr.pop() === "") { arr.pop(); @@ -9,7 +9,7 @@ export function removeLastDir(url) { // this function is taken from mozilla // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples -export function encodeRFC5987ValueChars(str) { +export function encodeRFC5987ValueChars(str: string) { return ( encodeURIComponent(str) // The following creates the sequences %27 %28 %29 %2A (Note that @@ -28,7 +28,7 @@ export function encodeRFC5987ValueChars(str) { ); } -export function encodePath(str) { +export function encodePath(str: string) { return str .split("/") .map((v) => encodeURIComponent(v)) From 4ea66b8bd37eee8f1ee91430d4fccefa43255586 Mon Sep 17 00:00:00 2001 From: Joep Date: Fri, 8 Sep 2023 00:15:13 +0200 Subject: [PATCH 04/24] Forgot to add ts files --- frontend/src/api/commands.ts | 18 +++ frontend/src/api/files.ts | 208 +++++++++++++++++++++++++++++++++ frontend/src/api/index.ts | 9 ++ frontend/src/api/pub.ts | 71 +++++++++++ frontend/src/api/search.ts | 27 +++++ frontend/src/api/settings.ts | 12 ++ frontend/src/api/share.ts | 40 +++++++ frontend/src/api/tus.ts | 96 +++++++++++++++ frontend/src/api/users.ts | 41 +++++++ frontend/src/api/utils.ts | 86 ++++++++++++++ frontend/src/types/api.d.ts | 37 ++++++ frontend/src/types/file.d.ts | 40 +++++++ frontend/src/types/layout.d.ts | 5 + frontend/src/types/user.d.ts | 7 ++ frontend/src/types/utils.d.ts | 1 + frontend/src/vite-env.d.ts | 1 + 16 files changed, 699 insertions(+) create mode 100644 frontend/src/api/commands.ts create mode 100644 frontend/src/api/files.ts create mode 100644 frontend/src/api/index.ts create mode 100644 frontend/src/api/pub.ts create mode 100644 frontend/src/api/search.ts create mode 100644 frontend/src/api/settings.ts create mode 100644 frontend/src/api/share.ts create mode 100644 frontend/src/api/tus.ts create mode 100644 frontend/src/api/users.ts create mode 100644 frontend/src/api/utils.ts create mode 100644 frontend/src/types/api.d.ts create mode 100644 frontend/src/types/file.d.ts create mode 100644 frontend/src/types/layout.d.ts create mode 100644 frontend/src/types/user.d.ts create mode 100644 frontend/src/types/utils.d.ts create mode 100644 frontend/src/vite-env.d.ts diff --git a/frontend/src/api/commands.ts b/frontend/src/api/commands.ts new file mode 100644 index 00000000..12e6af8f --- /dev/null +++ b/frontend/src/api/commands.ts @@ -0,0 +1,18 @@ +import { removePrefix } from "./utils"; +import { baseURL } from "@/utils/constants"; +import { useAuthStore } from "@/stores/auth"; + +const ssl = window.location.protocol === "https:"; +const protocol = ssl ? "wss:" : "ws:"; + +export default function command(url: string, command: string, onmessage: WebSocket["onmessage"], onclose: WebSocket["onclose"]) { + const authStore = useAuthStore(); + + url = removePrefix(url); + url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${authStore.jwt}`; + + let conn = new window.WebSocket(url); + conn.onopen = () => conn.send(command); + conn.onmessage = onmessage; + conn.onclose = onclose; +} diff --git a/frontend/src/api/files.ts b/frontend/src/api/files.ts new file mode 100644 index 00000000..3c0852f3 --- /dev/null +++ b/frontend/src/api/files.ts @@ -0,0 +1,208 @@ +import { createURL, fetchURL, removePrefix } from "./utils"; +import { baseURL } from "@/utils/constants"; +import { useAuthStore } from "@/stores/auth"; +import { upload as postTus, useTus } from "./tus"; + +export async function fetch(url: apiUrl) { + url = removePrefix(url); + + const res = await fetchURL(`/api/resources${url}`, {}); + + let data = await res.json(); + data.url = `/files${url}`; + + if (data.isDir) { + if (!data.url.endsWith("/")) data.url += "/"; + // Perhaps change the any + data.items = data.items.map((item: any, index: any) => { + item.index = index; + item.url = `${data.url}${encodeURIComponent(item.name)}`; + + if (item.isDir) { + item.url += "/"; + } + + return item; + }); + } + + return data; +} + +async function resourceAction(url: apiUrl, method: apiMethod, content?: any) { + debugger; + url = removePrefix(url); + + let opts: apiOpts = { + method + }; + + if (content) { + opts.body = content; + } + + const res = await fetchURL(`/api/resources${url}`, opts); + + return res; +} + +export async function remove(url: apiUrl) { + return resourceAction(url, "DELETE"); +} + +export async function put(url: apiUrl, content = "") { + return resourceAction(url, "PUT", content); +} + +export function download(format: any, ...files: string[]) { + let url = `${baseURL}/api/raw`; + + if (files.length === 1) { + url += removePrefix(files[0]) + "?"; + } else { + let arg = ""; + + for (let file of files) { + arg += removePrefix(file) + ","; + } + + arg = arg.substring(0, arg.length - 1); + arg = encodeURIComponent(arg); + url += `/?files=${arg}&`; + } + + if (format) { + url += `algo=${format}&`; + } + + const authStore = useAuthStore(); + if (authStore.jwt) { + url += `auth=${authStore.jwt}&`; + } + + window.open(url); +} + +export async function post(url: apiUrl, content: apiContent = "", overwrite = false, onupload: Function = () => {}) { + // Use the pre-existing API if: + const useResourcesApi = + // a folder is being created + url.endsWith("/") || + // We're not using http(s) + (content instanceof Blob && + !["http:", "https:"].includes(window.location.protocol)) || + // Tus is disabled / not applicable + !(await useTus(content)); + return useResourcesApi + ? postResources(url, content, overwrite, onupload) + : postTus(url, content, overwrite, onupload); +} + +async function postResources(url: apiUrl, content: apiContent = "", overwrite = false, onupload: any) { + url = removePrefix(url); + + let bufferContent: ArrayBuffer; + if ( + content instanceof Blob && + !["http:", "https:"].includes(window.location.protocol) + ) { + bufferContent = await new Response(content).arrayBuffer(); + } + + const authStore = useAuthStore(); + return new Promise((resolve, reject) => { + let request = new XMLHttpRequest(); + request.open( + "POST", + `${baseURL}/api/resources${url}?override=${overwrite}`, + true + ); + request.setRequestHeader("X-Auth", authStore.jwt); + + if (typeof onupload === "function") { + request.upload.onprogress = onupload; + } + + request.onload = () => { + if (request.status === 200) { + resolve(request.responseText); + } else if (request.status === 409) { + reject(request.status); + } else { + reject(request.responseText); + } + }; + + request.onerror = () => { + reject(new Error("001 Connection aborted")); + }; + + request.send(bufferContent || content); + }); +} + +function moveCopy(items: item[], copy = false, overwrite = false, rename = false) { + let promises = []; + + for (let item of items) { + const from = item.from; + const to = encodeURIComponent(removePrefix(item.to ?? "")); + const url = `${from}?action=${ + copy ? "copy" : "rename" + }&destination=${to}&override=${overwrite}&rename=${rename}`; + promises.push(resourceAction(url, "PATCH")); + } + + return Promise.all(promises); +} + +export function move(items: item[], overwrite = false, rename = false) { + return moveCopy(items, false, overwrite, rename); +} + +export function copy(items: item[], overwrite = false, rename = false) { + return moveCopy(items, true, overwrite, rename); +} + +export async function checksum(url: apiUrl, algo: algo) { + const data = await resourceAction(`${url}?checksum=${algo}`, "GET"); + return (await data.json()).checksums[algo]; +} + +export function getDownloadURL(file: file, inline: any) { + const params = { + ...(inline && { inline: "true" }), + }; + + return createURL("api/raw" + file.path, params); +} + +export function getPreviewURL(file: file, size: string) { + const params = { + inline: "true", + key: Date.parse(file.modified), + }; + + return createURL("api/preview/" + size + file.path, params); +} + +export function getSubtitlesURL(file: file) { + const params = { + inline: "true", + }; + + const subtitles = []; + for (const sub of file.subtitles) { + subtitles.push(createURL("api/raw" + sub, params)); + } + + return subtitles; +} + +export async function usage(url: apiUrl) { + url = removePrefix(url); + + const res = await fetchURL(`/api/usage${url}`, {}); + + return await res.json(); +} diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts new file mode 100644 index 00000000..abc189dc --- /dev/null +++ b/frontend/src/api/index.ts @@ -0,0 +1,9 @@ +import * as files from "./files"; +import * as share from "./share"; +import * as users from "./users"; +import * as settings from "./settings"; +import * as pub from "./pub"; +import search from "./search"; +import commands from "./commands"; + +export { files, share, users, settings, pub, commands, search }; diff --git a/frontend/src/api/pub.ts b/frontend/src/api/pub.ts new file mode 100644 index 00000000..526e2b0d --- /dev/null +++ b/frontend/src/api/pub.ts @@ -0,0 +1,71 @@ +import { fetchURL, removePrefix, createURL } from "./utils"; +import { baseURL } from "@/utils/constants"; + +export async function fetch(url: apiUrl, password: string = "") { + url = removePrefix(url); + + const res = await fetchURL( + `/api/public/share${url}`, + { + headers: { "X-SHARE-PASSWORD": encodeURIComponent(password) }, + }, + false + ); + + let data = await res.json(); + data.url = `/share${url}`; + + if (data.isDir) { + if (!data.url.endsWith("/")) data.url += "/"; + data.items = data.items.map((item: any, index: any) => { + item.index = index; + item.url = `${data.url}${encodeURIComponent(item.name)}`; + + if (item.isDir) { + item.url += "/"; + } + + return item; + }); + } + + return data; +} + +// Is this redundant code? +// export function download(format, hash, token, ...files) { +// let url = `${baseURL}/api/public/dl/${hash}`; + +// if (files.length === 1) { +// url += encodeURIComponent(files[0]) + "?"; +// } else { +// let arg = ""; + +// for (let file of files) { +// arg += encodeURIComponent(file) + ","; +// } + +// arg = arg.substring(0, arg.length - 1); +// arg = encodeURIComponent(arg); +// url += `/?files=${arg}&`; +// } + +// if (format) { +// url += `algo=${format}&`; +// } + +// if (token) { +// url += `token=${token}&`; +// } + +// window.open(url); +// } + +export function getDownloadURL(share: share, inline = false) { + const params = { + ...(inline && { inline: "true" }), + ...(share.token && { token: share.token }), + }; + + return createURL("api/public/dl/" + share.hash + share.path, params, false); +} diff --git a/frontend/src/api/search.ts b/frontend/src/api/search.ts new file mode 100644 index 00000000..a6cd6175 --- /dev/null +++ b/frontend/src/api/search.ts @@ -0,0 +1,27 @@ +import { fetchURL, removePrefix } from "./utils"; +import url from "../utils/url"; + +export default async function search(base: apiUrl, query: string) { + base = removePrefix(base); + query = encodeURIComponent(query); + + if (!base.endsWith("/")) { + base += "/"; + } + + let res = await fetchURL(`/api/search${base}?query=${query}`, {}); + + let data = await res.json(); + + data = data.map((item: item) => { + item.url = `/files${base}` + url.encodePath(item.path); + + if (item.dir) { + item.url += "/"; + } + + return item; + }); + + return data; +} diff --git a/frontend/src/api/settings.ts b/frontend/src/api/settings.ts new file mode 100644 index 00000000..43fc126c --- /dev/null +++ b/frontend/src/api/settings.ts @@ -0,0 +1,12 @@ +import { fetchURL, fetchJSON } from "./utils"; + +export function get() { + return fetchJSON(`/api/settings`, {}); +} + +export async function update(settings: settings) { + await fetchURL(`/api/settings`, { + method: "PUT", + body: JSON.stringify(settings), + }); +} diff --git a/frontend/src/api/share.ts b/frontend/src/api/share.ts new file mode 100644 index 00000000..441ea7a7 --- /dev/null +++ b/frontend/src/api/share.ts @@ -0,0 +1,40 @@ +import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils"; + +export async function list() { + return fetchJSON("/api/shares"); +} + +export async function get(url: apiUrl) { + url = removePrefix(url); + return fetchJSON(`/api/share${url}`); +} + +export async function remove(hash: string) { + await fetchURL(`/api/share/${hash}`, { + method: "DELETE", + }); +} + +export async function create(url: apiUrl, password = "", expires = "", unit = "hours") { + url = removePrefix(url); + url = `/api/share${url}`; + if (expires !== "") { + url += `?expires=${expires}&unit=${unit}`; + } + let body = "{}"; + if (password != "" || expires !== "" || unit !== "hours") { + body = JSON.stringify({ + password: password, + expires: expires.toString(), // backend expects string not number + unit: unit, + }); + } + return fetchJSON(url, { + method: "POST", + body: body, + }); +} + +export function getShareURL(share: share) { + return createURL("share/" + share.hash, {}, false); +} diff --git a/frontend/src/api/tus.ts b/frontend/src/api/tus.ts new file mode 100644 index 00000000..632572d0 --- /dev/null +++ b/frontend/src/api/tus.ts @@ -0,0 +1,96 @@ +import * as tus from "tus-js-client"; +import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants"; +import { useAuthStore } from "@/stores/auth"; +import { removePrefix } from "@/api/utils"; +import { fetchURL } from "./utils"; + +const RETRY_BASE_DELAY = 1000; +const RETRY_MAX_DELAY = 20000; + +export async function upload( + filePath: string, + content: apiContent = "", + overwrite = false, + onupload: Function +) { + if (!tusSettings) { + // Shouldn't happen as we check for tus support before calling this function + throw new Error("Tus.io settings are not defined"); + } + + filePath = removePrefix(filePath); + let resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`; + + await createUpload(resourcePath); + + const authStore = useAuthStore(); + + // Exit early because of typescript, tus content can't be a string + if(content === "") { + return false; + } + return new Promise((resolve, reject) => { + let upload = new tus.Upload(content, { + uploadUrl: `${baseURL}${resourcePath}`, + chunkSize: tusSettings.chunkSize, + retryDelays: computeRetryDelays(tusSettings), + parallelUploads: 1, + storeFingerprintForResuming: false, + headers: { + "X-Auth": authStore.jwt, + }, + onError: function (error) { + reject("Upload failed: " + error); + }, + onProgress: function (bytesUploaded) { + // Emulate ProgressEvent.loaded which is used by calling functions + // loaded is specified in bytes (https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent/loaded) + if (typeof onupload === "function") { + onupload({ loaded: bytesUploaded }); + } + }, + onSuccess: function () { + resolve(); + }, + }); + upload.start(); + }); +} + +async function createUpload(resourcePath: resourcePath) { + let headResp = await fetchURL(resourcePath, { + method: "POST", + }); + if (headResp.status !== 201) { + throw new Error( + `Failed to create an upload: ${headResp.status} ${headResp.statusText}` + ); + } +} + +function computeRetryDelays(tusSettings: tusSettings): number[] | undefined{ + if (!tusSettings.retryCount || tusSettings.retryCount < 1) { + // Disable retries altogether + return undefined; + } + // The tus client expects our retries as an array with computed backoffs + // E.g.: [0, 3000, 5000, 10000, 20000] + const retryDelays = []; + let delay = 0; + + for (let i = 0; i < tusSettings.retryCount; i++) { + retryDelays.push(Math.min(delay, RETRY_MAX_DELAY)); + delay = + delay === 0 ? RETRY_BASE_DELAY : Math.min(delay * 2, RETRY_MAX_DELAY); + } + + return retryDelays; +} + +export async function useTus(content: apiContent) { + return isTusSupported() && content instanceof Blob; +} + +function isTusSupported() { + return tus.isSupported === true; +} diff --git a/frontend/src/api/users.ts b/frontend/src/api/users.ts new file mode 100644 index 00000000..0acfaad5 --- /dev/null +++ b/frontend/src/api/users.ts @@ -0,0 +1,41 @@ +import { fetchURL, fetchJSON } from "./utils"; + +export async function getAll() { + return fetchJSON(`/api/users`, {}); +} + +export async function get(id: number) { + return fetchJSON(`/api/users/${id}`, {}); +} + +export async function create(user: user) { + const res = await fetchURL(`/api/users`, { + method: "POST", + body: JSON.stringify({ + what: "user", + which: [], + data: user, + }), + }); + + if (res.status === 201) { + return res.headers.get("Location"); + } +} + +export async function update(user: user, which = ["all"]) { + await fetchURL(`/api/users/${user.id}`, { + method: "PUT", + body: JSON.stringify({ + what: "user", + which: which, + data: user, + }), + }); +} + +export async function remove(id: number) { + await fetchURL(`/api/users/${id}`, { + method: "DELETE", + }); +} diff --git a/frontend/src/api/utils.ts b/frontend/src/api/utils.ts new file mode 100644 index 00000000..5e7467ab --- /dev/null +++ b/frontend/src/api/utils.ts @@ -0,0 +1,86 @@ +import { useAuthStore } from "@/stores/auth"; +import { renew, logout } from "@/utils/auth"; +import { baseURL } from "@/utils/constants"; +import { encodePath } from "@/utils/url"; + +export async function fetchURL(url: apiUrl, opts: apiOpts, auth = true) { + const authStore = useAuthStore(); + + opts = opts || {}; + opts.headers = opts.headers || {}; + + let { headers, ...rest } = opts; + let res; + try { + res = await fetch(`${baseURL}${url}`, { + headers: { + "X-Auth": authStore.jwt, + ...headers, + }, + ...rest, + }); + } catch { + const error = new Error("000 No connection"); + // @ts-ignore don't know yet how to solve + error.status = 0; + + throw error; + } + + if (auth && res.headers.get("X-Renew-Token") === "true") { + await renew(authStore.jwt); + } + + if (res.status < 200 || res.status > 299) { + const error = new Error(await res.text()); + // @ts-ignore don't know yet how to solve + error.status = res.status; + + if (auth && res.status == 401) { + logout(); + } + + throw error; + } + + return res; +} + +export async function fetchJSON(url: apiUrl, opts?: any) { + const res = await fetchURL(url, opts); + + if (res.status === 200) { + return res.json(); + } else { + throw new Error(res.status.toString()); + } +} + +export function removePrefix(url: apiUrl) { + url = url.split("/").splice(2).join("/"); + + if (url === "") url = "/"; + if (url[0] !== "/") url = "/" + url; + return url; +} + +export function createURL(endpoint: apiUrl, params = {}, auth = true) { + const authStore = useAuthStore(); + + let prefix = baseURL; + if (!prefix.endsWith("/")) { + prefix = prefix + "/"; + } + const url = new URL(prefix + encodePath(endpoint), origin); + + const searchParams: searchParams = { + ...(auth && { auth: authStore.jwt }), + ...params, + }; + + for (const key in searchParams) { + url.searchParams.set(key, searchParams[key]); + } + + return url.toString(); +} diff --git a/frontend/src/types/api.d.ts b/frontend/src/types/api.d.ts new file mode 100644 index 00000000..79b6a23a --- /dev/null +++ b/frontend/src/types/api.d.ts @@ -0,0 +1,37 @@ +type apiUrl = string // Can also be set as a path eg: "path1" | "path2" + +type resourcePath = string + +type apiMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" + +// type apiContent = string | Blob | File +type apiContent = Blob | File | Pick, "read"> | "" + +interface apiOpts { + method?: apiMethod, + headers?: object, + body?: any +} + +interface tusSettings { + retryCount: number +} + +type algo = any + +type inline = any + +interface share { + expire: any, + hash: string, + path: string, + userID: number, + token: string +} + +interface settings { + any +} + +type searchParams = any + diff --git a/frontend/src/types/file.d.ts b/frontend/src/types/file.d.ts new file mode 100644 index 00000000..69709c28 --- /dev/null +++ b/frontend/src/types/file.d.ts @@ -0,0 +1,40 @@ + +interface file { + name: string, + modified: string, + path: string, + subtitles: any[], + isDir: boolean, + size: number, + fullPath: string, + type: uploadType +} + +interface item { + id: number, + path: string, + file: file, + url?: string, + dir?: boolean, + from?: string, + to?: string, + name?: string, + type?: uploadType + overwrite: boolean +} + +type uploadType = "video" | "audio" | "image" | "pdf" | "text" | "blob" + +interface req { + isDir?: boolean +} + +interface uploads { + [key: string]: upload +} + +interface upload { + id: number, + file: file, + type: string +} \ No newline at end of file diff --git a/frontend/src/types/layout.d.ts b/frontend/src/types/layout.d.ts new file mode 100644 index 00000000..0f741a49 --- /dev/null +++ b/frontend/src/types/layout.d.ts @@ -0,0 +1,5 @@ +interface LayoutValue { + prompt: boolean, + confirm: boolean, + action: boolean, +} \ No newline at end of file diff --git a/frontend/src/types/user.d.ts b/frontend/src/types/user.d.ts new file mode 100644 index 00000000..0f465757 --- /dev/null +++ b/frontend/src/types/user.d.ts @@ -0,0 +1,7 @@ +interface user { + id: number, + locale: string, + perm: any +} + +type userKey = keyof user \ No newline at end of file diff --git a/frontend/src/types/utils.d.ts b/frontend/src/types/utils.d.ts new file mode 100644 index 00000000..4a25eb50 --- /dev/null +++ b/frontend/src/types/utils.d.ts @@ -0,0 +1 @@ +type settings = any \ No newline at end of file diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 00000000..151aa685 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file From 6b268c1ee6c1fe46fa3f55fb562533b99754420c Mon Sep 17 00:00:00 2001 From: Joep Date: Fri, 8 Sep 2023 21:08:10 +0200 Subject: [PATCH 05/24] Make the build in docker --- .dockerignore | 40 +++++++++++++++++++++++++++++++---- Dockerfile | 35 +++++++++++++++++++++++------- frontend/src/stores/auth.ts | 1 + frontend/src/stores/upload.ts | 1 + 4 files changed, 65 insertions(+), 12 deletions(-) diff --git a/.dockerignore b/.dockerignore index d1f98f1b..48661e9a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,36 @@ -* -!docker/* -!docker_config.json -!filebrowser \ No newline at end of file +*.db +*.bak +_old +rice-box.go +.idea/ +/filebrowser +/filebrowser.exe +/dist + +.DS_Store +node_modules + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw* +bin/ +build/ + +/frontend/dist/* +!/frontend/dist/.gitkeep + +default.nix \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 40a91a06..d36668fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,30 @@ -FROM alpine:latest +FROM alpine:latest as builder RUN apk --update add ca-certificates \ mailcap \ curl \ - jq + jq \ + libc6-compat \ + make \ + nodejs \ + npm \ + bash \ + ncurses \ + go \ + git +WORKDIR /build + +COPY ./ /build + +RUN go mod download + +RUN make build + +VOLUME /srv +EXPOSE 80 + +FROM alpine:latest as target + +WORKDIR /app COPY healthcheck.sh /healthcheck.sh RUN chmod +x /healthcheck.sh # Make the script executable @@ -10,10 +32,7 @@ RUN chmod +x /healthcheck.sh # Make the script executable HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \ CMD /healthcheck.sh || exit 1 -VOLUME /srv -EXPOSE 80 +COPY docker_config.json .filebrowser.json +COPY --from=builder /build/filebrowser filebrowser -COPY docker_config.json /.filebrowser.json -COPY filebrowser /filebrowser - -ENTRYPOINT [ "/filebrowser" ] \ No newline at end of file +ENTRYPOINT [ "./filebrowser" ] \ No newline at end of file diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index 985010b3..e1eae1b8 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { defineStore } from "pinia"; import dayjs from "dayjs"; import i18n, { detectLocale } from "@/i18n"; diff --git a/frontend/src/stores/upload.ts b/frontend/src/stores/upload.ts index fe3d8d7c..88f23d7a 100644 --- a/frontend/src/stores/upload.ts +++ b/frontend/src/stores/upload.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { defineStore } from "pinia"; import { useFileStore } from "./file"; import { files as api } from "@/api"; From 3e435673ea855310927af2095a56e7787b2427f4 Mon Sep 17 00:00:00 2001 From: Joep Date: Fri, 8 Sep 2023 21:34:42 +0200 Subject: [PATCH 06/24] Resolve Typescript errors --- frontend/src/main.ts | 1 + frontend/vite.config.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 02e9bf07..5e6c699a 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { disableExternal } from "@/utils/constants"; import { createApp } from "vue"; import VueLazyload from "vue-lazyload"; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 7413a5ab..2bb43020 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -8,7 +8,7 @@ import pluginRewriteAll from "vite-plugin-rewrite-all"; const plugins = [ vue(), - VueI18nPlugin(), + VueI18nPlugin({}), legacy({ // defaults already drop IE support targets: ["defaults"], From 22854bf699639aec2bba8678b1e977acb1380d19 Mon Sep 17 00:00:00 2001 From: Joep Date: Fri, 8 Sep 2023 21:37:46 +0200 Subject: [PATCH 07/24] Add docker build cmd in Makefile --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 743dafe8..5c1b7dc9 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,10 @@ build-frontend: ## Build frontend build-backend: ## Build backend $Q $(go) build -ldflags '$(LDFLAGS)' -o . +.PHONY: build-docker +build-docker: ## Build docker + docker build . -t filebrowser:dev + .PHONY: test test: | test-frontend test-backend ## Run all tests From ef5c8b0d8e794d0634cade975c5c70ac73aa8e21 Mon Sep 17 00:00:00 2001 From: Joep Date: Sat, 9 Sep 2023 13:06:04 +0200 Subject: [PATCH 08/24] Converted view/Share.vue to composition api --- frontend/.eslintrc.json | 6 +- frontend/src/api/pub.ts | 46 +-- frontend/src/api/utils.ts | 8 +- frontend/src/main.ts | 2 + frontend/src/stores/file.ts | 12 +- frontend/src/stores/layout.ts | 2 +- frontend/src/types/api.d.ts | 11 +- frontend/src/types/file.d.ts | 41 ++- frontend/src/types/global.d.ts | 3 + frontend/src/types/layout.d.ts | 6 +- frontend/src/views/Files.vue | 5 +- frontend/src/views/Share.vue | 413 ++++++++++------------- frontend/src/views/files/FileListing.vue | 2 +- 13 files changed, 251 insertions(+), 306 deletions(-) diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 607f9b61..ba4fcdfd 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -3,10 +3,14 @@ "env": { "node": true }, + "parser": "@typescript-eslint/parser", "extends": [ - "plugin:vue/vue3-essential", "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:vue/vue3-essential", "@vue/eslint-config-prettier" + ], "rules": { "vue/multi-word-component-names": "off", diff --git a/frontend/src/api/pub.ts b/frontend/src/api/pub.ts index 526e2b0d..6cae6bf1 100644 --- a/frontend/src/api/pub.ts +++ b/frontend/src/api/pub.ts @@ -1,7 +1,7 @@ import { fetchURL, removePrefix, createURL } from "./utils"; import { baseURL } from "@/utils/constants"; -export async function fetch(url: apiUrl, password: string = "") { +export async function fetch(url: ApiUrl, password: string = "") { url = removePrefix(url); const res = await fetchURL( @@ -33,35 +33,35 @@ export async function fetch(url: apiUrl, password: string = "") { } // Is this redundant code? -// export function download(format, hash, token, ...files) { -// let url = `${baseURL}/api/public/dl/${hash}`; +export function download(format: any, hash: string, token: string, ...files: any) { + let url = `${baseURL}/api/public/dl/${hash}`; -// if (files.length === 1) { -// url += encodeURIComponent(files[0]) + "?"; -// } else { -// let arg = ""; + if (files.length === 1) { + url += encodeURIComponent(files[0]) + "?"; + } else { + let arg = ""; -// for (let file of files) { -// arg += encodeURIComponent(file) + ","; -// } + for (let file of files) { + arg += encodeURIComponent(file) + ","; + } -// arg = arg.substring(0, arg.length - 1); -// arg = encodeURIComponent(arg); -// url += `/?files=${arg}&`; -// } + arg = arg.substring(0, arg.length - 1); + arg = encodeURIComponent(arg); + url += `/?files=${arg}&`; + } -// if (format) { -// url += `algo=${format}&`; -// } + if (format) { + url += `algo=${format}&`; + } -// if (token) { -// url += `token=${token}&`; -// } + if (token) { + url += `token=${token}&`; + } -// window.open(url); -// } + window.open(url); +} -export function getDownloadURL(share: share, inline = false) { +export function getDownloadURL(share: IFile, inline = false) { const params = { ...(inline && { inline: "true" }), ...(share.token && { token: share.token }), diff --git a/frontend/src/api/utils.ts b/frontend/src/api/utils.ts index 5e7467ab..5fa7609c 100644 --- a/frontend/src/api/utils.ts +++ b/frontend/src/api/utils.ts @@ -3,7 +3,7 @@ import { renew, logout } from "@/utils/auth"; import { baseURL } from "@/utils/constants"; import { encodePath } from "@/utils/url"; -export async function fetchURL(url: apiUrl, opts: apiOpts, auth = true) { +export async function fetchURL(url: ApiUrl, opts: ApiOpts, auth = true) { const authStore = useAuthStore(); opts = opts || {}; @@ -46,7 +46,7 @@ export async function fetchURL(url: apiUrl, opts: apiOpts, auth = true) { return res; } -export async function fetchJSON(url: apiUrl, opts?: any) { +export async function fetchJSON(url: ApiUrl, opts?: any) { const res = await fetchURL(url, opts); if (res.status === 200) { @@ -56,7 +56,7 @@ export async function fetchJSON(url: apiUrl, opts?: any) { } } -export function removePrefix(url: apiUrl) { +export function removePrefix(url: ApiUrl) { url = url.split("/").splice(2).join("/"); if (url === "") url = "/"; @@ -64,7 +64,7 @@ export function removePrefix(url: apiUrl) { return url; } -export function createURL(endpoint: apiUrl, params = {}, auth = true) { +export function createURL(endpoint: ApiUrl, params = {}, auth = true) { const authStore = useAuthStore(); let prefix = baseURL; diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 5e6c699a..923f1dde 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -13,6 +13,8 @@ import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; import relativeTime from "dayjs/plugin/relativeTime"; import duration from "dayjs/plugin/duration"; +import "./css/styles.css"; + // register dayjs plugins globally dayjs.extend(localizedFormat); dayjs.extend(relativeTime); diff --git a/frontend/src/stores/file.ts b/frontend/src/stores/file.ts index 045f6e2c..616a7f44 100644 --- a/frontend/src/stores/file.ts +++ b/frontend/src/stores/file.ts @@ -3,15 +3,15 @@ import { defineStore } from "pinia"; export const useFileStore = defineStore("file", { // convert to a function state: (): { - req: req, - oldReq: req, + req: IFile | null, + oldReq: IFile | null, reload: boolean, selected: any[], multiple: boolean, isFiles: boolean } => ({ - req: {}, - oldReq: {}, + req: null, + oldReq: null, reload: false, selected: [], multiple: false, @@ -28,7 +28,7 @@ export const useFileStore = defineStore("file", { // return !layoutStore.loading && state.route._value.name === "Files"; // }, isListing: (state) => { - return state.isFiles && state.req.isDir; + return state.isFiles && state?.req?.isDir; }, }, actions: { @@ -36,7 +36,7 @@ export const useFileStore = defineStore("file", { toggleMultiple() { this.multiple = !this.multiple; }, - updateRequest(value: req) { + updateRequest(value: IFile) { this.oldReq = this.req; this.req = value; }, diff --git a/frontend/src/stores/layout.ts b/frontend/src/stores/layout.ts index 1ca27243..aa2afc87 100644 --- a/frontend/src/stores/layout.ts +++ b/frontend/src/stores/layout.ts @@ -7,7 +7,7 @@ export const useLayoutStore = defineStore("layout", { state: (): { loading: boolean, show: string | null | boolean, - showConfirm: boolean | null, + showConfirm: Function | null, showAction: boolean | null, showShell: boolean | null } => ({ diff --git a/frontend/src/types/api.d.ts b/frontend/src/types/api.d.ts index 79b6a23a..fe56a5bd 100644 --- a/frontend/src/types/api.d.ts +++ b/frontend/src/types/api.d.ts @@ -1,14 +1,13 @@ -type apiUrl = string // Can also be set as a path eg: "path1" | "path2" +type ApiUrl = string // Can also be set as a path eg: "path1" | "path2" type resourcePath = string -type apiMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" +type ApiMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" -// type apiContent = string | Blob | File -type apiContent = Blob | File | Pick, "read"> | "" +type ApiContent = Blob | File | Pick, "read"> | "" -interface apiOpts { - method?: apiMethod, +interface ApiOpts { + method?: ApiMethod, headers?: object, body?: any } diff --git a/frontend/src/types/file.d.ts b/frontend/src/types/file.d.ts index 69709c28..b025210e 100644 --- a/frontend/src/types/file.d.ts +++ b/frontend/src/types/file.d.ts @@ -1,5 +1,5 @@ - -interface file { +interface IFile { + index?: number name: string, modified: string, path: string, @@ -7,27 +7,32 @@ interface file { isDir: boolean, size: number, fullPath: string, - type: uploadType + type: uploadType, + items: IFile[] + token?: string, + hash: string, + url?: string } -interface item { - id: number, - path: string, - file: file, - url?: string, - dir?: boolean, - from?: string, - to?: string, - name?: string, - type?: uploadType - overwrite: boolean -} + type uploadType = "video" | "audio" | "image" | "pdf" | "text" | "blob" -interface req { - isDir?: boolean -} +type req = { + path: string + name: string + size: number + extension: string + modified: string + mode: number + isDir: boolean + isSymlink: boolean + type: string + url: string + hash: string + } + + interface uploads { [key: string]: upload diff --git a/frontend/src/types/global.d.ts b/frontend/src/types/global.d.ts index 8f8d3240..5366524c 100644 --- a/frontend/src/types/global.d.ts +++ b/frontend/src/types/global.d.ts @@ -5,4 +5,7 @@ declare global { FileBrowser: any; grecaptcha: any } + interface HTMLAttributes extends HTMLAttributes { + title: any + } } \ No newline at end of file diff --git a/frontend/src/types/layout.d.ts b/frontend/src/types/layout.d.ts index 0f741a49..1d4efe7f 100644 --- a/frontend/src/types/layout.d.ts +++ b/frontend/src/types/layout.d.ts @@ -1,5 +1,5 @@ interface LayoutValue { - prompt: boolean, - confirm: boolean, - action: boolean, + prompt: string, + confirm: Function, + action?: boolean, } \ No newline at end of file diff --git a/frontend/src/views/Files.vue b/frontend/src/views/Files.vue index 51744265..ef50c51a 100644 --- a/frontend/src/views/Files.vue +++ b/frontend/src/views/Files.vue @@ -1,6 +1,6 @@