This commit is contained in:
Joep 2023-09-09 14:12:45 +02:00
parent bf0a2ac9f0
commit 01b1373130
34 changed files with 396 additions and 287 deletions

View File

@ -9,8 +9,8 @@
"build": "vite build", "build": "vite build",
"watch": "vite build --watch", "watch": "vite build --watch",
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +", "clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
"lint": "eslint --ext .vue,.js src/", "lint": "eslint --ext .vue,.ts src/",
"lint:fix": "eslint --ext .vue,.js --fix src/", "lint:fix": "eslint --ext .vue,.ts --fix src/",
"format": "prettier --write ." "format": "prettier --write ."
}, },
"dependencies": { "dependencies": {

View File

@ -5,7 +5,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue'; import { onMounted } from "vue";
onMounted(() => { onMounted(() => {
const loading = document.getElementById("loading"); const loading = document.getElementById("loading");
@ -18,6 +18,5 @@ onMounted(() => {
} }
}, 200); }, 200);
} }
}) });
</script> </script>

View File

@ -5,13 +5,18 @@ import { useAuthStore } from "@/stores/auth";
const ssl = window.location.protocol === "https:"; const ssl = window.location.protocol === "https:";
const protocol = ssl ? "wss:" : "ws:"; const protocol = ssl ? "wss:" : "ws:";
export default function command(url: string, command: string, onmessage: WebSocket["onmessage"], onclose: WebSocket["onclose"]) { export default function command(
url: string,
command: string,
onmessage: WebSocket["onmessage"],
onclose: WebSocket["onclose"]
) {
const authStore = useAuthStore(); const authStore = useAuthStore();
url = removePrefix(url); url = removePrefix(url);
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${authStore.jwt}`; url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${authStore.jwt}`;
let conn = new window.WebSocket(url); const conn = new window.WebSocket(url);
conn.onopen = () => conn.send(command); conn.onopen = () => conn.send(command);
conn.onmessage = onmessage; conn.onmessage = onmessage;
conn.onclose = onclose; conn.onclose = onclose;

View File

@ -8,7 +8,7 @@ export async function fetch(url: apiUrl) {
const res = await fetchURL(`/api/resources${url}`, {}); const res = await fetchURL(`/api/resources${url}`, {});
let data = await res.json(); const data = await res.json();
data.url = `/files${url}`; data.url = `/files${url}`;
if (data.isDir) { if (data.isDir) {
@ -33,8 +33,8 @@ async function resourceAction(url: apiUrl, method: apiMethod, content?: any) {
debugger; debugger;
url = removePrefix(url); url = removePrefix(url);
let opts: apiOpts = { const opts: apiOpts = {
method method,
}; };
if (content) { if (content) {
@ -62,7 +62,7 @@ export function download(format: any, ...files: string[]) {
} else { } else {
let arg = ""; let arg = "";
for (let file of files) { for (const file of files) {
arg += removePrefix(file) + ","; arg += removePrefix(file) + ",";
} }
@ -83,7 +83,12 @@ export function download(format: any, ...files: string[]) {
window.open(url); window.open(url);
} }
export async function post(url: apiUrl, content: apiContent = "", overwrite = false, onupload: Function = () => {}) { export async function post(
url: apiUrl,
content: apiContent = "",
overwrite = false,
onupload: Function = () => {}
) {
// Use the pre-existing API if: // Use the pre-existing API if:
const useResourcesApi = const useResourcesApi =
// a folder is being created // a folder is being created
@ -98,7 +103,12 @@ export async function post(url: apiUrl, content: apiContent = "", overwrite = fa
: postTus(url, content, overwrite, onupload); : postTus(url, content, overwrite, onupload);
} }
async function postResources(url: apiUrl, content: apiContent = "", overwrite = false, onupload: any) { async function postResources(
url: apiUrl,
content: apiContent = "",
overwrite = false,
onupload: any
) {
url = removePrefix(url); url = removePrefix(url);
let bufferContent: ArrayBuffer; let bufferContent: ArrayBuffer;
@ -111,7 +121,7 @@ async function postResources(url: apiUrl, content: apiContent = "", overwrite =
const authStore = useAuthStore(); const authStore = useAuthStore();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let request = new XMLHttpRequest(); const request = new XMLHttpRequest();
request.open( request.open(
"POST", "POST",
`${baseURL}/api/resources${url}?override=${overwrite}`, `${baseURL}/api/resources${url}?override=${overwrite}`,
@ -141,10 +151,15 @@ async function postResources(url: apiUrl, content: apiContent = "", overwrite =
}); });
} }
function moveCopy(items: item[], copy = false, overwrite = false, rename = false) { function moveCopy(
let promises = []; items: item[],
copy = false,
overwrite = false,
rename = false
) {
const promises = [];
for (let item of items) { for (const item of items) {
const from = item.from; const from = item.from;
const to = encodeURIComponent(removePrefix(item.to ?? "")); const to = encodeURIComponent(removePrefix(item.to ?? ""));
const url = `${from}?action=${ const url = `${from}?action=${

View File

@ -12,7 +12,7 @@ export async function fetch(url: ApiUrl, password: string = "") {
false false
); );
let data = await res.json(); const data = await res.json();
data.url = `/share${url}`; data.url = `/share${url}`;
if (data.isDir) { if (data.isDir) {
@ -33,7 +33,12 @@ export async function fetch(url: ApiUrl, password: string = "") {
} }
// Is this redundant code? // Is this redundant code?
export function download(format: any, hash: string, token: string, ...files: any) { export function download(
format: any,
hash: string,
token: string,
...files: any
) {
let url = `${baseURL}/api/public/dl/${hash}`; let url = `${baseURL}/api/public/dl/${hash}`;
if (files.length === 1) { if (files.length === 1) {
@ -41,7 +46,7 @@ export function download(format: any, hash: string, token: string, ...files: any
} else { } else {
let arg = ""; let arg = "";
for (let file of files) { for (const file of files) {
arg += encodeURIComponent(file) + ","; arg += encodeURIComponent(file) + ",";
} }

View File

@ -9,7 +9,7 @@ export default async function search(base: apiUrl, query: string) {
base += "/"; base += "/";
} }
let res = await fetchURL(`/api/search${base}?query=${query}`, {}); const res = await fetchURL(`/api/search${base}?query=${query}`, {});
let data = await res.json(); let data = await res.json();

View File

@ -15,7 +15,12 @@ export async function remove(hash: string) {
}); });
} }
export async function create(url: apiUrl, password = "", expires = "", unit = "hours") { export async function create(
url: apiUrl,
password = "",
expires = "",
unit = "hours"
) {
url = removePrefix(url); url = removePrefix(url);
url = `/api/share${url}`; url = `/api/share${url}`;
if (expires !== "") { if (expires !== "") {

View File

@ -19,7 +19,7 @@ export async function upload(
} }
filePath = removePrefix(filePath); filePath = removePrefix(filePath);
let resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`; const resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`;
await createUpload(resourcePath); await createUpload(resourcePath);
@ -30,7 +30,7 @@ export async function upload(
return false; return false;
} }
return new Promise<void | string>((resolve, reject) => { return new Promise<void | string>((resolve, reject) => {
let upload = new tus.Upload(content, { const upload = new tus.Upload(content, {
uploadUrl: `${baseURL}${resourcePath}`, uploadUrl: `${baseURL}${resourcePath}`,
chunkSize: tusSettings.chunkSize, chunkSize: tusSettings.chunkSize,
retryDelays: computeRetryDelays(tusSettings), retryDelays: computeRetryDelays(tusSettings),
@ -58,7 +58,7 @@ export async function upload(
} }
async function createUpload(resourcePath: resourcePath) { async function createUpload(resourcePath: resourcePath) {
let headResp = await fetchURL(resourcePath, { const headResp = await fetchURL(resourcePath, {
method: "POST", method: "POST",
}); });
if (headResp.status !== 201) { if (headResp.status !== 201) {

View File

@ -9,7 +9,7 @@ export async function fetchURL(url: ApiUrl, opts: ApiOpts, auth = true) {
opts = opts || {}; opts = opts || {};
opts.headers = opts.headers || {}; opts.headers = opts.headers || {};
let { headers, ...rest } = opts; const { headers, ...rest } = opts;
let res; let res;
try { try {
res = await fetch(`${baseURL}${url}`, { res = await fetch(`${baseURL}${url}`, {

View File

@ -1 +1 @@
declare module '*.vue'; declare module "*.vue";

View File

@ -144,7 +144,8 @@ const routes = [
}, },
{ {
path: "/:catchAll(.*)*", path: "/:catchAll(.*)*",
redirect: (to: RouteLocation) => `/files/${[...to.params.catchAll].join("/")}`, redirect: (to: RouteLocation) =>
`/files/${[...to.params.catchAll].join("/")}`,
}, },
]; ];

View File

@ -7,8 +7,8 @@ import { cloneDeep } from "lodash-es";
export const useAuthStore = defineStore("auth", { export const useAuthStore = defineStore("auth", {
// convert to a function // convert to a function
state: (): { state: (): {
user: user | null, user: user | null;
jwt: string jwt: string;
} => ({ } => ({
user: null, user: null,
jwt: "", jwt: "",
@ -34,7 +34,7 @@ export const useAuthStore = defineStore("auth", {
updateUser(value: user) { updateUser(value: user) {
if (typeof value !== "object") return; if (typeof value !== "object") return;
let field: userKey let field: userKey;
for (field in value) { for (field in value) {
if (field === "locale") { if (field === "locale") {
const locale = value[field]; const locale = value[field];

View File

@ -3,12 +3,12 @@ import { defineStore } from "pinia";
export const useFileStore = defineStore("file", { export const useFileStore = defineStore("file", {
// convert to a function // convert to a function
state: (): { state: (): {
req: IFile | null, req: IFile | null;
oldReq: IFile | null, oldReq: IFile | null;
reload: boolean, reload: boolean;
selected: any[], selected: any[];
multiple: boolean, multiple: boolean;
isFiles: boolean isFiles: boolean;
} => ({ } => ({
req: null, req: null,
oldReq: null, oldReq: null,

View File

@ -5,11 +5,11 @@ import { defineStore } from "pinia";
export const useLayoutStore = defineStore("layout", { export const useLayoutStore = defineStore("layout", {
// convert to a function // convert to a function
state: (): { state: (): {
loading: boolean, loading: boolean;
show: string | null | boolean, show: string | null | boolean;
showConfirm: any, showConfirm: any;
showAction: boolean | null, showAction: boolean | null;
showShell: boolean | null showShell: boolean | null;
} => ({ } => ({
loading: false, loading: false,
show: null, show: null,

View File

@ -16,12 +16,12 @@ const beforeUnload = (event: Event) => {
export const useUploadStore = defineStore("upload", { export const useUploadStore = defineStore("upload", {
// convert to a function // convert to a function
state: (): { state: (): {
id: number, id: number;
sizes: any[], sizes: any[];
progress: any[], progress: any[];
queue: any[], queue: any[];
uploads: uploads, uploads: uploads;
error: any error: any;
} => ({ } => ({
id: 0, id: 0,
sizes: [], sizes: [],
@ -75,9 +75,9 @@ export const useUploadStore = defineStore("upload", {
}, },
actions: { actions: {
// no context as first argument, use `this` instead // no context as first argument, use `this` instead
setProgress(obj: { id: number, loaded: boolean }) { setProgress(obj: { id: number; loaded: boolean }) {
// Vue.set(this.progress, id, loaded); // Vue.set(this.progress, id, loaded);
const { id, loaded } = obj const { id, loaded } = obj;
this.progress[id] = loaded; this.progress[id] = loaded;
}, },
setError(error) { setError(error) {
@ -104,10 +104,10 @@ export const useUploadStore = defineStore("upload", {
delete this.uploads[id]; delete this.uploads[id];
}, },
upload(item: item) { upload(item: item) {
let uploadsCount = Object.keys(this.uploads).length; const uploadsCount = Object.keys(this.uploads).length;
let isQueueEmpty = this.queue.length == 0; const isQueueEmpty = this.queue.length == 0;
let isUploadsEmpty = uploadsCount == 0; const isUploadsEmpty = uploadsCount == 0;
if (isQueueEmpty && isUploadsEmpty) { if (isQueueEmpty && isUploadsEmpty) {
window.addEventListener("beforeunload", beforeUnload); window.addEventListener("beforeunload", beforeUnload);
@ -118,7 +118,7 @@ export const useUploadStore = defineStore("upload", {
this.processUploads(); this.processUploads();
}, },
finishUpload(item: item) { finishUpload(item: item) {
this.setProgress({ id: item.id, loaded: (item.file.size > 0) }); this.setProgress({ id: item.id, loaded: item.file.size > 0 });
this.removeJob(item.id); this.removeJob(item.id);
this.processUploads(); this.processUploads();
}, },
@ -147,7 +147,7 @@ export const useUploadStore = defineStore("upload", {
if (item.file.isDir) { if (item.file.isDir) {
await api.post(item.path).catch(this.setError); await api.post(item.path).catch(this.setError);
} else { } else {
let onUpload = throttle( const onUpload = throttle(
(event) => (event) =>
this.setProgress({ this.setProgress({
id: item.id, id: item.id,

View File

@ -1,36 +1,39 @@
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 resourcePath = string;
type ApiMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" type ApiMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type ApiContent = Blob | File | Pick<ReadableStreamDefaultReader<any>, "read"> | "" type ApiContent =
| Blob
| File
| Pick<ReadableStreamDefaultReader<any>, "read">
| "";
interface ApiOpts { interface ApiOpts {
method?: ApiMethod, method?: ApiMethod;
headers?: object, headers?: object;
body?: any body?: any;
} }
interface tusSettings { interface tusSettings {
retryCount: number retryCount: number;
} }
type algo = any type algo = any;
type inline = any type inline = any;
interface share { interface share {
expire: any, expire: any;
hash: string, hash: string;
path: string, path: string;
userID: number, userID: number;
token: string token: string;
} }
interface settings { interface settings {
any any;
} }
type searchParams = any type searchParams = any;

View File

@ -1,45 +1,48 @@
interface IFile { interface IFile {
index?: number index?: number;
name: string, name: string;
modified: string, modified: string;
path: string, path: string;
subtitles: any[], subtitles: any[];
isDir: boolean, isDir: boolean;
size: number, size: number;
fullPath: string, fullPath: string;
type: uploadType, type: uploadType;
items: IFile[] items: IFile[];
token?: string, token?: string;
hash: string, hash: string;
url?: string url?: string;
} }
type uploadType =
| "video"
type uploadType = "video" | "audio" | "image" | "pdf" | "text" | "blob" | "textImmutable" | "audio"
| "image"
| "pdf"
| "text"
| "blob"
| "textImmutable";
type req = { type req = {
path: string path: string;
name: string name: string;
size: number size: number;
extension: string extension: string;
modified: string modified: string;
mode: number mode: number;
isDir: boolean isDir: boolean;
isSymlink: boolean isSymlink: boolean;
type: string type: string;
url: string url: string;
hash: string hash: string;
} };
interface uploads { interface uploads {
[key: string]: upload [key: string]: upload;
} }
interface upload { interface upload {
id: number, id: number;
file: file, file: file;
type: string type: string;
} }

View File

@ -3,6 +3,6 @@ export {};
declare global { declare global {
interface Window { interface Window {
FileBrowser: any; FileBrowser: any;
grecaptcha: any grecaptcha: any;
} }
} }

View File

@ -1,5 +1,5 @@
interface LayoutValue { interface LayoutValue {
prompt: string, prompt: string;
confirm: Function, confirm: any;
action?: boolean, action?: boolean;
} }

View File

@ -1,7 +1,7 @@
interface user { interface user {
id: number, id: number;
locale: string, locale: string;
perm: any perm: any;
} }
type userKey = keyof user type userKey = keyof user;

View File

@ -1 +1 @@
type settings = any type settings = any;

View File

@ -5,7 +5,7 @@ import { baseURL } from "./constants";
export function parseToken(token: string) { export function parseToken(token: string) {
// falsy or malformed jwt will throw InvalidTokenError // falsy or malformed jwt will throw InvalidTokenError
const data = jwt_decode<{[key:string]: any, user: user}>(token); const data = jwt_decode<{ [key: string]: any; user: user }>(token);
document.cookie = `auth=${token}; Path=/; SameSite=Strict;`; document.cookie = `auth=${token}; Path=/; SameSite=Strict;`;
@ -27,7 +27,11 @@ export async function validateLogin() {
} }
} }
export async function login(username: string, password: string, recaptcha: string) { export async function login(
username: string,
password: string,
recaptcha: string
) {
const data = { username, password, recaptcha }; const data = { username, password, recaptcha };
const res = await fetch(`${baseURL}/api/login`, { const res = await fetch(`${baseURL}/api/login`, {
@ -87,6 +91,6 @@ export function logout() {
const authStore = useAuthStore(); const authStore = useAuthStore();
authStore.clearUser(); authStore.clearUser();
localStorage.setItem("jwt", ''); localStorage.setItem("jwt", "");
router.push({ path: "/login" }); router.push({ path: "/login" });
} }

View File

@ -1,5 +1,7 @@
function loading(button: string) { function loading(button: string) {
let el: HTMLButtonElement | null = document.querySelector(`#${button}-button > i`); const el: HTMLButtonElement | null = document.querySelector(
`#${button}-button > i`
);
if (el === undefined || el === null) { if (el === undefined || el === null) {
console.log("Error getting button " + button); // eslint-disable-line console.log("Error getting button " + button); // eslint-disable-line
@ -23,7 +25,9 @@ function loading(button: string) {
} }
function done(button: string) { function done(button: string) {
let el: HTMLButtonElement | null = document.querySelector(`#${button}-button > i`); const el: HTMLButtonElement | null = document.querySelector(
`#${button}-button > i`
);
if (el === undefined || el === null) { if (el === undefined || el === null) {
console.log("Error getting button " + button); // eslint-disable-line console.log("Error getting button " + button); // eslint-disable-line
@ -38,13 +42,13 @@ function done(button: string) {
el.innerHTML = el?.dataset?.icon || ""; el.innerHTML = el?.dataset?.icon || "";
el.style.opacity = "1"; el.style.opacity = "1";
} }
}, 100); }, 100);
} }
function success(button: string) { function success(button: string) {
let el: HTMLButtonElement | null = document.querySelector(`#${button}-button > i`); const el: HTMLButtonElement | null = document.querySelector(
`#${button}-button > i`
);
if (el === undefined || el === null) { if (el === undefined || el === null) {
console.log("Error getting button " + button); // eslint-disable-line console.log("Error getting button " + button); // eslint-disable-line

View File

@ -1,5 +1,5 @@
export default function (name: string) { export default function (name: string) {
let re = new RegExp( const re = new RegExp(
"(?:(?:^|.*;\\s*)" + name + "\\s*\\=\\s*([^;]*).*$)|^.*$" "(?:(?:^|.*;\\s*)" + name + "\\s*\\=\\s*([^;]*).*$)|^.*$"
); );
return document.cookie.replace(re, "$1"); return document.cookie.replace(re, "$1");

View File

@ -4,7 +4,7 @@ export default function getRule(rules: any) {
} }
let result = null; let result = null;
let find = Array.prototype.find; const find = Array.prototype.find;
find.call(document.styleSheets, (styleSheet) => { find.call(document.styleSheets, (styleSheet) => {
result = find.call(styleSheet.cssRules, (cssRule) => { result = find.call(styleSheet.cssRules, (cssRule) => {

View File

@ -6,21 +6,21 @@ export function checkConflict(files: file[], items: item[]) {
items = []; items = [];
} }
let folder_upload = files[0].fullPath !== undefined; const folder_upload = files[0].fullPath !== undefined;
let conflict = false; let conflict = false;
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
let file = files[i]; const file = files[i];
let name = file.name; let name = file.name;
if (folder_upload) { if (folder_upload) {
let dirs = file.fullPath.split("/"); const dirs = file.fullPath.split("/");
if (dirs.length > 1) { if (dirs.length > 1) {
name = dirs[0]; name = dirs[0];
} }
} }
let res = items.findIndex(function hasConflict(element) { const res = items.findIndex(function hasConflict(element) {
// @ts-ignore Don't know what this does // @ts-ignore Don't know what this does
return element.name === this; return element.name === this;
}, name); }, name);
@ -34,13 +34,13 @@ export function checkConflict(files: file[], items: item[]) {
return conflict; return conflict;
} }
export function scanFiles(dt: {[key: string]: any, item: item}) { export function scanFiles(dt: { [key: string]: any; item: item }) {
return new Promise((resolve) => { return new Promise((resolve) => {
let reading = 0; let reading = 0;
const contents: any[] = []; const contents: any[] = [];
if (dt.items !== undefined) { if (dt.items !== undefined) {
for (let item of dt.items) { for (const item of dt.items) {
if ( if (
item.kind === "file" && item.kind === "file" &&
typeof item.webkitGetAsEntry === "function" typeof item.webkitGetAsEntry === "function"
@ -114,9 +114,9 @@ export function handleFiles(files: file[], base: string, overwrite = false) {
const uploadStore = useUploadStore(); const uploadStore = useUploadStore();
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
let id = uploadStore.id; const id = uploadStore.id;
let path = base; let path = base;
let file = files[i]; const file = files[i];
if (file.fullPath !== undefined) { if (file.fullPath !== undefined) {
path += url.encodePath(file.fullPath); path += url.encodePath(file.fullPath);

View File

@ -1,5 +1,5 @@
export function removeLastDir(url: string) { export function removeLastDir(url: string) {
var arr = url.split("/"); const arr = url.split("/");
if (arr.pop() === "") { if (arr.pop() === "") {
arr.pop(); arr.pop();
} }

View File

@ -14,13 +14,13 @@ import HeaderBar from "@/components/header/HeaderBar.vue";
import { computed } from "vue"; import { computed } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n({}) const { t } = useI18n({});
const errors: { const errors: {
[key: string]: { [key: string]: {
icon: string, icon: string;
message: string message: string;
} };
} = { } = {
0: { 0: {
icon: "cloud_off", icon: "cloud_off",
@ -40,11 +40,9 @@ const errors: {
}, },
}; };
const props = defineProps(["errorCode", "showHeader"]) const props = defineProps(["errorCode", "showHeader"]);
const info = computed(() => { const info = computed(() => {
return errors[props.errorCode] ? errors [props.errorCode] : errors[500] return errors[props.errorCode] ? errors[props.errorCode] : errors[500];
}) });
</script> </script>

View File

@ -1,6 +1,10 @@
<template> <template>
<div> <div>
<header-bar v-if="error || fileStore.req?.type === null" showMenu showLogo /> <header-bar
v-if="error || fileStore.req?.type === null"
showMenu
showLogo
/>
<breadcrumbs base="/files" /> <breadcrumbs base="/files" />
@ -20,7 +24,15 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, defineAsyncComponent, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from "vue"; import {
computed,
defineAsyncComponent,
onBeforeUnmount,
onMounted,
onUnmounted,
ref,
watch,
} from "vue";
import { files as api } from "@/api"; import { files as api } from "@/api";
import { useFileStore } from "@/stores/file"; import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
@ -32,23 +44,22 @@ import Errors from "@/views/Errors.vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
const layoutStore = useLayoutStore() const layoutStore = useLayoutStore();
const fileStore = useFileStore() const fileStore = useFileStore();
const uploadStore = useUploadStore() const uploadStore = useUploadStore();
const route = useRoute()
const router = useRouter()
const route = useRoute();
const router = useRouter();
const { t } = useI18n({}); const { t } = useI18n({});
const clean = (path: string) => { const clean = (path: string) => {
return path.endsWith("/") ? path.slice(0, -1) : path; return path.endsWith("/") ? path.slice(0, -1) : path;
} };
const Editor = defineAsyncComponent(() => import("@/views/files/Editor.vue")); const Editor = defineAsyncComponent(() => import("@/views/files/Editor.vue"));
const error = ref<any | null>(null) const error = ref<any | null>(null);
const width = computed(() => window.innerWidth) const width = computed(() => window.innerWidth);
const currentView = computed(() => { const currentView = computed(() => {
if (fileStore.req?.type == undefined) { if (fileStore.req?.type == undefined) {
@ -65,18 +76,18 @@ const currentView = computed(() => {
} else { } else {
return "preview"; return "preview";
} }
}) });
// Define hooks // Define hooks
onMounted(() => { onMounted(() => {
fetchData(); fetchData();
fileStore.isFiles = true; fileStore.isFiles = true;
window.addEventListener("keydown", keyEvent); window.addEventListener("keydown", keyEvent);
}) });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener("keydown", keyEvent); window.removeEventListener("keydown", keyEvent);
}) });
onUnmounted(() => { onUnmounted(() => {
fileStore.isFiles = false; fileStore.isFiles = false;
@ -84,20 +95,18 @@ onUnmounted(() => {
layoutStore.toggleShell(); layoutStore.toggleShell();
} }
fileStore.updateRequest(null); fileStore.updateRequest(null);
}) });
watch(route, () => fetchData()) watch(route, () => fetchData());
// @ts-ignore // @ts-ignore
watch(fileStore.reload, (val) => { watch(fileStore.reload, (val) => {
if (val) { if (val) {
fetchData(); fetchData();
} }
} });
)
watch(uploadStore.error, (newValue, oldValue) => { watch(uploadStore.error, (newValue, oldValue) => {
newValue && newValue !== oldValue && layoutStore.showError(); newValue && newValue !== oldValue && layoutStore.showError();
}) });
// Define functions // Define functions
@ -118,10 +127,7 @@ const fetchData = async () => {
try { try {
const res = await api.fetch(url); const res = await api.fetch(url);
if ( if (clean(res.path) !== clean(`/${[...route.params.path].join("/")}`)) {
clean(res.path) !==
clean(`/${[...route.params.path].join("/")}`)
) {
throw new Error("Data Mismatch!"); throw new Error("Data Mismatch!");
} }
@ -132,11 +138,11 @@ const fetchData = async () => {
} finally { } finally {
layoutStore.loading = false; layoutStore.loading = false;
} }
} };
const keyEvent = (event: KeyboardEvent) => { const keyEvent = (event: KeyboardEvent) => {
if (event.key === "F1") { if (event.key === "F1") {
event.preventDefault(); event.preventDefault();
layoutStore.showHover("help"); layoutStore.showHover("help");
} }
} };
</script> </script>

View File

@ -6,7 +6,11 @@
<sidebar></sidebar> <sidebar></sidebar>
<main> <main>
<router-view></router-view> <router-view></router-view>
<shell v-if="enableExec && authStore.isLoggedIn && authStore.user?.perm.execute" /> <shell
v-if="
enableExec && authStore.isLoggedIn && authStore.user?.perm.execute
"
/>
</main> </main>
<prompts></prompts> <prompts></prompts>
<upload-files></upload-files> <upload-files></upload-files>
@ -37,6 +41,5 @@ watch(route, (newval, oldval) => {
if (layoutStore.show !== "success") { if (layoutStore.show !== "success") {
layoutStore.closeHovers(); layoutStore.closeHovers();
} }
}) });
</script> </script>

View File

@ -35,9 +35,7 @@
/> />
<p @click="toggleMode" v-if="signup"> <p @click="toggleMode" v-if="signup">
{{ {{ createMode ? t("login.loginInstead") : t("login.createAnAccount") }}
createMode ? t("login.loginInstead") : t("login.createAnAccount")
}}
</p> </p>
</form> </form>
</div> </div>
@ -57,18 +55,17 @@ import { useI18n } from "vue-i18n";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
// Define refs // Define refs
const createMode = ref<boolean>(false) const createMode = ref<boolean>(false);
const error = ref<string>("") const error = ref<string>("");
const username = ref<string>("") const username = ref<string>("");
const password = ref<string>("") const password = ref<string>("");
const passwordConfirm = ref<string>("") const passwordConfirm = ref<string>("");
const route = useRoute() const route = useRoute();
const router = useRouter() const router = useRouter();
const { t } = useI18n({}) const { t } = useI18n({});
// Define functions // Define functions
const toggleMode = () => createMode.value = !createMode.value; const toggleMode = () => (createMode.value = !createMode.value);
const submit = async (event: Event) => { const submit = async (event: Event) => {
event.preventDefault(); event.preventDefault();
@ -112,7 +109,7 @@ const submit = async (event: Event) => {
error.value = t("login.wrongCredentials"); error.value = t("login.wrongCredentials");
} }
} }
} };
// Run hooks // Run hooks
onMounted(() => { onMounted(() => {
@ -123,6 +120,5 @@ onMounted(() => {
sitekey: recaptchaKey, sitekey: recaptchaKey,
}); });
}); });
}) });
</script> </script>

View File

@ -61,8 +61,6 @@ const { t } = useI18n();
const authStore = useAuthStore(); const authStore = useAuthStore();
const layoutStore = useLayoutStore(); const layoutStore = useLayoutStore();
const user = computed(() => authStore.user) const user = computed(() => authStore.user);
const loading = computed(() => layoutStore.loading) const loading = computed(() => layoutStore.loading);
</script> </script>

View File

@ -3,13 +3,27 @@
<header-bar showMenu showLogo> <header-bar showMenu showLogo>
<title /> <title />
<action v-if="fileStore.selectedCount" icon="file_download" :label="t('buttons.download')" @action="download" <action
:counter="fileStore.selectedCount" /> v-if="fileStore.selectedCount"
<button v-if="isSingleFile()" class="action copy-clipboard" :data-clipboard-text="linkSelected()" icon="file_download"
:aria-label="t('buttons.copyDownloadLinkToClipboard')" :data-title="t('buttons.copyDownloadLinkToClipboard')"> :label="t('buttons.download')"
@action="download"
:counter="fileStore.selectedCount"
/>
<button
v-if="isSingleFile()"
class="action copy-clipboard"
:data-clipboard-text="linkSelected()"
:aria-label="t('buttons.copyDownloadLinkToClipboard')"
:data-title="t('buttons.copyDownloadLinkToClipboard')"
>
<i class="material-icons">content_paste</i> <i class="material-icons">content_paste</i>
</button> </button>
<action icon="check_circle" :label="t('buttons.selectMultiple')" @action="toggleMultipleSelection" /> <action
icon="check_circle"
:label="t('buttons.selectMultiple')"
@action="toggleMultipleSelection"
/>
</header-bar> </header-bar>
<breadcrumbs :base="'/share/' + hash" /> <breadcrumbs :base="'/share/' + hash" />
@ -35,12 +49,21 @@
</div> </div>
<div class="card-content"> <div class="card-content">
<input v-focus type="password" :placeholder="t('login.password')" v-model="password" <input
@keyup.enter="fetchData" /> v-focus
type="password"
:placeholder="t('login.password')"
v-model="password"
@keyup.enter="fetchData"
/>
</div> </div>
<div class="card-action"> <div class="card-action">
<button class="button button--flat" @click="fetchData" :aria-label="t('buttons.submit')" <button
:data-title="t('buttons.submit')"> class="button button--flat"
@click="fetchData"
:aria-label="t('buttons.submit')"
:data-title="t('buttons.submit')"
>
{{ t("buttons.submit") }} {{ t("buttons.submit") }}
</button> </button>
</div> </div>
@ -73,12 +96,19 @@
<div class="share__box__element share__box__center"> <div class="share__box__element share__box__center">
<a target="_blank" :href="link" class="button button--flat"> <a target="_blank" :href="link" class="button button--flat">
<div> <div>
<i class="material-icons">file_download</i>{{ t("buttons.download") }} <i class="material-icons">file_download</i
>{{ t("buttons.download") }}
</div> </div>
</a> </a>
<a target="_blank" :href="inlineLink" class="button button--flat" v-if="!req.isDir"> <a
target="_blank"
:href="inlineLink"
class="button button--flat"
v-if="!req.isDir"
>
<div> <div>
<i class="material-icons">open_in_new</i>{{ t("buttons.openFile") }} <i class="material-icons">open_in_new</i
>{{ t("buttons.openFile") }}
</div> </div>
</a> </a>
</div> </div>
@ -86,31 +116,59 @@
<qrcode-vue :value="link" :size="200" level="M"></qrcode-vue> <qrcode-vue :value="link" :size="200" level="M"></qrcode-vue>
</div> </div>
</div> </div>
<div v-if="req.isDir && req.items.length > 0" class="share__box share__box__items"> <div
v-if="req.isDir && req.items.length > 0"
class="share__box share__box__items"
>
<div class="share__box__header" v-if="req.isDir"> <div class="share__box__header" v-if="req.isDir">
{{ t("files.files") }} {{ t("files.files") }}
</div> </div>
<div id="listing" class="list file-icons"> <div id="listing" class="list file-icons">
<item v-for="item in req.items.slice(0, showLimit)" :key="base64(item.name)" v-bind:index="item.index" <item
v-bind:name="item.name" v-bind:isDir="item.isDir" v-bind:url="item.url" v-bind:modified="item.modified" v-for="item in req.items.slice(0, showLimit)"
v-bind:type="item.type" v-bind:size="item.size" readOnly> :key="base64(item.name)"
v-bind:index="item.index"
v-bind:name="item.name"
v-bind:isDir="item.isDir"
v-bind:url="item.url"
v-bind:modified="item.modified"
v-bind:type="item.type"
v-bind:size="item.size"
readOnly
>
</item> </item>
<div v-if="req.items.length > showLimit" class="item" @click="showLimit += 100"> <div
v-if="req.items.length > showLimit"
class="item"
@click="showLimit += 100"
>
<div> <div>
<p class="name">+ {{ req.items.length - showLimit }}</p> <p class="name">+ {{ req.items.length - showLimit }}</p>
</div> </div>
</div> </div>
<div :class="{ active: fileStore.multiple }" id="multiple-selection"> <div
:class="{ active: fileStore.multiple }"
id="multiple-selection"
>
<p>{{ t("files.multipleSelectionEnabled") }}</p> <p>{{ t("files.multipleSelectionEnabled") }}</p>
<div @click="() => (fileStore.multiple = false)" tabindex="0" role="button" :data-title="t('files.clear')" <div
:aria-label="t('files.clear')" class="action"> @click="() => (fileStore.multiple = false)"
tabindex="0"
role="button"
:data-title="t('files.clear')"
:aria-label="t('files.clear')"
class="action"
>
<i class="material-icons">clear</i> <i class="material-icons">clear</i>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-else-if="req.isDir && req.items.length === 0" class="share__box share__box__items"> <div
v-else-if="req.isDir && req.items.length === 0"
class="share__box share__box__items"
>
<h2 class="message"> <h2 class="message">
<i class="material-icons">sentiment_dissatisfied</i> <i class="material-icons">sentiment_dissatisfied</i>
<span>{{ t("files.lonely") }}</span> <span>{{ t("files.lonely") }}</span>
@ -140,49 +198,57 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const error = ref<null | any>(null) const error = ref<null | any>(null);
const showLimit = ref<number>(100) const showLimit = ref<number>(100);
const password = ref<string>("") const password = ref<string>("");
const attemptedPasswordLogin = ref<boolean>(false) const attemptedPasswordLogin = ref<boolean>(false);
const hash = ref<any>(null) const hash = ref<any>(null);
const token = ref<any>(null) const token = ref<any>(null);
const clip = ref<any>(null) const clip = ref<any>(null);
const { t } = useI18n({}) const { t } = useI18n({});
const route = useRoute() const route = useRoute();
const fileStore = useFileStore() const fileStore = useFileStore();
const layoutStore = useLayoutStore() const layoutStore = useLayoutStore();
watch(route, () => { watch(route, () => {
showLimit.value = 100 showLimit.value = 100;
fetchData(); fetchData();
}) });
const req = computed(() => fileStore.req) const req = computed(() => fileStore.req);
// Define computes // Define computes
const icon = computed(() => { const icon = computed(() => {
if (req.value === null) return "insert_drive_file" if (req.value === null) return "insert_drive_file";
if (req.value.isDir) return "folder"; if (req.value.isDir) return "folder";
if (req.value.type === "image") return "insert_photo"; if (req.value.type === "image") return "insert_photo";
if (req.value.type === "audio") return "volume_up"; if (req.value.type === "audio") return "volume_up";
if (req.value.type === "video") return "movie"; if (req.value.type === "video") return "movie";
return "insert_drive_file"; return "insert_drive_file";
}) });
const link = computed(() => (req.value ? api.getDownloadURL(req.value) : "")) const link = computed(() => (req.value ? api.getDownloadURL(req.value) : ""));
const inlineLink = computed(() => (req.value ? api.getDownloadURL(req.value, true) : "")) const inlineLink = computed(() =>
req.value ? api.getDownloadURL(req.value, true) : ""
);
const humanSize = computed(() => { const humanSize = computed(() => {
if (req.value) { if (req.value) {
return (req.value.isDir ? req.value.items.length : filesize(req.value.size ?? 0)) return req.value.isDir
? req.value.items.length
: filesize(req.value.size ?? 0);
} else { } else {
return "" return "";
} }
}) });
const humanTime = computed(() => dayjs(req.value?.modified).fromNow()) const humanTime = computed(() => dayjs(req.value?.modified).fromNow());
const modTime = computed(() => (req.value ? new Date(Date.parse(req.value.modified)).toLocaleString() : new Date())) const modTime = computed(() =>
req.value
? new Date(Date.parse(req.value.modified)).toLocaleString()
: new Date()
);
// Functions // Functions
const base64 = (name: any) => Base64.encodeURI(name); const base64 = (name: any) => Base64.encodeURI(name);
@ -207,7 +273,6 @@ const fetchData = async () => {
let file = await api.fetch(url, password.value); let file = await api.fetch(url, password.value);
file.hash = hash.value; file.hash = hash.value;
token.value = file.token || ""; token.value = file.token || "";
fileStore.updateRequest(file); fileStore.updateRequest(file);
@ -217,7 +282,7 @@ const fetchData = async () => {
} finally { } finally {
layoutStore.loading = false; layoutStore.loading = false;
} }
} };
const keyEvent = (event: KeyboardEvent) => { const keyEvent = (event: KeyboardEvent) => {
if (event.key === "Escape") { if (event.key === "Escape") {
@ -227,13 +292,15 @@ const keyEvent = (event: KeyboardEvent) => {
fileStore.selected = []; fileStore.selected = [];
} }
} }
} };
const toggleMultipleSelection = () => { const toggleMultipleSelection = () => {
// toggle // toggle
} };
const isSingleFile = () => fileStore.selectedCount === 1 && !req.value?.items[fileStore.selected[0]].isDir const isSingleFile = () =>
fileStore.selectedCount === 1 &&
!req.value?.items[fileStore.selected[0]].isDir;
const download = () => { const download = () => {
if (isSingleFile()) { if (isSingleFile()) {
@ -248,7 +315,7 @@ const download = () => {
layoutStore.showHover({ layoutStore.showHover({
prompt: "download", prompt: "download",
confirm: (format: any) => { confirm: (format: any) => {
if (req.value === null) return false if (req.value === null) return false;
layoutStore.closeHovers(); layoutStore.closeHovers();
let files: string[] = []; let files: string[] = [];
@ -261,35 +328,32 @@ const download = () => {
api.download(format, hash.value, token.value, ...files); api.download(format, hash.value, token.value, ...files);
}, },
}); });
} };
const linkSelected = () => { const linkSelected = () => {
return isSingleFile() && req.value return isSingleFile() && req.value
// @ts-ignore ? // @ts-ignore
? api.getDownloadURL({ api.getDownloadURL({
hash: hash.value, hash: hash.value,
path: req.value.items[fileStore.selected[0]].path, path: req.value.items[fileStore.selected[0]].path,
}) })
: ""; : "";
} };
onMounted(async () => { onMounted(async () => {
// Created // Created
hash.value = route.params.path[0]; hash.value = route.params.path[0];
await fetchData(); await fetchData();
window.addEventListener("keydown", keyEvent); window.addEventListener("keydown", keyEvent);
clip.value = new Clipboard(".copy-clipboard"); clip.value = new Clipboard(".copy-clipboard");
clip.value.on("success", () => { clip.value.on("success", () => {
// $showSuccess(this.t("success.linkCopied")); // $showSuccess(this.t("success.linkCopied"));
}); });
}) });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener("keydown", keyEvent); window.removeEventListener("keydown", keyEvent);
clip.value.destroy(); clip.value.destroy();
}) });
</script> </script>