chore: add prettier frontent linter

This commit is contained in:
Oleg Lobanov 2021-03-21 12:51:58 +01:00
parent a721dc1f31
commit c44b37c50c
No known key found for this signature in database
GPG Key ID: 7CC64E41212621B0
73 changed files with 18898 additions and 4499 deletions

View File

@ -1,5 +1,3 @@
module.exports = { module.exports = {
presets: [ presets: ["@vue/app"],
'@vue/app' };
]
}

16687
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,9 @@
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean", "build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean",
"watch": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --watch --no-clean", "lint": "npx vue-cli-service lint --no-fix",
"lint": "vue-cli-service lint --fix" "fix": "npx vue-cli-service lint",
"watch": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --watch --no-clean"
}, },
"dependencies": { "dependencies": {
"ace-builds": "^1.4.7", "ace-builds": "^1.4.7",
@ -29,11 +30,14 @@
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^4.1.2", "@vue/cli-plugin-babel": "^4.1.2",
"@vue/cli-plugin-eslint": "^4.1.1", "@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "^4.1.2", "@vue/cli-service": "^4.1.2",
"babel-eslint": "^10.0.3", "@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-vue": "^6.1.2", "eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^6.2.2",
"prettier": "^2.2.1",
"vue-template-compiler": "^2.6.10" "vue-template-compiler": "^2.6.10"
}, },
"eslintConfig": { "eslintConfig": {
@ -43,7 +47,8 @@
}, },
"extends": [ "extends": [
"plugin:vue/essential", "plugin:vue/essential",
"eslint:recommended" "eslint:recommended",
"@vue/prettier"
], ],
"rules": {}, "rules": {},
"parserOptions": { "parserOptions": {

View File

@ -6,18 +6,18 @@
<script> <script>
export default { export default {
name: 'app', name: "app",
mounted() { mounted() {
const loading = document.getElementById('loading') const loading = document.getElementById("loading");
loading.classList.add('done') loading.classList.add("done");
setTimeout(function () { setTimeout(function () {
loading.parentNode.removeChild(loading) loading.parentNode.removeChild(loading);
}, 200) }, 200);
} },
} };
</script> </script>
<style> <style>
@import './css/styles.css'; @import "./css/styles.css";
</style> </style>

View File

@ -1,16 +1,16 @@
import { removePrefix } from './utils' import { removePrefix } from "./utils";
import { baseURL } from '@/utils/constants' import { baseURL } from "@/utils/constants";
import store from '@/store' import store from "@/store";
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, command, onmessage, onclose) { export default function command(url, command, onmessage, onclose) {
url = removePrefix(url) url = removePrefix(url);
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}` url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}`;
let conn = new window.WebSocket(url) let 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

@ -1,147 +1,156 @@
import { fetchURL, removePrefix } from './utils' import { fetchURL, removePrefix } from "./utils";
import { baseURL } from '@/utils/constants' import { baseURL } from "@/utils/constants";
import store from '@/store' import store from "@/store";
export async function fetch(url) { export async function fetch(url) {
url = removePrefix(url) url = removePrefix(url);
const res = await fetchURL(`/api/resources${url}`, {}) const res = await fetchURL(`/api/resources${url}`, {});
if (res.status === 200) { if (res.status === 200) {
let data = await res.json() let data = await res.json();
data.url = `/files${url}` data.url = `/files${url}`;
if (data.isDir) { if (data.isDir) {
if (!data.url.endsWith('/')) data.url += '/' if (!data.url.endsWith("/")) data.url += "/";
data.items = data.items.map((item, index) => { data.items = data.items.map((item, index) => {
item.index = index item.index = index;
item.url = `${data.url}${encodeURIComponent(item.name)}` item.url = `${data.url}${encodeURIComponent(item.name)}`;
if (item.isDir) { if (item.isDir) {
item.url += '/' item.url += "/";
} }
return item return item;
}) });
} }
return data return data;
} else { } else {
throw new Error(res.status) throw new Error(res.status);
} }
} }
async function resourceAction(url, method, content) { async function resourceAction(url, method, content) {
url = removePrefix(url) url = removePrefix(url);
let opts = { method } let opts = { method };
if (content) { if (content) {
opts.body = content opts.body = content;
} }
const res = await fetchURL(`/api/resources${url}`, opts) const res = await fetchURL(`/api/resources${url}`, opts);
if (res.status !== 200) { if (res.status !== 200) {
throw new Error(await res.text()) throw new Error(await res.text());
} else { } else {
return res return res;
} }
} }
export async function remove(url) { export async function remove(url) {
return resourceAction(url, 'DELETE') return resourceAction(url, "DELETE");
} }
export async function put (url, content = '') { export async function put(url, content = "") {
return resourceAction(url, 'PUT', content) return resourceAction(url, "PUT", content);
} }
export function download(format, ...files) { export function download(format, ...files) {
let url = `${baseURL}/api/raw` let url = `${baseURL}/api/raw`;
if (files.length === 1) { if (files.length === 1) {
url += removePrefix(files[0]) + '?' url += removePrefix(files[0]) + "?";
} else { } else {
let arg = '' let arg = "";
for (let file of files) { for (let file of files) {
arg += removePrefix(file) + ',' arg += removePrefix(file) + ",";
} }
arg = arg.substring(0, arg.length - 1) arg = arg.substring(0, arg.length - 1);
arg = encodeURIComponent(arg) arg = encodeURIComponent(arg);
url += `/?files=${arg}&` url += `/?files=${arg}&`;
} }
if (format) { if (format) {
url += `algo=${format}&` url += `algo=${format}&`;
} }
if (store.state.jwt) { if (store.state.jwt) {
url += `auth=${store.state.jwt}&` url += `auth=${store.state.jwt}&`;
} }
window.open(url) window.open(url);
} }
export async function post (url, content = '', overwrite = false, onupload) { export async function post(url, content = "", overwrite = false, onupload) {
url = removePrefix(url) url = removePrefix(url);
let bufferContent let bufferContent;
if (content instanceof Blob && !['http:', 'https:'].includes(window.location.protocol)) { if (
bufferContent = await new Response(content).arrayBuffer() content instanceof Blob &&
!["http:", "https:"].includes(window.location.protocol)
) {
bufferContent = await new Response(content).arrayBuffer();
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let request = new XMLHttpRequest() let request = new XMLHttpRequest();
request.open('POST', `${baseURL}/api/resources${url}?override=${overwrite}`, true) request.open(
request.setRequestHeader('X-Auth', store.state.jwt) "POST",
`${baseURL}/api/resources${url}?override=${overwrite}`,
true
);
request.setRequestHeader("X-Auth", store.state.jwt);
if (typeof onupload === 'function') { if (typeof onupload === "function") {
request.upload.onprogress = onupload request.upload.onprogress = onupload;
} }
request.onload = () => { request.onload = () => {
if (request.status === 200) { if (request.status === 200) {
resolve(request.responseText) resolve(request.responseText);
} else if (request.status === 409) { } else if (request.status === 409) {
reject(request.status) reject(request.status);
} else { } else {
reject(request.responseText) reject(request.responseText);
}
} }
};
request.onerror = (error) => { request.onerror = (error) => {
reject(error) reject(error);
} };
request.send(bufferContent || content) request.send(bufferContent || content);
}) });
} }
function moveCopy(items, copy = false, overwrite = false, rename = false) { function moveCopy(items, copy = false, overwrite = false, rename = false) {
let promises = [] let promises = [];
for (let item of items) { for (let 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=${copy ? 'copy' : 'rename'}&destination=${to}&override=${overwrite}&rename=${rename}` const url = `${from}?action=${
promises.push(resourceAction(url, 'PATCH')) copy ? "copy" : "rename"
}&destination=${to}&override=${overwrite}&rename=${rename}`;
promises.push(resourceAction(url, "PATCH"));
} }
return Promise.all(promises) return Promise.all(promises);
} }
export function move(items, overwrite = false, rename = false) { export function move(items, overwrite = false, rename = false) {
return moveCopy(items, false, overwrite, rename) return moveCopy(items, false, overwrite, rename);
} }
export function copy(items, overwrite = false, rename = false) { export function copy(items, overwrite = false, rename = false) {
return moveCopy(items, true, overwrite, rename) return moveCopy(items, true, overwrite, rename);
} }
export async function checksum(url, algo) { export async function checksum(url, algo) {
const data = await resourceAction(`${url}?checksum=${algo}`, 'GET') const data = await resourceAction(`${url}?checksum=${algo}`, "GET");
return (await data.json()).checksums[algo] return (await data.json()).checksums[algo];
} }

View File

@ -1,17 +1,9 @@
import * as files from './files' import * as files from "./files";
import * as share from './share' import * as share from "./share";
import * as users from './users' import * as users from "./users";
import * as settings from './settings' import * as settings from "./settings";
import * as pub from './pub' import * as pub from "./pub";
import search from './search' import search from "./search";
import commands from './commands' import commands from "./commands";
export { export { files, share, users, settings, pub, commands, search };
files,
share,
users,
settings,
pub,
commands,
search
}

View File

@ -1,61 +1,61 @@
import { fetchURL, removePrefix } from './utils' import { fetchURL, removePrefix } from "./utils";
import { baseURL } from '@/utils/constants' import { baseURL } from "@/utils/constants";
export async function fetch(url, password = "") { export async function fetch(url, password = "") {
url = removePrefix(url) url = removePrefix(url);
const res = await fetchURL(`/api/public/share${url}`, { const res = await fetchURL(`/api/public/share${url}`, {
headers: {'X-SHARE-PASSWORD': password}, headers: { "X-SHARE-PASSWORD": password },
}) });
if (res.status === 200) { if (res.status === 200) {
let data = await res.json() let data = await res.json();
data.url = `/share${url}` data.url = `/share${url}`;
if (data.isDir) { if (data.isDir) {
if (!data.url.endsWith('/')) data.url += '/' if (!data.url.endsWith("/")) data.url += "/";
data.items = data.items.map((item, index) => { data.items = data.items.map((item, index) => {
item.index = index item.index = index;
item.url = `${data.url}${encodeURIComponent(item.name)}` item.url = `${data.url}${encodeURIComponent(item.name)}`;
if (item.isDir) { if (item.isDir) {
item.url += '/' item.url += "/";
} }
return item return item;
}) });
} }
return data return data;
} else { } else {
throw new Error(res.status) throw new Error(res.status);
} }
} }
export function download(format, hash, token, ...files) { export function download(format, hash, token, ...files) {
let url = `${baseURL}/api/public/dl/${hash}` let url = `${baseURL}/api/public/dl/${hash}`;
if (files.length === 1) { if (files.length === 1) {
url += encodeURIComponent(files[0]) + '?' url += encodeURIComponent(files[0]) + "?";
} else { } else {
let arg = '' let arg = "";
for (let file of files) { for (let file of files) {
arg += encodeURIComponent(file) + ',' arg += encodeURIComponent(file) + ",";
} }
arg = arg.substring(0, arg.length - 1) arg = arg.substring(0, arg.length - 1);
arg = encodeURIComponent(arg) arg = encodeURIComponent(arg);
url += `/?files=${arg}&` url += `/?files=${arg}&`;
} }
if (format) { if (format) {
url += `algo=${format}&` url += `algo=${format}&`;
} }
if (token) { if (token) {
url += `token=${token}&` url += `token=${token}&`;
} }
window.open(url) window.open(url);
} }

View File

@ -1,31 +1,31 @@
import { fetchURL, removePrefix } from './utils' import { fetchURL, removePrefix } from "./utils";
import url from '../utils/url' import url from "../utils/url";
export default async function search(base, query) { export default async function search(base, query) {
base = removePrefix(base) base = removePrefix(base);
query = encodeURIComponent(query) query = encodeURIComponent(query);
if (!base.endsWith('/')) { if (!base.endsWith("/")) {
base += '/' base += "/";
} }
let res = await fetchURL(`/api/search${base}?query=${query}`, {}) let res = await fetchURL(`/api/search${base}?query=${query}`, {});
if (res.status === 200) { if (res.status === 200) {
let data = await res.json() let data = await res.json();
data = data.map((item) => { data = data.map((item) => {
item.url = `/files${base}` + url.encodePath(item.path) item.url = `/files${base}` + url.encodePath(item.path);
if (item.dir) { if (item.dir) {
item.url += '/' item.url += "/";
} }
return item return item;
}) });
return data return data;
} else { } else {
throw Error(res.status) throw Error(res.status);
} }
} }

View File

@ -1,16 +1,16 @@
import { fetchURL, fetchJSON } from './utils' import { fetchURL, fetchJSON } from "./utils";
export function get() { export function get() {
return fetchJSON(`/api/settings`, {}) return fetchJSON(`/api/settings`, {});
} }
export async function update(settings) { export async function update(settings) {
const res = await fetchURL(`/api/settings`, { const res = await fetchURL(`/api/settings`, {
method: 'PUT', method: "PUT",
body: JSON.stringify(settings) body: JSON.stringify(settings),
}) });
if (res.status !== 200) { if (res.status !== 200) {
throw new Error(res.status) throw new Error(res.status);
} }
} }

View File

@ -1,36 +1,36 @@
import { fetchURL, fetchJSON, removePrefix } from './utils' import { fetchURL, fetchJSON, removePrefix } from "./utils";
export async function list() { export async function list() {
return fetchJSON('/api/shares') return fetchJSON("/api/shares");
} }
export async function get(url) { export async function get(url) {
url = removePrefix(url) url = removePrefix(url);
return fetchJSON(`/api/share${url}`) return fetchJSON(`/api/share${url}`);
} }
export async function remove(hash) { export async function remove(hash) {
const res = await fetchURL(`/api/share/${hash}`, { const res = await fetchURL(`/api/share/${hash}`, {
method: 'DELETE' method: "DELETE",
}) });
if (res.status !== 200) { if (res.status !== 200) {
throw new Error(res.status) throw new Error(res.status);
} }
} }
export async function create(url, password = '', expires = '', unit = 'hours') { export async function create(url, password = "", expires = "", unit = "hours") {
url = removePrefix(url) url = removePrefix(url);
url = `/api/share${url}` url = `/api/share${url}`;
if (expires !== '') { if (expires !== "") {
url += `?expires=${expires}&unit=${unit}` url += `?expires=${expires}&unit=${unit}`;
} }
let body = '{}'; let body = "{}";
if (password != '' || expires !== '' || unit !== 'hours') { if (password != "" || expires !== "" || unit !== "hours") {
body = JSON.stringify({password: password, expires: expires, unit: unit}) body = JSON.stringify({ password: password, expires: expires, unit: unit });
} }
return fetchJSON(url, { return fetchJSON(url, {
method: 'POST', method: "POST",
body: body, body: body,
}) });
} }

View File

@ -1,52 +1,51 @@
import { fetchURL, fetchJSON } from './utils' import { fetchURL, fetchJSON } from "./utils";
export async function getAll() { export async function getAll() {
return fetchJSON(`/api/users`, {}) return fetchJSON(`/api/users`, {});
} }
export async function get(id) { export async function get(id) {
return fetchJSON(`/api/users/${id}`, {}) return fetchJSON(`/api/users/${id}`, {});
} }
export async function create(user) { export async function create(user) {
const res = await fetchURL(`/api/users`, { const res = await fetchURL(`/api/users`, {
method: 'POST', method: "POST",
body: JSON.stringify({ body: JSON.stringify({
what: 'user', what: "user",
which: [], which: [],
data: user data: user,
}) }),
}) });
if (res.status === 201) { if (res.status === 201) {
return res.headers.get('Location') return res.headers.get("Location");
} else { } else {
throw new Error(res.status) throw new Error(res.status);
}
} }
} export async function update(user, which = ["all"]) {
export async function update (user, which = ['all']) {
const res = await fetchURL(`/api/users/${user.id}`, { const res = await fetchURL(`/api/users/${user.id}`, {
method: 'PUT', method: "PUT",
body: JSON.stringify({ body: JSON.stringify({
what: 'user', what: "user",
which: which, which: which,
data: user data: user,
}) }),
}) });
if (res.status !== 200) { if (res.status !== 200) {
throw new Error(res.status) throw new Error(res.status);
} }
} }
export async function remove(id) { export async function remove(id) {
const res = await fetchURL(`/api/users/${id}`, { const res = await fetchURL(`/api/users/${id}`, {
method: 'DELETE' method: "DELETE",
}) });
if (res.status !== 200) { if (res.status !== 200) {
throw new Error(res.status) throw new Error(res.status);
} }
} }

View File

@ -1,43 +1,42 @@
import store from '@/store' import store from "@/store";
import { renew } from '@/utils/auth' import { renew } from "@/utils/auth";
import { baseURL } from '@/utils/constants' import { baseURL } from "@/utils/constants";
export async function fetchURL(url, opts) { export async function fetchURL(url, opts) {
opts = opts || {} opts = opts || {};
opts.headers = opts.headers || {} opts.headers = opts.headers || {};
let { headers, ...rest } = opts let { headers, ...rest } = opts;
const res = await fetch(`${baseURL}${url}`, { const res = await fetch(`${baseURL}${url}`, {
headers: { headers: {
'X-Auth': store.state.jwt, "X-Auth": store.state.jwt,
...headers ...headers,
}, },
...rest ...rest,
}) });
if (res.headers.get('X-Renew-Token') === 'true') { if (res.headers.get("X-Renew-Token") === "true") {
await renew(store.state.jwt) await renew(store.state.jwt);
} }
return res return res;
} }
export async function fetchJSON(url, opts) { export async function fetchJSON(url, opts) {
const res = await fetchURL(url, opts) const res = await fetchURL(url, opts);
if (res.status === 200) { if (res.status === 200) {
return res.json() return res.json();
} else { } else {
throw new Error(res.status) throw new Error(res.status);
} }
} }
export function removePrefix(url) { export function removePrefix(url) {
url = url.split('/').splice(2).join('/') url = url.split("/").splice(2).join("/");
if (url === '') url = '/' if (url === "") url = "/";
if (url[0] !== '/') url = '/' + url if (url[0] !== "/") url = "/" + url;
return url return url;
} }

View File

@ -1,11 +1,18 @@
<template> <template>
<div class="breadcrumbs"> <div class="breadcrumbs">
<component :is="element" :to="base || ''" :aria-label="$t('files.home')" :title="$t('files.home')"> <component
:is="element"
:to="base || ''"
:aria-label="$t('files.home')"
:title="$t('files.home')"
>
<i class="material-icons">home</i> <i class="material-icons">home</i>
</component> </component>
<span v-for="(link, index) in items" :key="index"> <span v-for="(link, index) in items" :key="index">
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span> <span class="chevron"
><i class="material-icons">keyboard_arrow_right</i></span
>
<component :is="element" :to="link.url">{{ link.name }}</component> <component :is="element" :to="link.url">{{ link.name }}</component>
</span> </span>
</div> </div>
@ -13,55 +20,56 @@
<script> <script>
export default { export default {
name: 'breadcrumbs', name: "breadcrumbs",
props: [ props: ["base", "noLink"],
'base',
'noLink'
],
computed: { computed: {
items() { items() {
const relativePath = this.$route.path.replace(this.base, '') const relativePath = this.$route.path.replace(this.base, "");
let parts = relativePath.split('/') let parts = relativePath.split("/");
if (parts[0] === '') { if (parts[0] === "") {
parts.shift() parts.shift();
} }
if (parts[parts.length - 1] === '') { if (parts[parts.length - 1] === "") {
parts.pop() parts.pop();
} }
let breadcrumbs = [] let breadcrumbs = [];
for (let i = 0; i < parts.length; i++) { for (let i = 0; i < parts.length; i++) {
if (i === 0) { if (i === 0) {
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: this.base + '/' + parts[i] + '/' }) breadcrumbs.push({
name: decodeURIComponent(parts[i]),
url: this.base + "/" + parts[i] + "/",
});
} else { } else {
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' }) breadcrumbs.push({
name: decodeURIComponent(parts[i]),
url: breadcrumbs[i - 1].url + parts[i] + "/",
});
} }
} }
if (breadcrumbs.length > 3) { if (breadcrumbs.length > 3) {
while (breadcrumbs.length !== 4) { while (breadcrumbs.length !== 4) {
breadcrumbs.shift() breadcrumbs.shift();
} }
breadcrumbs[0].name = '...' breadcrumbs[0].name = "...";
} }
return breadcrumbs return breadcrumbs;
}, },
element() { element() {
if (this.noLink !== undefined) { if (this.noLink !== undefined) {
return 'span' return "span";
} }
return 'router-link' return "router-link";
} },
} },
} };
</script> </script>
<style> <style></style>
</style>

View File

@ -20,7 +20,7 @@
v-model.trim="value" v-model.trim="value"
:aria-label="$t('search.search')" :aria-label="$t('search.search')"
:placeholder="$t('search.search')" :placeholder="$t('search.search')"
> />
</div> </div>
<div id="result" ref="result"> <div id="result" ref="result">
@ -30,7 +30,7 @@
<template v-if="value.length === 0"> <template v-if="value.length === 0">
<div class="boxes"> <div class="boxes">
<h3>{{ $t('search.types') }}</h3> <h3>{{ $t("search.types") }}</h3>
<div> <div>
<div <div
tabindex="0" tabindex="0"
@ -41,7 +41,7 @@
:aria-label="$t('search.' + v.label)" :aria-label="$t('search.' + v.label)"
> >
<i class="material-icons">{{ v.icon }}</i> <i class="material-icons">{{ v.icon }}</i>
<p>{{ $t('search.'+v.label) }}</p> <p>{{ $t("search." + v.label) }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -65,16 +65,16 @@
</template> </template>
<script> <script>
import { mapState, mapGetters, mapMutations } from "vuex" import { mapState, mapGetters, mapMutations } from "vuex";
import url from "@/utils/url" import url from "@/utils/url";
import { search } from "@/api" import { search } from "@/api";
var boxes = { var boxes = {
image: { label: "images", icon: "insert_photo" }, image: { label: "images", icon: "insert_photo" },
audio: { label: "music", icon: "volume_up" }, audio: { label: "music", icon: "volume_up" },
video: { label: "video", icon: "movie" }, video: { label: "video", icon: "movie" },
pdf: { label: "pdf", icon: "picture_as_pdf" } pdf: { label: "pdf", icon: "picture_as_pdf" },
} };
export default { export default {
name: "search", name: "search",
@ -86,111 +86,116 @@ export default {
results: [], results: [],
reload: false, reload: false,
resultsCount: 50, resultsCount: 50,
scrollable: null scrollable: null,
} };
}, },
watch: { watch: {
show(val, old) { show(val, old) {
this.active = val === "search" this.active = val === "search";
if (old === "search" && !this.active) { if (old === "search" && !this.active) {
if (this.reload) { if (this.reload) {
this.setReload(true) this.setReload(true);
} }
document.body.style.overflow = "auto" document.body.style.overflow = "auto";
this.reset() this.reset();
this.value = '' this.value = "";
this.active = false this.active = false;
this.$refs.input.blur() this.$refs.input.blur();
} else if (this.active) { } else if (this.active) {
this.reload = false this.reload = false;
this.$refs.input.focus() this.$refs.input.focus();
document.body.style.overflow = "hidden" document.body.style.overflow = "hidden";
} }
}, },
value() { value() {
if (this.results.length) { if (this.results.length) {
this.reset() this.reset();
}
} }
}, },
},
computed: { computed: {
...mapState(["user", "show"]), ...mapState(["user", "show"]),
...mapGetters(["isListing"]), ...mapGetters(["isListing"]),
boxes() { boxes() {
return boxes return boxes;
}, },
isEmpty() { isEmpty() {
return this.results.length === 0 return this.results.length === 0;
}, },
text() { text() {
if (this.ongoing) { if (this.ongoing) {
return "" return "";
} }
return this.value === '' ? this.$t("search.typeToSearch") : this.$t("search.pressToSearch") return this.value === ""
? this.$t("search.typeToSearch")
: this.$t("search.pressToSearch");
}, },
filteredResults() { filteredResults() {
return this.results.slice(0, this.resultsCount) return this.results.slice(0, this.resultsCount);
} },
}, },
mounted() { mounted() {
this.$refs.result.addEventListener('scroll', event => { this.$refs.result.addEventListener("scroll", (event) => {
if (event.target.offsetHeight + event.target.scrollTop >= event.target.scrollHeight - 100) { if (
this.resultsCount += 50 event.target.offsetHeight + event.target.scrollTop >=
event.target.scrollHeight - 100
) {
this.resultsCount += 50;
} }
}) });
}, },
methods: { methods: {
...mapMutations(["showHover", "closeHovers", "setReload"]), ...mapMutations(["showHover", "closeHovers", "setReload"]),
open() { open() {
this.showHover("search") this.showHover("search");
}, },
close(event) { close(event) {
event.stopPropagation() event.stopPropagation();
event.preventDefault() event.preventDefault();
this.closeHovers() this.closeHovers();
}, },
keyup(event) { keyup(event) {
if (event.keyCode === 27) { if (event.keyCode === 27) {
this.close(event) this.close(event);
return return;
} }
this.results.length = 0 this.results.length = 0;
}, },
init(string) { init(string) {
this.value = `${string} ` this.value = `${string} `;
this.$refs.input.focus() this.$refs.input.focus();
}, },
reset() { reset() {
this.ongoing = false this.ongoing = false;
this.resultsCount = 50 this.resultsCount = 50;
this.results = [] this.results = [];
}, },
async submit(event) { async submit(event) {
event.preventDefault() event.preventDefault();
if (this.value === '') { if (this.value === "") {
return return;
} }
let path = this.$route.path let path = this.$route.path;
if (!this.isListing) { if (!this.isListing) {
path = url.removeLastDir(path) + "/" path = url.removeLastDir(path) + "/";
} }
this.ongoing = true this.ongoing = true;
try { try {
this.results = await search(path, this.value) this.results = await search(path, this.value);
} catch (error) { } catch (error) {
this.$showError(error) this.$showError(error);
} }
this.ongoing = false this.ongoing = false;
} },
} },
} };
</script> </script>

View File

@ -1,12 +1,21 @@
<template> <template>
<div @click="focus" class="shell" ref="scrollable" :class="{ ['shell--hidden']: !showShell}"> <div
@click="focus"
class="shell"
ref="scrollable"
:class="{ ['shell--hidden']: !showShell }"
>
<div v-for="(c, index) in content" :key="index" class="shell__result"> <div v-for="(c, index) in content" :key="index" class="shell__result">
<div class="shell__prompt"><i class="material-icons">chevron_right</i></div> <div class="shell__prompt">
<i class="material-icons">chevron_right</i>
</div>
<pre class="shell__text">{{ c.text }}</pre> <pre class="shell__text">{{ c.text }}</pre>
</div> </div>
<div class="shell__result" :class="{ 'shell__result--hidden': !canInput }"> <div class="shell__result" :class="{ 'shell__result--hidden': !canInput }">
<div class="shell__prompt"><i class="material-icons">chevron_right</i></div> <div class="shell__prompt">
<i class="material-icons">chevron_right</i>
</div>
<pre <pre
tabindex="0" tabindex="0"
ref="input" ref="input"
@ -14,102 +23,103 @@
contenteditable="true" contenteditable="true"
@keydown.prevent.38="historyUp" @keydown.prevent.38="historyUp"
@keydown.prevent.40="historyDown" @keydown.prevent.40="historyDown"
@keypress.prevent.enter="submit" /> @keypress.prevent.enter="submit"
/>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapMutations, mapState, mapGetters } from 'vuex' import { mapMutations, mapState, mapGetters } from "vuex";
import { commands } from '@/api' import { commands } from "@/api";
export default { export default {
name: 'shell', name: "shell",
computed: { computed: {
...mapState([ 'user', 'showShell' ]), ...mapState(["user", "showShell"]),
...mapGetters([ 'isFiles', 'isLogged' ]), ...mapGetters(["isFiles", "isLogged"]),
path: function () { path: function () {
if (this.isFiles) { if (this.isFiles) {
return this.$route.path return this.$route.path;
} }
return '' return "";
} },
}, },
data: () => ({ data: () => ({
content: [], content: [],
history: [], history: [],
historyPos: 0, historyPos: 0,
canInput: true canInput: true,
}), }),
methods: { methods: {
...mapMutations([ 'toggleShell' ]), ...mapMutations(["toggleShell"]),
scroll: function () { scroll: function () {
this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight;
}, },
focus: function () { focus: function () {
this.$refs.input.focus() this.$refs.input.focus();
}, },
historyUp() { historyUp() {
if (this.historyPos > 0) { if (this.historyPos > 0) {
this.$refs.input.innerText = this.history[--this.historyPos] this.$refs.input.innerText = this.history[--this.historyPos];
this.focus() this.focus();
} }
}, },
historyDown() { historyDown() {
if (this.historyPos >= 0 && this.historyPos < this.history.length - 1) { if (this.historyPos >= 0 && this.historyPos < this.history.length - 1) {
this.$refs.input.innerText = this.history[++this.historyPos] this.$refs.input.innerText = this.history[++this.historyPos];
this.focus() this.focus();
} else { } else {
this.historyPos = this.history.length this.historyPos = this.history.length;
this.$refs.input.innerText = '' this.$refs.input.innerText = "";
} }
}, },
submit: function (event) { submit: function (event) {
const cmd = event.target.innerText.trim() const cmd = event.target.innerText.trim();
if (cmd === '') { if (cmd === "") {
return return;
} }
if (cmd === 'clear') { if (cmd === "clear") {
this.content = [] this.content = [];
event.target.innerHTML = '' event.target.innerHTML = "";
return return;
} }
if (cmd === 'exit') { if (cmd === "exit") {
event.target.innerHTML = '' event.target.innerHTML = "";
this.toggleShell() this.toggleShell();
return return;
} }
this.canInput = false this.canInput = false;
event.target.innerHTML = '' event.target.innerHTML = "";
let results = { let results = {
text: `${cmd}\n\n` text: `${cmd}\n\n`,
} };
this.history.push(cmd) this.history.push(cmd);
this.historyPos = this.history.length this.historyPos = this.history.length;
this.content.push(results) this.content.push(results);
commands( commands(
this.path, this.path,
cmd, cmd,
event => { (event) => {
results.text += `${event.data}\n` results.text += `${event.data}\n`;
this.scroll() this.scroll();
}, },
() => { () => {
results.text = results.text.trimEnd() results.text = results.text.trimEnd();
this.canInput = true this.canInput = true;
this.$refs.input.focus() this.$refs.input.focus();
this.scroll() this.scroll();
}
)
}
}
} }
);
},
},
};
</script> </script>

View File

@ -1,82 +1,134 @@
<template> <template>
<nav :class="{ active }"> <nav :class="{ active }">
<template v-if="isLogged"> <template v-if="isLogged">
<router-link class="action" to="/files/" :aria-label="$t('sidebar.myFiles')" :title="$t('sidebar.myFiles')"> <router-link
class="action"
to="/files/"
:aria-label="$t('sidebar.myFiles')"
:title="$t('sidebar.myFiles')"
>
<i class="material-icons">folder</i> <i class="material-icons">folder</i>
<span>{{ $t('sidebar.myFiles') }}</span> <span>{{ $t("sidebar.myFiles") }}</span>
</router-link> </router-link>
<div v-if="user.perm.create"> <div v-if="user.perm.create">
<button @click="$store.commit('showHover', 'newDir')" class="action" :aria-label="$t('sidebar.newFolder')" :title="$t('sidebar.newFolder')"> <button
@click="$store.commit('showHover', 'newDir')"
class="action"
:aria-label="$t('sidebar.newFolder')"
:title="$t('sidebar.newFolder')"
>
<i class="material-icons">create_new_folder</i> <i class="material-icons">create_new_folder</i>
<span>{{ $t('sidebar.newFolder') }}</span> <span>{{ $t("sidebar.newFolder") }}</span>
</button> </button>
<button @click="$store.commit('showHover', 'newFile')" class="action" :aria-label="$t('sidebar.newFile')" :title="$t('sidebar.newFile')"> <button
@click="$store.commit('showHover', 'newFile')"
class="action"
:aria-label="$t('sidebar.newFile')"
:title="$t('sidebar.newFile')"
>
<i class="material-icons">note_add</i> <i class="material-icons">note_add</i>
<span>{{ $t('sidebar.newFile') }}</span> <span>{{ $t("sidebar.newFile") }}</span>
</button> </button>
</div> </div>
<div> <div>
<router-link class="action" to="/settings" :aria-label="$t('sidebar.settings')" :title="$t('sidebar.settings')"> <router-link
class="action"
to="/settings"
:aria-label="$t('sidebar.settings')"
:title="$t('sidebar.settings')"
>
<i class="material-icons">settings_applications</i> <i class="material-icons">settings_applications</i>
<span>{{ $t('sidebar.settings') }}</span> <span>{{ $t("sidebar.settings") }}</span>
</router-link> </router-link>
<button v-if="authMethod == 'json'" @click="logout" class="action" id="logout" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')"> <button
v-if="authMethod == 'json'"
@click="logout"
class="action"
id="logout"
:aria-label="$t('sidebar.logout')"
:title="$t('sidebar.logout')"
>
<i class="material-icons">exit_to_app</i> <i class="material-icons">exit_to_app</i>
<span>{{ $t('sidebar.logout') }}</span> <span>{{ $t("sidebar.logout") }}</span>
</button> </button>
</div> </div>
</template> </template>
<template v-else> <template v-else>
<router-link class="action" to="/login" :aria-label="$t('sidebar.login')" :title="$t('sidebar.login')"> <router-link
class="action"
to="/login"
:aria-label="$t('sidebar.login')"
:title="$t('sidebar.login')"
>
<i class="material-icons">exit_to_app</i> <i class="material-icons">exit_to_app</i>
<span>{{ $t('sidebar.login') }}</span> <span>{{ $t("sidebar.login") }}</span>
</router-link> </router-link>
<router-link v-if="signup" class="action" to="/login" :aria-label="$t('sidebar.signup')" :title="$t('sidebar.signup')"> <router-link
v-if="signup"
class="action"
to="/login"
:aria-label="$t('sidebar.signup')"
:title="$t('sidebar.signup')"
>
<i class="material-icons">person_add</i> <i class="material-icons">person_add</i>
<span>{{ $t('sidebar.signup') }}</span> <span>{{ $t("sidebar.signup") }}</span>
</router-link> </router-link>
</template> </template>
<p class="credits"> <p class="credits">
<span> <span>
<span v-if="disableExternal">File Browser</span> <span v-if="disableExternal">File Browser</span>
<a v-else rel="noopener noreferrer" target="_blank" href="https://github.com/filebrowser/filebrowser">File Browser</a> <a
v-else
rel="noopener noreferrer"
target="_blank"
href="https://github.com/filebrowser/filebrowser"
>File Browser</a
>
<span> {{ version }}</span> <span> {{ version }}</span>
</span> </span>
<span><a @click="help">{{ $t('sidebar.help') }}</a></span> <span
><a @click="help">{{ $t("sidebar.help") }}</a></span
>
</p> </p>
</nav> </nav>
</template> </template>
<script> <script>
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from "vuex";
import * as auth from '@/utils/auth' import * as auth from "@/utils/auth";
import { version, signup, disableExternal, noAuth, authMethod } from '@/utils/constants' import {
version,
signup,
disableExternal,
noAuth,
authMethod,
} from "@/utils/constants";
export default { export default {
name: 'sidebar', name: "sidebar",
computed: { computed: {
...mapState([ 'user' ]), ...mapState(["user"]),
...mapGetters([ 'isLogged' ]), ...mapGetters(["isLogged"]),
active() { active() {
return this.$store.state.show === 'sidebar' return this.$store.state.show === "sidebar";
}, },
signup: () => signup, signup: () => signup,
version: () => version, version: () => version,
disableExternal: () => disableExternal, disableExternal: () => disableExternal,
noAuth: () => noAuth, noAuth: () => noAuth,
authMethod: () => authMethod authMethod: () => authMethod,
}, },
methods: { methods: {
help() { help() {
this.$store.commit('showHover', 'help') this.$store.commit("showHover", "help");
}, },
logout: auth.logout logout: auth.logout,
} },
} };
</script> </script>

View File

@ -10,40 +10,45 @@
@mouseup="mouseUp" @mouseup="mouseUp"
@wheel="wheelMove" @wheel="wheelMove"
> >
<img src="" class="image-ex-img image-ex-img-center" ref="imgex" @load="onLoad"> <img
src=""
class="image-ex-img image-ex-img-center"
ref="imgex"
@load="onLoad"
/>
</div> </div>
</template> </template>
<script> <script>
import throttle from 'lodash.throttle' import throttle from "lodash.throttle";
import UTIF from 'utif' import UTIF from "utif";
export default { export default {
props: { props: {
src: String, src: String,
moveDisabledTime: { moveDisabledTime: {
type: Number, type: Number,
default: () => 200 default: () => 200,
}, },
maxScale: { maxScale: {
type: Number, type: Number,
default: () => 4 default: () => 4,
}, },
minScale: { minScale: {
type: Number, type: Number,
default: () => 0.25 default: () => 0.25,
}, },
classList: { classList: {
type: Array, type: Array,
default: () => [] default: () => [],
}, },
zoomStep: { zoomStep: {
type: Number, type: Number,
default: () => 0.25 default: () => 0.25,
}, },
autofill: { autofill: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
} },
}, },
data() { data() {
return { return {
@ -57,204 +62,208 @@ export default {
imageLoaded: false, imageLoaded: false,
position: { position: {
center: { x: 0, y: 0 }, center: { x: 0, y: 0 },
relative: { x: 0, y: 0 } relative: { x: 0, y: 0 },
} },
} };
}, },
mounted() { mounted() {
if (!this.decodeUTIF()) { if (!this.decodeUTIF()) {
this.$refs.imgex.src = this.src this.$refs.imgex.src = this.src;
} }
let container = this.$refs.container let container = this.$refs.container;
this.classList.forEach(className => container.classList.add(className)) this.classList.forEach((className) => container.classList.add(className));
// set width and height if they are zero // set width and height if they are zero
if (getComputedStyle(container).width === "0px") { if (getComputedStyle(container).width === "0px") {
container.style.width = "100%" container.style.width = "100%";
} }
if (getComputedStyle(container).height === "0px") { if (getComputedStyle(container).height === "0px") {
container.style.height = "100%" container.style.height = "100%";
} }
window.addEventListener('resize', this.onResize) window.addEventListener("resize", this.onResize);
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('resize', this.onResize) window.removeEventListener("resize", this.onResize);
document.removeEventListener('mouseup', this.onMouseUp) document.removeEventListener("mouseup", this.onMouseUp);
}, },
watch: { watch: {
src: function () { src: function () {
this.scale = 1 this.scale = 1;
this.setZoom() this.setZoom();
this.setCenter() this.setCenter();
} },
}, },
methods: { methods: {
// Modified from UTIF.replaceIMG // Modified from UTIF.replaceIMG
decodeUTIF() { decodeUTIF() {
const sufs = ["tif", "tiff", "dng", "cr2", "nef"] const sufs = ["tif", "tiff", "dng", "cr2", "nef"];
let suff = document.location.pathname.split(".").pop().toLowerCase() let suff = document.location.pathname.split(".").pop().toLowerCase();
if (sufs.indexOf(suff) == -1) return false if (sufs.indexOf(suff) == -1) return false;
let xhr = new XMLHttpRequest() let xhr = new XMLHttpRequest();
UTIF._xhrs.push(xhr) UTIF._xhrs.push(xhr);
UTIF._imgs.push(this.$refs.imgex) UTIF._imgs.push(this.$refs.imgex);
xhr.open("GET", this.src) xhr.open("GET", this.src);
xhr.responseType = "arraybuffer" xhr.responseType = "arraybuffer";
xhr.onload = UTIF._imgLoaded xhr.onload = UTIF._imgLoaded;
xhr.send() xhr.send();
return true return true;
}, },
onLoad() { onLoad() {
let img = this.$refs.imgex let img = this.$refs.imgex;
this.imageLoaded = true this.imageLoaded = true;
if (img === undefined) { if (img === undefined) {
return return;
} }
img.classList.remove('image-ex-img-center') img.classList.remove("image-ex-img-center");
this.setCenter() this.setCenter();
img.classList.add('image-ex-img-ready') img.classList.add("image-ex-img-ready");
document.addEventListener('mouseup', this.onMouseUp) document.addEventListener("mouseup", this.onMouseUp);
}, },
onMouseUp() { onMouseUp() {
this.inDrag = false this.inDrag = false;
}, },
onResize: throttle(function () { onResize: throttle(function () {
if (this.imageLoaded) { if (this.imageLoaded) {
this.setCenter() this.setCenter();
this.doMove(this.position.relative.x, this.position.relative.y) this.doMove(this.position.relative.x, this.position.relative.y);
} }
}, 100), }, 100),
setCenter() { setCenter() {
let container = this.$refs.container let container = this.$refs.container;
let img = this.$refs.imgex let img = this.$refs.imgex;
this.position.center.x = Math.floor((container.clientWidth - img.clientWidth) / 2) this.position.center.x = Math.floor(
this.position.center.y = Math.floor((container.clientHeight - img.clientHeight) / 2) (container.clientWidth - img.clientWidth) / 2
);
this.position.center.y = Math.floor(
(container.clientHeight - img.clientHeight) / 2
);
img.style.left = this.position.center.x + 'px' img.style.left = this.position.center.x + "px";
img.style.top = this.position.center.y + 'px' img.style.top = this.position.center.y + "px";
}, },
mousedownStart(event) { mousedownStart(event) {
this.lastX = null this.lastX = null;
this.lastY = null this.lastY = null;
this.inDrag = true this.inDrag = true;
event.preventDefault() event.preventDefault();
}, },
mouseMove(event) { mouseMove(event) {
if (!this.inDrag) return if (!this.inDrag) return;
this.doMove(event.movementX, event.movementY) this.doMove(event.movementX, event.movementY);
event.preventDefault() event.preventDefault();
}, },
mouseUp(event) { mouseUp(event) {
this.inDrag = false this.inDrag = false;
event.preventDefault() event.preventDefault();
}, },
touchStart(event) { touchStart(event) {
this.lastX = null this.lastX = null;
this.lastY = null this.lastY = null;
this.lastTouchDistance = null this.lastTouchDistance = null;
if (event.targetTouches.length < 2) { if (event.targetTouches.length < 2) {
setTimeout(() => { setTimeout(() => {
this.touches = 0 this.touches = 0;
}, 300) }, 300);
this.touches++ this.touches++;
if (this.touches > 1) { if (this.touches > 1) {
this.zoomAuto(event) this.zoomAuto(event);
} }
} }
event.preventDefault() event.preventDefault();
}, },
zoomAuto(event) { zoomAuto(event) {
switch (this.scale) { switch (this.scale) {
case 1: case 1:
this.scale = 2 this.scale = 2;
break break;
case 2: case 2:
this.scale = 4 this.scale = 4;
break break;
default: default:
case 4: case 4:
this.scale = 1 this.scale = 1;
this.setCenter() this.setCenter();
break break;
} }
this.setZoom() this.setZoom();
event.preventDefault() event.preventDefault();
}, },
touchMove(event) { touchMove(event) {
event.preventDefault() event.preventDefault();
if (this.lastX === null) { if (this.lastX === null) {
this.lastX = event.targetTouches[0].pageX this.lastX = event.targetTouches[0].pageX;
this.lastY = event.targetTouches[0].pageY this.lastY = event.targetTouches[0].pageY;
return return;
} }
let step = this.$refs.imgex.width / 5 let step = this.$refs.imgex.width / 5;
if (event.targetTouches.length === 2) { if (event.targetTouches.length === 2) {
this.moveDisabled = true this.moveDisabled = true;
clearTimeout(this.disabledTimer) clearTimeout(this.disabledTimer);
this.disabledTimer = setTimeout( this.disabledTimer = setTimeout(
() => (this.moveDisabled = false), () => (this.moveDisabled = false),
this.moveDisabledTime this.moveDisabledTime
) );
let p1 = event.targetTouches[0] let p1 = event.targetTouches[0];
let p2 = event.targetTouches[1] let p2 = event.targetTouches[1];
let touchDistance = Math.sqrt( let touchDistance = Math.sqrt(
Math.pow(p2.pageX - p1.pageX, 2) + Math.pow(p2.pageY - p1.pageY, 2) Math.pow(p2.pageX - p1.pageX, 2) + Math.pow(p2.pageY - p1.pageY, 2)
) );
if (!this.lastTouchDistance) { if (!this.lastTouchDistance) {
this.lastTouchDistance = touchDistance this.lastTouchDistance = touchDistance;
return return;
} }
this.scale += (touchDistance - this.lastTouchDistance) / step this.scale += (touchDistance - this.lastTouchDistance) / step;
this.lastTouchDistance = touchDistance this.lastTouchDistance = touchDistance;
this.setZoom() this.setZoom();
} else if (event.targetTouches.length === 1) { } else if (event.targetTouches.length === 1) {
if (this.moveDisabled) return if (this.moveDisabled) return;
let x = event.targetTouches[0].pageX - this.lastX let x = event.targetTouches[0].pageX - this.lastX;
let y = event.targetTouches[0].pageY - this.lastY let y = event.targetTouches[0].pageY - this.lastY;
if (Math.abs(x) >= step && Math.abs(y) >= step) return if (Math.abs(x) >= step && Math.abs(y) >= step) return;
this.lastX = event.targetTouches[0].pageX this.lastX = event.targetTouches[0].pageX;
this.lastY = event.targetTouches[0].pageY this.lastY = event.targetTouches[0].pageY;
this.doMove(x, y) this.doMove(x, y);
} }
}, },
doMove(x, y) { doMove(x, y) {
let style = this.$refs.imgex.style let style = this.$refs.imgex.style;
let posX = this.pxStringToNumber(style.left) + x let posX = this.pxStringToNumber(style.left) + x;
let posY = this.pxStringToNumber(style.top) + y let posY = this.pxStringToNumber(style.top) + y;
style.left = posX + 'px' style.left = posX + "px";
style.top = posY + 'px' style.top = posY + "px";
this.position.relative.x = Math.abs(this.position.center.x - posX) this.position.relative.x = Math.abs(this.position.center.x - posX);
this.position.relative.y = Math.abs(this.position.center.y - posY) this.position.relative.y = Math.abs(this.position.center.y - posY);
if (posX < this.position.center.x) { if (posX < this.position.center.x) {
this.position.relative.x = this.position.relative.x * -1 this.position.relative.x = this.position.relative.x * -1;
} }
if (posY < this.position.center.y) { if (posY < this.position.center.y) {
this.position.relative.y = this.position.relative.y * -1 this.position.relative.y = this.position.relative.y * -1;
} }
}, },
wheelMove(event) { wheelMove(event) {
this.scale += (event.wheelDeltaY / 100) * this.zoomStep this.scale += (event.wheelDeltaY / 100) * this.zoomStep;
this.setZoom() this.setZoom();
}, },
setZoom() { setZoom() {
this.scale = this.scale < this.minScale ? this.minScale : this.scale this.scale = this.scale < this.minScale ? this.minScale : this.scale;
this.scale = this.scale > this.maxScale ? this.maxScale : this.scale this.scale = this.scale > this.maxScale ? this.maxScale : this.scale;
this.$refs.imgex.style.transform = `scale(${this.scale})` this.$refs.imgex.style.transform = `scale(${this.scale})`;
}, },
pxStringToNumber(style) { pxStringToNumber(style) {
return +style.replace("px", "") return +style.replace("px", "");
} },
} },
} };
</script> </script>
<style> <style>
.image-ex-container { .image-ex-container {

View File

@ -1,5 +1,6 @@
<template> <template>
<div class="item" <div
class="item"
role="button" role="button"
tabindex="0" tabindex="0"
:draggable="isDraggable" :draggable="isDraggable"
@ -11,9 +12,13 @@
@touchstart="touchstart" @touchstart="touchstart"
:data-dir="isDir" :data-dir="isDir"
:aria-label="name" :aria-label="name"
:aria-selected="isSelected"> :aria-selected="isSelected"
>
<div> <div>
<img v-if="readOnly == undefined && type==='image' && isThumbsEnabled" v-lazy="thumbnailUrl"> <img
v-if="readOnly == undefined && type === 'image' && isThumbsEnabled"
v-lazy="thumbnailUrl"
/>
<i v-else class="material-icons">{{ icon }}</i> <i v-else class="material-icons">{{ icon }}</i>
</div> </div>
@ -31,203 +36,221 @@
</template> </template>
<script> <script>
import { baseURL, enableThumbs } from '@/utils/constants' import { baseURL, enableThumbs } from "@/utils/constants";
import { mapMutations, mapGetters, mapState } from 'vuex' import { mapMutations, mapGetters, mapState } from "vuex";
import filesize from 'filesize' import filesize from "filesize";
import moment from 'moment' import moment from "moment";
import { files as api } from '@/api' import { files as api } from "@/api";
import * as upload from '@/utils/upload' import * as upload from "@/utils/upload";
export default { export default {
name: 'item', name: "item",
data: function () { data: function () {
return { return {
touches: 0 touches: 0,
} };
}, },
props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index', 'readOnly'], props: [
"name",
"isDir",
"url",
"type",
"size",
"modified",
"index",
"readOnly",
],
computed: { computed: {
...mapState(['user', 'selected', 'req', 'jwt']), ...mapState(["user", "selected", "req", "jwt"]),
...mapGetters(['selectedCount']), ...mapGetters(["selectedCount"]),
singleClick() { singleClick() {
return this.readOnly == undefined && this.user.singleClick return this.readOnly == undefined && this.user.singleClick;
}, },
isSelected() { isSelected() {
return (this.selected.indexOf(this.index) !== -1) return this.selected.indexOf(this.index) !== -1;
}, },
icon() { icon() {
if (this.isDir) return 'folder' if (this.isDir) return "folder";
if (this.type === 'image') return 'insert_photo' if (this.type === "image") return "insert_photo";
if (this.type === 'audio') return 'volume_up' if (this.type === "audio") return "volume_up";
if (this.type === 'video') return 'movie' if (this.type === "video") return "movie";
return 'insert_drive_file' return "insert_drive_file";
}, },
isDraggable() { isDraggable() {
return this.readOnly == undefined && this.user.perm.rename return this.readOnly == undefined && this.user.perm.rename;
}, },
canDrop() { canDrop() {
if (!this.isDir || this.readOnly !== undefined) return false if (!this.isDir || this.readOnly !== undefined) return false;
for (let i of this.selected) { for (let i of this.selected) {
if (this.req.items[i].url === this.url) { if (this.req.items[i].url === this.url) {
return false return false;
} }
} }
return true return true;
}, },
thumbnailUrl() { thumbnailUrl() {
const path = this.url.replace(/^\/files\//, '') const path = this.url.replace(/^\/files\//, "");
// reload the image when the file is replaced // reload the image when the file is replaced
const key = Date.parse(this.modified) const key = Date.parse(this.modified);
return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true&k=${key}` return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true&k=${key}`;
}, },
isThumbsEnabled() { isThumbsEnabled() {
return enableThumbs return enableThumbs;
} },
}, },
methods: { methods: {
...mapMutations(['addSelected', 'removeSelected', 'resetSelected']), ...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
humanSize: function () { humanSize: function () {
return filesize(this.size) return filesize(this.size);
}, },
humanTime: function () { humanTime: function () {
return moment(this.modified).fromNow() return moment(this.modified).fromNow();
}, },
dragStart: function () { dragStart: function () {
if (this.selectedCount === 0) { if (this.selectedCount === 0) {
this.addSelected(this.index) this.addSelected(this.index);
return return;
} }
if (!this.isSelected) { if (!this.isSelected) {
this.resetSelected() this.resetSelected();
this.addSelected(this.index) this.addSelected(this.index);
} }
}, },
dragOver: function (event) { dragOver: function (event) {
if (!this.canDrop) return if (!this.canDrop) return;
event.preventDefault() event.preventDefault();
let el = event.target let el = event.target;
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
if (!el.classList.contains('item')) { if (!el.classList.contains("item")) {
el = el.parentElement el = el.parentElement;
} }
} }
el.style.opacity = 1 el.style.opacity = 1;
}, },
drop: async function (event) { drop: async function (event) {
if (!this.canDrop) return if (!this.canDrop) return;
event.preventDefault() event.preventDefault();
if (this.selectedCount === 0) return if (this.selectedCount === 0) return;
let el = event.target let el = event.target;
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
if (el !== null && !el.classList.contains('item')) { if (el !== null && !el.classList.contains("item")) {
el = el.parentElement el = el.parentElement;
} }
} }
let items = [] let items = [];
for (let i of this.selected) { for (let i of this.selected) {
items.push({ items.push({
from: this.req.items[i].url, from: this.req.items[i].url,
to: this.url + this.req.items[i].name, to: this.url + this.req.items[i].name,
name: this.req.items[i].name name: this.req.items[i].name,
}) });
} }
let base = el.querySelector('.name').innerHTML + '/' let base = el.querySelector(".name").innerHTML + "/";
let path = this.$route.path + base let path = this.$route.path + base;
let baseItems = (await api.fetch(path)).items let baseItems = (await api.fetch(path)).items;
let action = (overwrite, rename) => { let action = (overwrite, rename) => {
api.move(items, overwrite, rename).then(() => { api
this.$store.commit('setReload', true) .move(items, overwrite, rename)
}).catch(this.$showError) .then(() => {
} this.$store.commit("setReload", true);
})
.catch(this.$showError);
};
let conflict = upload.checkConflict(items, baseItems) let conflict = upload.checkConflict(items, baseItems);
let overwrite = false let overwrite = false;
let rename = false let rename = false;
if (conflict) { if (conflict) {
this.$store.commit('showHover', { this.$store.commit("showHover", {
prompt: 'replace-rename', prompt: "replace-rename",
confirm: (event, option) => { confirm: (event, option) => {
overwrite = option == 'overwrite' overwrite = option == "overwrite";
rename = option == 'rename' rename = option == "rename";
event.preventDefault() event.preventDefault();
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
action(overwrite, rename) action(overwrite, rename);
} },
}) });
return return;
} }
action(overwrite, rename) action(overwrite, rename);
}, },
itemClick: function (event) { itemClick: function (event) {
if (this.singleClick && !this.$store.state.multiple) this.open() if (this.singleClick && !this.$store.state.multiple) this.open();
else this.click(event) else this.click(event);
}, },
click: function (event) { click: function (event) {
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault() if (!this.singleClick && this.selectedCount !== 0) event.preventDefault();
if (this.$store.state.selected.indexOf(this.index) !== -1) { if (this.$store.state.selected.indexOf(this.index) !== -1) {
this.removeSelected(this.index) this.removeSelected(this.index);
return return;
} }
if (event.shiftKey && this.selected.length > 0) { if (event.shiftKey && this.selected.length > 0) {
let fi = 0 let fi = 0;
let la = 0 let la = 0;
if (this.index > this.selected[0]) { if (this.index > this.selected[0]) {
fi = this.selected[0] + 1 fi = this.selected[0] + 1;
la = this.index la = this.index;
} else { } else {
fi = this.index fi = this.index;
la = this.selected[0] - 1 la = this.selected[0] - 1;
} }
for (; fi <= la; fi++) { for (; fi <= la; fi++) {
if (this.$store.state.selected.indexOf(fi) == -1) { if (this.$store.state.selected.indexOf(fi) == -1) {
this.addSelected(fi) this.addSelected(fi);
} }
} }
return return;
} }
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !this.$store.state.multiple) this.resetSelected() if (
this.addSelected(this.index) !this.singleClick &&
!event.ctrlKey &&
!event.metaKey &&
!this.$store.state.multiple
)
this.resetSelected();
this.addSelected(this.index);
}, },
dblclick: function () { dblclick: function () {
if (!this.singleClick) this.open() if (!this.singleClick) this.open();
}, },
touchstart() { touchstart() {
setTimeout(() => { setTimeout(() => {
this.touches = 0 this.touches = 0;
}, 300) }, 300);
this.touches++ this.touches++;
if (this.touches > 1) { if (this.touches > 1) {
this.open() this.open();
} }
}, },
open: function () { open: function () {
this.$router.push({path: this.url}) this.$router.push({ path: this.url });
} },
} },
} };
</script> </script>

View File

@ -8,25 +8,18 @@
<script> <script>
export default { export default {
name: 'action', name: "action",
props: [ props: ["icon", "label", "counter", "show"],
'icon',
'label',
'counter',
'show'
],
methods: { methods: {
action: function () { action: function () {
if (this.show) { if (this.show) {
this.$store.commit('showHover', this.show) this.$store.commit("showHover", this.show);
} }
this.$emit('action') this.$emit("action");
} },
} },
} };
</script> </script>
<style> <style></style>
</style>

View File

@ -1,7 +1,13 @@
<template> <template>
<header> <header>
<img v-if="showLogo !== undefined" :src="logoURL" /> <img v-if="showLogo !== undefined" :src="logoURL" />
<action v-if="showMenu !== undefined" class="menu-button" icon="menu" :label="$t('buttons.toggleSidebar')" @action="openSidebar()" /> <action
v-if="showMenu !== undefined"
class="menu-button"
icon="menu"
:label="$t('buttons.toggleSidebar')"
@action="openSidebar()"
/>
<slot /> <slot />
@ -9,39 +15,44 @@
<slot name="actions" /> <slot name="actions" />
</div> </div>
<action v-if="this.$slots.actions" id="more" icon="more_vert" :label="$t('buttons.more')" @action="$store.commit('showHover', 'more')" /> <action
v-if="this.$slots.actions"
id="more"
icon="more_vert"
:label="$t('buttons.more')"
@action="$store.commit('showHover', 'more')"
/>
<div class="overlay" v-show="this.$store.state.show == 'more'" @click="$store.commit('closeHovers')" /> <div
class="overlay"
v-show="this.$store.state.show == 'more'"
@click="$store.commit('closeHovers')"
/>
</header> </header>
</template> </template>
<script> <script>
import { logoURL } from '@/utils/constants' import { logoURL } from "@/utils/constants";
import Action from '@/components/header/Action' import Action from "@/components/header/Action";
export default { export default {
name: 'header-bar', name: "header-bar",
props: [ props: ["showLogo", "showMenu"],
'showLogo',
'showMenu',
],
components: { components: {
Action Action,
}, },
data: function () { data: function () {
return { return {
logoURL logoURL,
} };
}, },
methods: { methods: {
openSidebar() { openSidebar() {
this.$store.commit('showHover', 'sidebar') this.$store.commit("showHover", "sidebar");
} },
} },
} };
</script> </script>
<style> <style></style>
</style>

View File

@ -1,108 +1,119 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('prompts.copy') }}</h2> <h2>{{ $t("prompts.copy") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p>{{ $t('prompts.copyMessage') }}</p> <p>{{ $t("prompts.copyMessage") }}</p>
<file-list @update:selected="val => dest = val"></file-list> <file-list @update:selected="(val) => (dest = val)"></file-list>
</div> </div>
<div class="card-action"> <div class="card-action">
<button class="button button--flat button--grey" <button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')"
<button class="button button--flat" >
{{ $t("buttons.cancel") }}
</button>
<button
class="button button--flat"
@click="copy" @click="copy"
:aria-label="$t('buttons.copy')" :aria-label="$t('buttons.copy')"
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button> :title="$t('buttons.copy')"
>
{{ $t("buttons.copy") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
import FileList from './FileList' import FileList from "./FileList";
import { files as api } from '@/api' import { files as api } from "@/api";
import buttons from '@/utils/buttons' import buttons from "@/utils/buttons";
import * as upload from '@/utils/upload' import * as upload from "@/utils/upload";
export default { export default {
name: 'copy', name: "copy",
components: { FileList }, components: { FileList },
data: function () { data: function () {
return { return {
current: window.location.pathname, current: window.location.pathname,
dest: null dest: null,
} };
}, },
computed: mapState(['req', 'selected']), computed: mapState(["req", "selected"]),
methods: { methods: {
copy: async function (event) { copy: async function (event) {
event.preventDefault() event.preventDefault();
let items = [] let items = [];
// Create a new promise for each file. // Create a new promise for each file.
for (let item of this.selected) { for (let item of this.selected) {
items.push({ items.push({
from: this.req.items[item].url, from: this.req.items[item].url,
to: this.dest + encodeURIComponent(this.req.items[item].name), to: this.dest + encodeURIComponent(this.req.items[item].name),
name: this.req.items[item].name name: this.req.items[item].name,
}) });
} }
let action = async (overwrite, rename) => { let action = async (overwrite, rename) => {
buttons.loading('copy') buttons.loading("copy");
await api.copy(items, overwrite, rename).then(() => { await api
buttons.success('copy') .copy(items, overwrite, rename)
.then(() => {
buttons.success("copy");
if (this.$route.path === this.dest) { if (this.$route.path === this.dest) {
this.$store.commit('setReload', true) this.$store.commit("setReload", true);
return return;
} }
this.$router.push({ path: this.dest }) this.$router.push({ path: this.dest });
}).catch((e) => {
buttons.done('copy')
this.$showError(e)
}) })
} .catch((e) => {
buttons.done("copy");
this.$showError(e);
});
};
if (this.$route.path === this.dest) { if (this.$route.path === this.dest) {
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
action(false, true) action(false, true);
return return;
} }
let dstItems = (await api.fetch(this.dest)).items let dstItems = (await api.fetch(this.dest)).items;
let conflict = upload.checkConflict(items, dstItems) let conflict = upload.checkConflict(items, dstItems);
let overwrite = false let overwrite = false;
let rename = false let rename = false;
if (conflict) { if (conflict) {
this.$store.commit('showHover', { this.$store.commit("showHover", {
prompt: 'replace-rename', prompt: "replace-rename",
confirm: (event, option) => { confirm: (event, option) => {
overwrite = option == 'overwrite' overwrite = option == "overwrite";
rename = option == 'rename' rename = option == "rename";
event.preventDefault() event.preventDefault();
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
action(overwrite, rename) action(overwrite, rename);
} },
}) });
return return;
} }
action(overwrite, rename) action(overwrite, rename);
} },
} },
} };
</script> </script>

View File

@ -1,68 +1,80 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-content"> <div class="card-content">
<p v-if="req.kind !== 'listing'">{{ $t('prompts.deleteMessageSingle') }}</p> <p v-if="req.kind !== 'listing'">
<p v-else>{{ $t('prompts.deleteMessageMultiple', { count: selectedCount}) }}</p> {{ $t("prompts.deleteMessageSingle") }}
</p>
<p v-else>
{{ $t("prompts.deleteMessageMultiple", { count: selectedCount }) }}
</p>
</div> </div>
<div class="card-action"> <div class="card-action">
<button @click="$store.commit('closeHovers')" <button
@click="$store.commit('closeHovers')"
class="button button--flat button--grey" class="button button--flat button--grey"
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')"
<button @click="submit" >
{{ $t("buttons.cancel") }}
</button>
<button
@click="submit"
class="button button--flat button--red" class="button button--flat button--red"
:aria-label="$t('buttons.delete')" :aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button> :title="$t('buttons.delete')"
>
{{ $t("buttons.delete") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import {mapGetters, mapMutations, mapState} from 'vuex' import { mapGetters, mapMutations, mapState } from "vuex";
import { files as api } from '@/api' import { files as api } from "@/api";
import buttons from '@/utils/buttons' import buttons from "@/utils/buttons";
export default { export default {
name: 'delete', name: "delete",
computed: { computed: {
...mapGetters(['isListing', 'selectedCount']), ...mapGetters(["isListing", "selectedCount"]),
...mapState(['req', 'selected', 'showConfirm']) ...mapState(["req", "selected", "showConfirm"]),
}, },
methods: { methods: {
...mapMutations(['closeHovers']), ...mapMutations(["closeHovers"]),
submit: async function () { submit: async function () {
buttons.loading('delete') buttons.loading("delete");
try { try {
if (!this.isListing) { if (!this.isListing) {
await api.remove(this.$route.path) await api.remove(this.$route.path);
buttons.success('delete') buttons.success("delete");
this.showConfirm() this.showConfirm();
this.closeHovers() this.closeHovers();
return return;
} }
this.closeHovers() this.closeHovers();
if (this.selectedCount === 0) { if (this.selectedCount === 0) {
return return;
} }
let promises = [] let promises = [];
for (let index of this.selected) { for (let index of this.selected) {
promises.push(api.remove(this.req.items[index].url)) promises.push(api.remove(this.req.items[index].url));
} }
await Promise.all(promises) await Promise.all(promises);
buttons.success('delete') buttons.success("delete");
this.$store.commit('setReload', true) this.$store.commit("setReload", true);
} catch (e) { } catch (e) {
buttons.done('delete') buttons.done("delete");
this.$showError(e) this.$showError(e);
if (this.isListing) this.$store.commit('setReload', true) if (this.isListing) this.$store.commit("setReload", true);
}
}
}
} }
},
},
};
</script> </script>

View File

@ -1,35 +1,43 @@
<template> <template>
<div class="card floating" id="download"> <div class="card floating" id="download">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('prompts.download') }}</h2> <h2>{{ $t("prompts.download") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p>{{ $t('prompts.downloadMessage') }}</p> <p>{{ $t("prompts.downloadMessage") }}</p>
<button v-for="(ext, format) in formats" :key="format" class="button button--block" @click="showConfirm(format)" v-focus>{{ ext }}</button> <button
v-for="(ext, format) in formats"
:key="format"
class="button button--block"
@click="showConfirm(format)"
v-focus
>
{{ ext }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
export default { export default {
name: 'download', name: "download",
data: function () { data: function () {
return { return {
formats: { formats: {
zip: 'zip', zip: "zip",
tar: 'tar', tar: "tar",
targz: 'tar.gz', targz: "tar.gz",
tarbz2: 'tar.bz2', tarbz2: "tar.bz2",
tarxz: 'tar.xz', tarxz: "tar.xz",
tarlz4: 'tar.lz4', tarlz4: "tar.lz4",
tarsz: 'tar.sz' tarsz: "tar.sz",
}
}
}, },
computed: mapState(['showConfirm']) };
} },
computed: mapState(["showConfirm"]),
};
</script> </script>

View File

@ -1,132 +1,138 @@
<template> <template>
<div> <div>
<ul class="file-list"> <ul class="file-list">
<li @click="itemClick" <li
@click="itemClick"
@touchstart="touchstart" @touchstart="touchstart"
@dblclick="next" @dblclick="next"
role="button" role="button"
tabindex="0" tabindex="0"
:aria-label="item.name" :aria-label="item.name"
:aria-selected="selected == item.url" :aria-selected="selected == item.url"
:key="item.name" v-for="item in items" :key="item.name"
:data-url="item.url">{{ item.name }}</li> v-for="item in items"
:data-url="item.url"
>
{{ item.name }}
</li>
</ul> </ul>
<p>{{ $t('prompts.currentlyNavigating') }} <code>{{ nav }}</code>.</p> <p>
{{ $t("prompts.currentlyNavigating") }} <code>{{ nav }}</code
>.
</p>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
import url from '@/utils/url' import url from "@/utils/url";
import { files } from '@/api' import { files } from "@/api";
export default { export default {
name: 'file-list', name: "file-list",
data: function () { data: function () {
return { return {
items: [], items: [],
touches: { touches: {
id: '', id: "",
count: 0 count: 0,
}, },
selected: null, selected: null,
current: window.location.pathname current: window.location.pathname,
} };
}, },
computed: { computed: {
...mapState([ 'req', 'user' ]), ...mapState(["req", "user"]),
nav() { nav() {
return decodeURIComponent(this.current) return decodeURIComponent(this.current);
} },
}, },
mounted() { mounted() {
this.fillOptions(this.req) this.fillOptions(this.req);
}, },
methods: { methods: {
fillOptions(req) { fillOptions(req) {
// Sets the current path and resets // Sets the current path and resets
// the current items. // the current items.
this.current = req.url this.current = req.url;
this.items = [] this.items = [];
this.$emit('update:selected', this.current) this.$emit("update:selected", this.current);
// If the path isn't the root path, // If the path isn't the root path,
// show a button to navigate to the previous // show a button to navigate to the previous
// directory. // directory.
if (req.url !== '/files/') { if (req.url !== "/files/") {
this.items.push({ this.items.push({
name: '..', name: "..",
url: url.removeLastDir(req.url) + '/' url: url.removeLastDir(req.url) + "/",
}) });
} }
// If this folder is empty, finish here. // If this folder is empty, finish here.
if (req.items === null) return if (req.items === null) return;
// Otherwise we add every directory to the // Otherwise we add every directory to the
// move options. // move options.
for (let item of req.items) { for (let item of req.items) {
if (!item.isDir) continue if (!item.isDir) continue;
this.items.push({ this.items.push({
name: item.name, name: item.name,
url: item.url url: item.url,
}) });
} }
}, },
next: function (event) { next: function (event) {
// Retrieves the URL of the directory the user // Retrieves the URL of the directory the user
// just clicked in and fill the options with its // just clicked in and fill the options with its
// content. // content.
let uri = event.currentTarget.dataset.url let uri = event.currentTarget.dataset.url;
files.fetch(uri) files.fetch(uri).then(this.fillOptions).catch(this.$showError);
.then(this.fillOptions)
.catch(this.$showError)
}, },
touchstart(event) { touchstart(event) {
let url = event.currentTarget.dataset.url let url = event.currentTarget.dataset.url;
// In 300 milliseconds, we shall reset the count. // In 300 milliseconds, we shall reset the count.
setTimeout(() => { setTimeout(() => {
this.touches.count = 0 this.touches.count = 0;
}, 300) }, 300);
// If the element the user is touching // If the element the user is touching
// is different from the last one he touched, // is different from the last one he touched,
// reset the count. // reset the count.
if (this.touches.id !== url) { if (this.touches.id !== url) {
this.touches.id = url this.touches.id = url;
this.touches.count = 1 this.touches.count = 1;
return return;
} }
this.touches.count++ this.touches.count++;
// If there is more than one touch already, // If there is more than one touch already,
// open the next screen. // open the next screen.
if (this.touches.count > 1) { if (this.touches.count > 1) {
this.next(event) this.next(event);
} }
}, },
itemClick: function (event) { itemClick: function (event) {
if (this.user.singleClick) this.next(event) if (this.user.singleClick) this.next(event);
else this.select(event) else this.select(event);
}, },
select: function (event) { select: function (event) {
// If the element is already selected, unselect it. // If the element is already selected, unselect it.
if (this.selected === event.currentTarget.dataset.url) { if (this.selected === event.currentTarget.dataset.url) {
this.selected = null this.selected = null;
this.$emit('update:selected', this.current) this.$emit("update:selected", this.current);
return return;
} }
// Otherwise select the element. // Otherwise select the element.
this.selected = event.currentTarget.dataset.url this.selected = event.currentTarget.dataset.url;
this.$emit('update:selected', this.selected) this.$emit("update:selected", this.selected);
} },
} },
} };
</script> </script>

View File

@ -1,34 +1,37 @@
<template> <template>
<div class="card floating help"> <div class="card floating help">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('help.help') }}</h2> <h2>{{ $t("help.help") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<ul> <ul>
<li><strong>F1</strong> - {{ $t('help.f1') }}</li> <li><strong>F1</strong> - {{ $t("help.f1") }}</li>
<li><strong>F2</strong> - {{ $t('help.f2') }}</li> <li><strong>F2</strong> - {{ $t("help.f2") }}</li>
<li><strong>DEL</strong> - {{ $t('help.del') }}</li> <li><strong>DEL</strong> - {{ $t("help.del") }}</li>
<li><strong>ESC</strong> - {{ $t('help.esc') }}</li> <li><strong>ESC</strong> - {{ $t("help.esc") }}</li>
<li><strong>CTRL + S</strong> - {{ $t('help.ctrl.s') }}</li> <li><strong>CTRL + S</strong> - {{ $t("help.ctrl.s") }}</li>
<li><strong>CTRL + F</strong> - {{ $t('help.ctrl.f') }}</li> <li><strong>CTRL + F</strong> - {{ $t("help.ctrl.f") }}</li>
<li><strong>CTRL + Click</strong> - {{ $t('help.ctrl.click') }}</li> <li><strong>CTRL + Click</strong> - {{ $t("help.ctrl.click") }}</li>
<li><strong>Click</strong> - {{ $t('help.click') }}</li> <li><strong>Click</strong> - {{ $t("help.click") }}</li>
<li><strong>Double click</strong> - {{ $t('help.doubleClick') }}</li> <li><strong>Double click</strong> - {{ $t("help.doubleClick") }}</li>
</ul> </ul>
</div> </div>
<div class="card-action"> <div class="card-action">
<button type="submit" <button
type="submit"
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
class="button button--flat" class="button button--flat"
:aria-label="$t('buttons.ok')" :aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button> :title="$t('buttons.ok')"
>
{{ $t("buttons.ok") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { name: 'help' } export default { name: "help" };
</script> </script>

View File

@ -1,99 +1,149 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('prompts.fileInfo') }}</h2> <h2>{{ $t("prompts.fileInfo") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p v-if="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p> <p v-if="selected.length > 1">
{{ $t("prompts.filesSelected", { count: selected.length }) }}
</p>
<p class="break-word" v-if="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name }}</p> <p class="break-word" v-if="selected.length < 2">
<p v-if="!dir || selected.length > 1"><strong>{{ $t('prompts.size') }}:</strong> <span id="content_length"></span> {{ humanSize }}</p> <strong>{{ $t("prompts.displayName") }}</strong> {{ name }}
<p v-if="selected.length < 2"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime }}</p> </p>
<p v-if="!dir || selected.length > 1">
<strong>{{ $t("prompts.size") }}:</strong>
<span id="content_length"></span> {{ humanSize }}
</p>
<p v-if="selected.length < 2">
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
</p>
<template v-if="dir && selected.length === 0"> <template v-if="dir && selected.length === 0">
<p><strong>{{ $t('prompts.numberFiles') }}:</strong> {{ req.numFiles }}</p> <p>
<p><strong>{{ $t('prompts.numberDirs') }}:</strong> {{ req.numDirs }}</p> <strong>{{ $t("prompts.numberFiles") }}:</strong> {{ req.numFiles }}
</p>
<p>
<strong>{{ $t("prompts.numberDirs") }}:</strong> {{ req.numDirs }}
</p>
</template> </template>
<template v-if="!dir"> <template v-if="!dir">
<p><strong>MD5: </strong><code><a @click="checksum($event, 'md5')">{{ $t('prompts.show') }}</a></code></p> <p>
<p><strong>SHA1: </strong><code><a @click="checksum($event, 'sha1')">{{ $t('prompts.show') }}</a></code></p> <strong>MD5: </strong
<p><strong>SHA256: </strong><code><a @click="checksum($event, 'sha256')">{{ $t('prompts.show') }}</a></code></p> ><code
<p><strong>SHA512: </strong><code><a @click="checksum($event, 'sha512')">{{ $t('prompts.show') }}</a></code></p> ><a @click="checksum($event, 'md5')">{{
$t("prompts.show")
}}</a></code
>
</p>
<p>
<strong>SHA1: </strong
><code
><a @click="checksum($event, 'sha1')">{{
$t("prompts.show")
}}</a></code
>
</p>
<p>
<strong>SHA256: </strong
><code
><a @click="checksum($event, 'sha256')">{{
$t("prompts.show")
}}</a></code
>
</p>
<p>
<strong>SHA512: </strong
><code
><a @click="checksum($event, 'sha512')">{{
$t("prompts.show")
}}</a></code
>
</p>
</template> </template>
</div> </div>
<div class="card-action"> <div class="card-action">
<button type="submit" <button
type="submit"
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
class="button button--flat" class="button button--flat"
:aria-label="$t('buttons.ok')" :aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button> :title="$t('buttons.ok')"
>
{{ $t("buttons.ok") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import {mapState, mapGetters} from 'vuex' import { mapState, mapGetters } from "vuex";
import filesize from 'filesize' import filesize from "filesize";
import moment from 'moment' import moment from "moment";
import { files as api } from '@/api' import { files as api } from "@/api";
export default { export default {
name: 'info', name: "info",
computed: { computed: {
...mapState(['req', 'selected']), ...mapState(["req", "selected"]),
...mapGetters(['selectedCount', 'isListing']), ...mapGetters(["selectedCount", "isListing"]),
humanSize: function () { humanSize: function () {
if (this.selectedCount === 0 || !this.isListing) { if (this.selectedCount === 0 || !this.isListing) {
return filesize(this.req.size) return filesize(this.req.size);
} }
let sum = 0 let sum = 0;
for (let selected of this.selected) { for (let selected of this.selected) {
sum += this.req.items[selected].size sum += this.req.items[selected].size;
} }
return filesize(sum) return filesize(sum);
}, },
humanTime: function () { humanTime: function () {
if (this.selectedCount === 0) { if (this.selectedCount === 0) {
return moment(this.req.modified).fromNow() return moment(this.req.modified).fromNow();
} }
return moment(this.req.items[this.selected[0]].modified).fromNow() return moment(this.req.items[this.selected[0]].modified).fromNow();
}, },
name: function () { name: function () {
return this.selectedCount === 0 ? this.req.name : this.req.items[this.selected[0]].name return this.selectedCount === 0
? this.req.name
: this.req.items[this.selected[0]].name;
}, },
dir: function () { dir: function () {
return this.selectedCount > 1 || (this.selectedCount === 0 return (
this.selectedCount > 1 ||
(this.selectedCount === 0
? this.req.isDir ? this.req.isDir
: this.req.items[this.selected[0]].isDir) : this.req.items[this.selected[0]].isDir)
} );
},
}, },
methods: { methods: {
checksum: async function (event, algo) { checksum: async function (event, algo) {
event.preventDefault() event.preventDefault();
let link let link;
if (this.selectedCount) { if (this.selectedCount) {
link = this.req.items[this.selected[0]].url link = this.req.items[this.selected[0]].url;
} else { } else {
link = this.$route.path link = this.$route.path;
} }
try { try {
const hash = await api.checksum(link, algo) const hash = await api.checksum(link, algo);
// eslint-disable-next-line // eslint-disable-next-line
event.target.innerHTML = hash event.target.innerHTML = hash
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
}
}
}
} }
},
},
};
</script> </script>

View File

@ -1,93 +1,104 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('prompts.move') }}</h2> <h2>{{ $t("prompts.move") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<file-list @update:selected="val => dest = val"></file-list> <file-list @update:selected="(val) => (dest = val)"></file-list>
</div> </div>
<div class="card-action"> <div class="card-action">
<button class="button button--flat button--grey" <button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')"
<button class="button button--flat" >
{{ $t("buttons.cancel") }}
</button>
<button
class="button button--flat"
@click="move" @click="move"
:disabled="$route.path === dest" :disabled="$route.path === dest"
:aria-label="$t('buttons.move')" :aria-label="$t('buttons.move')"
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button> :title="$t('buttons.move')"
>
{{ $t("buttons.move") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
import FileList from './FileList' import FileList from "./FileList";
import { files as api } from '@/api' import { files as api } from "@/api";
import buttons from '@/utils/buttons' import buttons from "@/utils/buttons";
import * as upload from '@/utils/upload' import * as upload from "@/utils/upload";
export default { export default {
name: 'move', name: "move",
components: { FileList }, components: { FileList },
data: function () { data: function () {
return { return {
current: window.location.pathname, current: window.location.pathname,
dest: null dest: null,
} };
}, },
computed: mapState(['req', 'selected']), computed: mapState(["req", "selected"]),
methods: { methods: {
move: async function (event) { move: async function (event) {
event.preventDefault() event.preventDefault();
let items = [] let items = [];
for (let item of this.selected) { for (let item of this.selected) {
items.push({ items.push({
from: this.req.items[item].url, from: this.req.items[item].url,
to: this.dest + encodeURIComponent(this.req.items[item].name), to: this.dest + encodeURIComponent(this.req.items[item].name),
name: this.req.items[item].name name: this.req.items[item].name,
}) });
} }
let action = async (overwrite, rename) => { let action = async (overwrite, rename) => {
buttons.loading('move') buttons.loading("move");
await api.move(items, overwrite, rename).then(() => { await api
buttons.success('move') .move(items, overwrite, rename)
this.$router.push({ path: this.dest }) .then(() => {
}).catch((e) => { buttons.success("move");
buttons.done('move') this.$router.push({ path: this.dest });
this.$showError(e)
}) })
} .catch((e) => {
buttons.done("move");
this.$showError(e);
});
};
let dstItems = (await api.fetch(this.dest)).items let dstItems = (await api.fetch(this.dest)).items;
let conflict = upload.checkConflict(items, dstItems) let conflict = upload.checkConflict(items, dstItems);
let overwrite = false let overwrite = false;
let rename = false let rename = false;
if (conflict) { if (conflict) {
this.$store.commit('showHover', { this.$store.commit("showHover", {
prompt: 'replace-rename', prompt: "replace-rename",
confirm: (event, option) => { confirm: (event, option) => {
overwrite = option == 'overwrite' overwrite = option == "overwrite";
rename = option == 'rename' rename = option == "rename";
event.preventDefault() event.preventDefault();
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
action(overwrite, rename) action(overwrite, rename);
} },
}) });
return return;
} }
action(overwrite, rename) action(overwrite, rename);
} },
} },
} };
</script> </script>

View File

@ -1,12 +1,18 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('prompts.newDir') }}</h2> <h2>{{ $t("prompts.newDir") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p>{{ $t('prompts.newDirMessage') }}</p> <p>{{ $t("prompts.newDirMessage") }}</p>
<input class="input input--block" type="text" @keyup.enter="submit" v-model.trim="name" v-focus> <input
class="input input--block"
type="text"
@keyup.enter="submit"
v-model.trim="name"
v-focus
/>
</div> </div>
<div class="card-action"> <div class="card-action">
@ -15,57 +21,60 @@
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')" :title="$t('buttons.cancel')"
>{{ $t('buttons.cancel') }}</button> >
{{ $t("buttons.cancel") }}
</button>
<button <button
class="button button--flat" class="button button--flat"
:aria-label="$t('buttons.create')" :aria-label="$t('buttons.create')"
:title="$t('buttons.create')" :title="$t('buttons.create')"
@click="submit" @click="submit"
>{{ $t('buttons.create') }}</button> >
{{ $t("buttons.create") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from "vuex";
import { files as api } from '@/api' import { files as api } from "@/api";
import url from '@/utils/url' import url from "@/utils/url";
export default { export default {
name: 'new-dir', name: "new-dir",
data: function () { data: function () {
return { return {
name: '' name: "",
}; };
}, },
computed: { computed: {
...mapGetters([ 'isFiles', 'isListing' ]) ...mapGetters(["isFiles", "isListing"]),
}, },
methods: { methods: {
submit: async function (event) { submit: async function (event) {
event.preventDefault() event.preventDefault();
if (this.new === '') return if (this.new === "") return;
// Build the path of the new directory. // Build the path of the new directory.
let uri = this.isFiles ? this.$route.path + '/' : '/' let uri = this.isFiles ? this.$route.path + "/" : "/";
if (!this.isListing) { if (!this.isListing) {
uri = url.removeLastDir(uri) + '/' uri = url.removeLastDir(uri) + "/";
} }
uri += encodeURIComponent(this.name) + '/' uri += encodeURIComponent(this.name) + "/";
uri = uri.replace('//', '/') uri = uri.replace("//", "/");
try { try {
await api.post(uri) await api.post(uri);
this.$router.push({ path: uri }) this.$router.push({ path: uri });
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
} },
} },
}; };
</script> </script>

View File

@ -1,12 +1,18 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('prompts.newFile') }}</h2> <h2>{{ $t("prompts.newFile") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p>{{ $t('prompts.newFileMessage') }}</p> <p>{{ $t("prompts.newFileMessage") }}</p>
<input class="input input--block" v-focus type="text" @keyup.enter="submit" v-model.trim="name"> <input
class="input input--block"
v-focus
type="text"
@keyup.enter="submit"
v-model.trim="name"
/>
</div> </div>
<div class="card-action"> <div class="card-action">
@ -15,57 +21,60 @@
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')" :title="$t('buttons.cancel')"
>{{ $t('buttons.cancel') }}</button> >
{{ $t("buttons.cancel") }}
</button>
<button <button
class="button button--flat" class="button button--flat"
@click="submit" @click="submit"
:aria-label="$t('buttons.create')" :aria-label="$t('buttons.create')"
:title="$t('buttons.create')" :title="$t('buttons.create')"
>{{ $t('buttons.create') }}</button> >
{{ $t("buttons.create") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from "vuex";
import { files as api } from '@/api' import { files as api } from "@/api";
import url from '@/utils/url' import url from "@/utils/url";
export default { export default {
name: 'new-file', name: "new-file",
data: function () { data: function () {
return { return {
name: '' name: "",
}; };
}, },
computed: { computed: {
...mapGetters([ 'isFiles', 'isListing' ]) ...mapGetters(["isFiles", "isListing"]),
}, },
methods: { methods: {
submit: async function (event) { submit: async function (event) {
event.preventDefault() event.preventDefault();
if (this.new === '') return if (this.new === "") return;
// Build the path of the new directory. // Build the path of the new directory.
let uri = this.isFiles ? this.$route.path + '/' : '/' let uri = this.isFiles ? this.$route.path + "/" : "/";
if (!this.isListing) { if (!this.isListing) {
uri = url.removeLastDir(uri) + '/' uri = url.removeLastDir(uri) + "/";
} }
uri += encodeURIComponent(this.name) uri += encodeURIComponent(this.name);
uri = uri.replace('//', '/') uri = uri.replace("//", "/");
try { try {
await api.post(uri) await api.post(uri);
this.$router.push({ path: uri }) this.$router.push({ path: uri });
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
} },
} },
}; };
</script> </script>

View File

@ -6,25 +6,25 @@
</template> </template>
<script> <script>
import Help from './Help' import Help from "./Help";
import Info from './Info' import Info from "./Info";
import Delete from './Delete' import Delete from "./Delete";
import Rename from './Rename' import Rename from "./Rename";
import Download from './Download' import Download from "./Download";
import Move from './Move' import Move from "./Move";
import Copy from './Copy' import Copy from "./Copy";
import NewFile from './NewFile' import NewFile from "./NewFile";
import NewDir from './NewDir' import NewDir from "./NewDir";
import Replace from './Replace' import Replace from "./Replace";
import ReplaceRename from './ReplaceRename' import ReplaceRename from "./ReplaceRename";
import Share from './Share' import Share from "./Share";
import Upload from './Upload' import Upload from "./Upload";
import ShareDelete from './ShareDelete' import ShareDelete from "./ShareDelete";
import { mapState } from 'vuex' import { mapState } from "vuex";
import buttons from '@/utils/buttons' import buttons from "@/utils/buttons";
export default { export default {
name: 'prompts', name: "prompts",
components: { components: {
Info, Info,
Delete, Delete,
@ -39,74 +39,75 @@ export default {
Replace, Replace,
ReplaceRename, ReplaceRename,
Upload, Upload,
ShareDelete ShareDelete,
}, },
data: function () { data: function () {
return { return {
pluginData: { pluginData: {
buttons, buttons,
'store': this.$store, store: this.$store,
'router': this.$router router: this.$router,
} },
} };
}, },
created() { created() {
window.addEventListener('keydown', (event) => { window.addEventListener("keydown", (event) => {
if (this.show == null) if (this.show == null) return;
return
let prompt = this.$refs.currentComponent; let prompt = this.$refs.currentComponent;
// Enter // Enter
if (event.keyCode == 13) { if (event.keyCode == 13) {
switch (this.show) { switch (this.show) {
case 'delete': case "delete":
prompt.submit() prompt.submit();
break; break;
case 'copy': case "copy":
prompt.copy(event) prompt.copy(event);
break; break;
case 'move': case "move":
prompt.move(event) prompt.move(event);
break; break;
case 'replace': case "replace":
prompt.showConfirm(event) prompt.showConfirm(event);
break; break;
} }
} }
}) });
}, },
computed: { computed: {
...mapState(['show', 'plugins']), ...mapState(["show", "plugins"]),
currentComponent: function () { currentComponent: function () {
const matched = [ const matched =
'info', [
'help', "info",
'delete', "help",
'rename', "delete",
'move', "rename",
'copy', "move",
'newFile', "copy",
'newDir', "newFile",
'download', "newDir",
'replace', "download",
'replace-rename', "replace",
'share', "replace-rename",
'upload', "share",
'share-delete' "upload",
"share-delete",
].indexOf(this.show) >= 0; ].indexOf(this.show) >= 0;
return matched && this.show || null; return (matched && this.show) || null;
}, },
showOverlay: function () { showOverlay: function () {
return (this.show !== null && this.show !== 'search' && this.show !== 'more') return (
} this.show !== null && this.show !== "search" && this.show !== "more"
);
},
}, },
methods: { methods: {
resetPrompts() { resetPrompts() {
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
} },
} },
} };
</script> </script>

View File

@ -1,89 +1,107 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('prompts.rename') }}</h2> <h2>{{ $t("prompts.rename") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p> <p>
<input class="input input--block" v-focus type="text" @keyup.enter="submit" v-model.trim="name"> {{ $t("prompts.renameMessage") }} <code>{{ oldName() }}</code
>:
</p>
<input
class="input input--block"
v-focus
type="text"
@keyup.enter="submit"
v-model.trim="name"
/>
</div> </div>
<div class="card-action"> <div class="card-action">
<button class="button button--flat button--grey" <button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')"
<button @click="submit" >
{{ $t("buttons.cancel") }}
</button>
<button
@click="submit"
class="button button--flat" class="button button--flat"
type="submit" type="submit"
:aria-label="$t('buttons.rename')" :aria-label="$t('buttons.rename')"
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button> :title="$t('buttons.rename')"
>
{{ $t("buttons.rename") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from "vuex";
import url from '@/utils/url' import url from "@/utils/url";
import { files as api } from '@/api' import { files as api } from "@/api";
export default { export default {
name: 'rename', name: "rename",
data: function () { data: function () {
return { return {
name: '' name: "",
} };
}, },
created() { created() {
this.name = this.oldName() this.name = this.oldName();
}, },
computed: { computed: {
...mapState(['req', 'selected', 'selectedCount']), ...mapState(["req", "selected", "selectedCount"]),
...mapGetters(['isListing']) ...mapGetters(["isListing"]),
}, },
methods: { methods: {
cancel: function () { cancel: function () {
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
}, },
oldName: function () { oldName: function () {
if (!this.isListing) { if (!this.isListing) {
return this.req.name return this.req.name;
} }
if (this.selectedCount === 0 || this.selectedCount > 1) { if (this.selectedCount === 0 || this.selectedCount > 1) {
// This shouldn't happen. // This shouldn't happen.
return return;
} }
return this.req.items[this.selected[0]].name return this.req.items[this.selected[0]].name;
}, },
submit: async function () { submit: async function () {
let oldLink = '' let oldLink = "";
let newLink = '' let newLink = "";
if (!this.isListing) { if (!this.isListing) {
oldLink = this.req.url oldLink = this.req.url;
} else { } else {
oldLink = this.req.items[this.selected[0]].url oldLink = this.req.items[this.selected[0]].url;
} }
newLink = url.removeLastDir(oldLink) + '/' + encodeURIComponent(this.name) newLink =
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
try { try {
await api.move([{ from: oldLink, to: newLink }]) await api.move([{ from: oldLink, to: newLink }]);
if (!this.isListing) { if (!this.isListing) {
this.$router.push({ path: newLink }) this.$router.push({ path: newLink });
return return;
} }
this.$store.commit('setReload', true) this.$store.commit("setReload", true);
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
} },
} },
} };
</script> </script>

View File

@ -1,31 +1,39 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('prompts.replace') }}</h2> <h2>{{ $t("prompts.replace") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p>{{ $t('prompts.replaceMessage') }}</p> <p>{{ $t("prompts.replaceMessage") }}</p>
</div> </div>
<div class="card-action"> <div class="card-action">
<button class="button button--flat button--grey" <button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')"
<button class="button button--flat button--red" >
{{ $t("buttons.cancel") }}
</button>
<button
class="button button--flat button--red"
@click="showConfirm" @click="showConfirm"
:aria-label="$t('buttons.replace')" :aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button> :title="$t('buttons.replace')"
>
{{ $t("buttons.replace") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
export default { export default {
name: 'replace', name: "replace",
computed: mapState(['showConfirm']) computed: mapState(["showConfirm"]),
} };
</script> </script>

View File

@ -1,35 +1,47 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('prompts.replace') }}</h2> <h2>{{ $t("prompts.replace") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p>{{ $t('prompts.replaceMessage') }}</p> <p>{{ $t("prompts.replaceMessage") }}</p>
</div> </div>
<div class="card-action"> <div class="card-action">
<button class="button button--flat button--grey" <button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')"
<button class="button button--flat button--blue" >
{{ $t("buttons.cancel") }}
</button>
<button
class="button button--flat button--blue"
@click="(event) => showConfirm(event, 'rename')" @click="(event) => showConfirm(event, 'rename')"
:aria-label="$t('buttons.rename')" :aria-label="$t('buttons.rename')"
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button> :title="$t('buttons.rename')"
<button class="button button--flat button--red" >
{{ $t("buttons.rename") }}
</button>
<button
class="button button--flat button--red"
@click="(event) => showConfirm(event, 'overwrite')" @click="(event) => showConfirm(event, 'overwrite')"
:aria-label="$t('buttons.replace')" :aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button> :title="$t('buttons.replace')"
>
{{ $t("buttons.replace") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
export default { export default {
name: 'replace-rename', name: "replace-rename",
computed: mapState(['showConfirm']) computed: mapState(["showConfirm"]),
} };
</script> </script>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="card floating share__promt__card" id="share"> <div class="card floating share__promt__card" id="share">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('buttons.share') }}</h2> <h2>{{ $t("buttons.share") }}</h2>
</div> </div>
<template v-if="listing"> <template v-if="listing">
@ -9,7 +9,7 @@
<table> <table>
<tr> <tr>
<th>#</th> <th>#</th>
<th>{{ $t('settings.shareDuration') }}</th> <th>{{ $t("settings.shareDuration") }}</th>
<th></th> <th></th>
<th></th> <th></th>
</tr> </tr>
@ -17,188 +17,219 @@
<tr v-for="link in links" :key="link.hash"> <tr v-for="link in links" :key="link.hash">
<td>{{ link.hash }}</td> <td>{{ link.hash }}</td>
<td> <td>
<template v-if="link.expire !== 0">{{ humanTime(link.expire) }}</template> <template v-if="link.expire !== 0">{{
<template v-else>{{ $t('permanent') }}</template> humanTime(link.expire)
}}</template>
<template v-else>{{ $t("permanent") }}</template>
</td> </td>
<td class="small"> <td class="small">
<button class="action copy-clipboard" <button
class="action copy-clipboard"
:data-clipboard-text="buildLink(link.hash)" :data-clipboard-text="buildLink(link.hash)"
:aria-label="$t('buttons.copyToClipboard')" :aria-label="$t('buttons.copyToClipboard')"
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button> :title="$t('buttons.copyToClipboard')"
>
<i class="material-icons">content_paste</i>
</button>
</td> </td>
<td class="small"> <td class="small">
<button class="action" <button
class="action"
@click="deleteLink($event, link)" @click="deleteLink($event, link)"
:aria-label="$t('buttons.delete')" :aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button> :title="$t('buttons.delete')"
>
<i class="material-icons">delete</i>
</button>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
<div class="card-action"> <div class="card-action">
<button class="button button--flat button--grey" <button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
:aria-label="$t('buttons.close')" :aria-label="$t('buttons.close')"
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button> :title="$t('buttons.close')"
<button class="button button--flat button--blue" >
{{ $t("buttons.close") }}
</button>
<button
class="button button--flat button--blue"
@click="() => switchListing()" @click="() => switchListing()"
:aria-label="$t('buttons.new')" :aria-label="$t('buttons.new')"
:title="$t('buttons.new')">{{ $t('buttons.new') }}</button> :title="$t('buttons.new')"
>
{{ $t("buttons.new") }}
</button>
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div class="card-content"> <div class="card-content">
<p>{{ $t('settings.shareDuration') }}</p> <p>{{ $t("settings.shareDuration") }}</p>
<div class="input-group input"> <div class="input-group input">
<input v-focus <input
v-focus
type="number" type="number"
max="2147483647" max="2147483647"
min="1" min="1"
@keyup.enter="submit" @keyup.enter="submit"
v-model.trim="time"> v-model.trim="time"
/>
<select class="right" v-model="unit" :aria-label="$t('time.unit')"> <select class="right" v-model="unit" :aria-label="$t('time.unit')">
<option value="seconds">{{ $t('time.seconds') }}</option> <option value="seconds">{{ $t("time.seconds") }}</option>
<option value="minutes">{{ $t('time.minutes') }}</option> <option value="minutes">{{ $t("time.minutes") }}</option>
<option value="hours">{{ $t('time.hours') }}</option> <option value="hours">{{ $t("time.hours") }}</option>
<option value="days">{{ $t('time.days') }}</option> <option value="days">{{ $t("time.days") }}</option>
</select> </select>
</div> </div>
<p>{{ $t('prompts.optionalPassword') }}</p> <p>{{ $t("prompts.optionalPassword") }}</p>
<input class="input input--block" type="password" v-model.trim="password"> <input
class="input input--block"
type="password"
v-model.trim="password"
/>
</div> </div>
<div class="card-action"> <div class="card-action">
<button class="button button--flat button--grey" <button
class="button button--flat button--grey"
@click="() => switchListing()" @click="() => switchListing()"
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')"
<button class="button button--flat button--blue" >
{{ $t("buttons.cancel") }}
</button>
<button
class="button button--flat button--blue"
@click="submit" @click="submit"
:aria-label="$t('buttons.share')" :aria-label="$t('buttons.share')"
:title="$t('buttons.share')">{{ $t('buttons.share') }}</button> :title="$t('buttons.share')"
>
{{ $t("buttons.share") }}
</button>
</div> </div>
</template> </template>
</div> </div>
</template> </template>
<script> <script>
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from "vuex";
import { share as api } from '@/api' import { share as api } from "@/api";
import { baseURL } from '@/utils/constants' import { baseURL } from "@/utils/constants";
import moment from 'moment' import moment from "moment";
import Clipboard from 'clipboard' import Clipboard from "clipboard";
export default { export default {
name: 'share', name: "share",
data: function () { data: function () {
return { return {
time: '', time: "",
unit: 'hours', unit: "hours",
links: [], links: [],
clip: null, clip: null,
password: '', password: "",
listing: true listing: true,
} };
}, },
computed: { computed: {
...mapState([ 'req', 'selected', 'selectedCount' ]), ...mapState(["req", "selected", "selectedCount"]),
...mapGetters([ 'isListing' ]), ...mapGetters(["isListing"]),
url() { url() {
if (!this.isListing) { if (!this.isListing) {
return this.$route.path return this.$route.path;
} }
if (this.selectedCount === 0 || this.selectedCount > 1) { if (this.selectedCount === 0 || this.selectedCount > 1) {
// This shouldn't happen. // This shouldn't happen.
return return;
} }
return this.req.items[this.selected[0]].url return this.req.items[this.selected[0]].url;
} },
}, },
async beforeMount() { async beforeMount() {
try { try {
const links = await api.get(this.url) const links = await api.get(this.url);
this.links = links this.links = links;
this.sort() this.sort();
if (this.links.length == 0) { if (this.links.length == 0) {
this.listing = false this.listing = false;
} }
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
}, },
mounted() { mounted() {
this.clip = new Clipboard('.copy-clipboard') this.clip = new Clipboard(".copy-clipboard");
this.clip.on('success', () => { this.clip.on("success", () => {
this.$showSuccess(this.$t('success.linkCopied')) this.$showSuccess(this.$t("success.linkCopied"));
}) });
}, },
beforeDestroy() { beforeDestroy() {
this.clip.destroy() this.clip.destroy();
}, },
methods: { methods: {
submit: async function () { submit: async function () {
let isPermanent = !this.time || this.time == 0 let isPermanent = !this.time || this.time == 0;
try { try {
let res = null let res = null;
if (isPermanent) { if (isPermanent) {
res = await api.create(this.url, this.password) res = await api.create(this.url, this.password);
} else { } else {
res = await api.create(this.url, this.password, this.time, this.unit) res = await api.create(this.url, this.password, this.time, this.unit);
} }
this.links.push(res) this.links.push(res);
this.sort() this.sort();
this.time = '' this.time = "";
this.unit = 'hours' this.unit = "hours";
this.password = '' this.password = "";
this.listing = true this.listing = true;
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
}, },
deleteLink: async function (event, link) { deleteLink: async function (event, link) {
event.preventDefault() event.preventDefault();
try { try {
await api.remove(link.hash) await api.remove(link.hash);
this.links = this.links.filter(item => item.hash !== link.hash) this.links = this.links.filter((item) => item.hash !== link.hash);
if (this.links.length == 0) { if (this.links.length == 0) {
this.listing = false this.listing = false;
} }
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
}, },
humanTime(time) { humanTime(time) {
return moment(time * 1000).fromNow() return moment(time * 1000).fromNow();
}, },
buildLink(hash) { buildLink(hash) {
return `${window.location.origin}${baseURL}/share/${hash}` return `${window.location.origin}${baseURL}/share/${hash}`;
}, },
sort() { sort() {
this.links = this.links.sort((a, b) => { this.links = this.links.sort((a, b) => {
if (a.expire === 0) return -1 if (a.expire === 0) return -1;
if (b.expire === 0) return 1 if (b.expire === 0) return 1;
return new Date(a.expire) - new Date(b.expire) return new Date(a.expire) - new Date(b.expire);
}) });
}, },
switchListing() { switchListing() {
if (this.links.length == 0 && !this.listing) { if (this.links.length == 0 && !this.listing) {
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
} }
this.listing = !this.listing this.listing = !this.listing;
} },
} },
} };
</script> </script>

View File

@ -1,33 +1,41 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-content"> <div class="card-content">
<p>{{ $t('prompts.deleteMessageShare', {path: ''}) }}</p> <p>{{ $t("prompts.deleteMessageShare", { path: "" }) }}</p>
</div> </div>
<div class="card-action"> <div class="card-action">
<button @click="$store.commit('closeHovers')" <button
@click="$store.commit('closeHovers')"
class="button button--flat button--grey" class="button button--flat button--grey"
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')"
<button @click="submit" >
{{ $t("buttons.cancel") }}
</button>
<button
@click="submit"
class="button button--flat button--red" class="button button--flat button--red"
:aria-label="$t('buttons.delete')" :aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button> :title="$t('buttons.delete')"
>
{{ $t("buttons.delete") }}
</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import {mapState} from 'vuex' import { mapState } from "vuex";
export default { export default {
name: 'share-delete', name: "share-delete",
computed: { computed: {
...mapState(['showConfirm']) ...mapState(["showConfirm"]),
}, },
methods: { methods: {
submit: function () { submit: function () {
this.showConfirm() this.showConfirm();
} },
} },
} };
</script> </script>

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="card floating"> <div class="card floating">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('prompts.upload') }}</h2> <h2>{{ $t("prompts.upload") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p>{{ $t('prompts.uploadMessage') }}</p> <p>{{ $t("prompts.uploadMessage") }}</p>
</div> </div>
<div class="card-action full"> <div class="card-action full">
@ -22,18 +22,17 @@
</template> </template>
<script> <script>
export default { export default {
name: 'upload', name: "upload",
methods: { methods: {
uploadFile: function () { uploadFile: function () {
document.getElementById('upload-input').value = '' document.getElementById("upload-input").value = "";
document.getElementById('upload-input').click() document.getElementById("upload-input").click();
}, },
uploadFolder: function () { uploadFolder: function () {
document.getElementById('upload-folder-input').value = '' document.getElementById("upload-folder-input").value = "";
document.getElementById('upload-folder-input').click() document.getElementById("upload-folder-input").click();
} },
} },
} };
</script> </script>

View File

@ -1,28 +1,30 @@
<template> <template>
<div> <div>
<h3>{{ $t('settings.userCommands') }}</h3> <h3>{{ $t("settings.userCommands") }}</h3>
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p> <p class="small">
<input class="input input--block" type="text" v-model.trim="raw"> {{ $t("settings.userCommandsHelp") }} <i>git svn hg</i>.
</p>
<input class="input input--block" type="text" v-model.trim="raw" />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'permissions', name: "permissions",
props: ['commands'], props: ["commands"],
computed: { computed: {
raw: { raw: {
get() { get() {
return this.commands.join(' ') return this.commands.join(" ");
}, },
set(value) { set(value) {
if (value !== '') { if (value !== "") {
this.$emit('update:commands', value.split(' ')) this.$emit("update:commands", value.split(" "));
} else { } else {
this.$emit('update:commands', []) this.$emit("update:commands", []);
}
}
}
}
} }
},
},
},
};
</script> </script>

View File

@ -1,46 +1,50 @@
<template> <template>
<select v-on:change="change" :value="locale"> <select v-on:change="change" :value="locale">
<option v-for="(language, value) in locales" :key="value" :value="value">{{ $t('languages.' + language) }}</option> <option v-for="(language, value) in locales" :key="value" :value="value">
{{ $t("languages." + language) }}
</option>
</select> </select>
</template> </template>
<script> <script>
export default { export default {
name: 'languages', name: "languages",
props: [ 'locale' ], props: ["locale"],
data() { data() {
let dataObj = { let dataObj = {
locales: { locales: {
ar: 'ar', ar: "ar",
de: 'de', de: "de",
en: 'en', en: "en",
es: 'es', es: "es",
fr: 'fr', fr: "fr",
is: 'is', is: "is",
it: 'it', it: "it",
ja: 'ja', ja: "ja",
ko: 'ko', ko: "ko",
'nl-be': 'nlBE', "nl-be": "nlBE",
pl: 'pl', pl: "pl",
'pt-br': 'ptBR', "pt-br": "ptBR",
pt: 'pt', pt: "pt",
ro: 'ro', ro: "ro",
ru: 'ru', ru: "ru",
'sv-se': 'svSE', "sv-se": "svSE",
'zh-cn': 'zhCN', "zh-cn": "zhCN",
'zh-tw': 'zhTW' "zh-tw": "zhTW",
} },
}; };
Object.defineProperty(dataObj, "locales", { configurable: false, writable: false }); Object.defineProperty(dataObj, "locales", {
configurable: false,
writable: false,
});
return dataObj; return dataObj;
}, },
methods: { methods: {
change(event) { change(event) {
this.$emit('update:locale', event.target.value) this.$emit("update:locale", event.target.value);
} },
} },
} };
</script> </script>

View File

@ -1,41 +1,65 @@
<template> <template>
<div> <div>
<h3>{{ $t('settings.permissions') }}</h3> <h3>{{ $t("settings.permissions") }}</h3>
<p class="small">{{ $t('settings.permissionsHelp') }}</p> <p class="small">{{ $t("settings.permissionsHelp") }}</p>
<p><input type="checkbox" v-model="admin"> {{ $t('settings.administrator') }}</p> <p>
<input type="checkbox" v-model="admin" />
{{ $t("settings.administrator") }}
</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.create"> {{ $t('settings.perm.create') }}</p> <p>
<p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p> <input type="checkbox" :disabled="admin" v-model="perm.create" />
<p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p> {{ $t("settings.perm.create") }}
<p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p> </p>
<p v-if="isExecEnabled"><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p> <p>
<p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p> <input type="checkbox" :disabled="admin" v-model="perm.delete" />
<p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p> {{ $t("settings.perm.delete") }}
</p>
<p>
<input type="checkbox" :disabled="admin" v-model="perm.download" />
{{ $t("settings.perm.download") }}
</p>
<p>
<input type="checkbox" :disabled="admin" v-model="perm.modify" />
{{ $t("settings.perm.modify") }}
</p>
<p v-if="isExecEnabled">
<input type="checkbox" :disabled="admin" v-model="perm.execute" />
{{ $t("settings.perm.execute") }}
</p>
<p>
<input type="checkbox" :disabled="admin" v-model="perm.rename" />
{{ $t("settings.perm.rename") }}
</p>
<p>
<input type="checkbox" :disabled="admin" v-model="perm.share" />
{{ $t("settings.perm.share") }}
</p>
</div> </div>
</template> </template>
<script> <script>
import { enableExec } from '@/utils/constants' import { enableExec } from "@/utils/constants";
export default { export default {
name: 'permissions', name: "permissions",
props: ['perm'], props: ["perm"],
computed: { computed: {
admin: { admin: {
get() { get() {
return this.perm.admin return this.perm.admin;
}, },
set(value) { set(value) {
if (value) { if (value) {
for (const key in this.perm) { for (const key in this.perm) {
this.perm[key] = true this.perm[key] = true;
} }
} }
this.perm.admin = value this.perm.admin = value;
}
}, },
isExecEnabled: () => enableExec },
} isExecEnabled: () => enableExec,
} },
};
</script> </script>

View File

@ -1,57 +1,63 @@
<template> <template>
<form class="rules small"> <form class="rules small">
<div v-for="(rule, index) in rules" :key="index"> <div v-for="(rule, index) in rules" :key="index">
<input type="checkbox" v-model="rule.regex"><label>Regex</label> <input type="checkbox" v-model="rule.regex" /><label>Regex</label>
<input type="checkbox" v-model="rule.allow"><label>Allow</label> <input type="checkbox" v-model="rule.allow" /><label>Allow</label>
<input <input
@keypress.enter.prevent @keypress.enter.prevent
type="text" type="text"
v-if="rule.regex" v-if="rule.regex"
v-model="rule.regexp.raw" v-model="rule.regexp.raw"
:placeholder="$t('settings.insertRegex')" /> :placeholder="$t('settings.insertRegex')"
/>
<input <input
@keypress.enter.prevent @keypress.enter.prevent
type="text" type="text"
v-else v-else
v-model="rule.path" v-model="rule.path"
:placeholder="$t('settings.insertPath')" /> :placeholder="$t('settings.insertPath')"
/>
<button class="button button--red" @click="remove($event, index)">-</button> <button class="button button--red" @click="remove($event, index)">
-
</button>
</div> </div>
<div> <div>
<button class="button" @click="create" default="false">{{ $t('buttons.new') }}</button> <button class="button" @click="create" default="false">
{{ $t("buttons.new") }}
</button>
</div> </div>
</form> </form>
</template> </template>
<script> <script>
export default { export default {
name: 'rules-textarea', name: "rules-textarea",
props: ['rules'], props: ["rules"],
methods: { methods: {
remove(event, index) { remove(event, index) {
event.preventDefault() event.preventDefault();
let rules = [ ...this.rules ] let rules = [...this.rules];
rules.splice(index, 1) rules.splice(index, 1);
this.$emit('update:rules', [ ...rules ]) this.$emit("update:rules", [...rules]);
}, },
create(event) { create(event) {
event.preventDefault() event.preventDefault();
this.$emit('update:rules', [ this.$emit("update:rules", [
...this.rules, ...this.rules,
{ {
allow: true, allow: true,
path: '', path: "",
regex: false, regex: false,
regexp: { regexp: {
raw: '' raw: "",
} },
} },
]) ]);
} },
} },
} };
</script> </script>

View File

@ -1,18 +1,18 @@
<template> <template>
<select v-on:change="change" :value="theme"> <select v-on:change="change" :value="theme">
<option value="">{{ $t('settings.themes.light') }}</option> <option value="">{{ $t("settings.themes.light") }}</option>
<option value="dark">{{ $t('settings.themes.dark') }}</option> <option value="dark">{{ $t("settings.themes.dark") }}</option>
</select> </select>
</template> </template>
<script> <script>
export default { export default {
name: 'themes', name: "themes",
props: [ 'theme' ], props: ["theme"],
methods: { methods: {
change(event) { change(event) {
this.$emit('update:theme', event.target.value) this.$emit("update:theme", event.target.value);
} },
} },
} };
</script> </script>

View File

@ -1,67 +1,92 @@
<template> <template>
<div> <div>
<p v-if="!isDefault"> <p v-if="!isDefault">
<label for="username">{{ $t('settings.username') }}</label> <label for="username">{{ $t("settings.username") }}</label>
<input class="input input--block" type="text" v-model="user.username" id="username"> <input
class="input input--block"
type="text"
v-model="user.username"
id="username"
/>
</p> </p>
<p v-if="!isDefault"> <p v-if="!isDefault">
<label for="password">{{ $t('settings.password') }}</label> <label for="password">{{ $t("settings.password") }}</label>
<input class="input input--block" type="password" :placeholder="passwordPlaceholder" v-model="user.password" id="password"> <input
class="input input--block"
type="password"
:placeholder="passwordPlaceholder"
v-model="user.password"
id="password"
/>
</p> </p>
<p> <p>
<label for="scope">{{ $t('settings.scope') }}</label> <label for="scope">{{ $t("settings.scope") }}</label>
<input class="input input--block" type="text" v-model="user.scope" id="scope"> <input
class="input input--block"
type="text"
v-model="user.scope"
id="scope"
/>
</p> </p>
<p> <p>
<label for="locale">{{ $t('settings.language') }}</label> <label for="locale">{{ $t("settings.language") }}</label>
<languages class="input input--block" id="locale" :locale.sync="user.locale"></languages> <languages
class="input input--block"
id="locale"
:locale.sync="user.locale"
></languages>
</p> </p>
<p v-if="!isDefault"> <p v-if="!isDefault">
<input type="checkbox" :disabled="user.perm.admin" v-model="user.lockPassword"> {{ $t('settings.lockPassword') }} <input
type="checkbox"
:disabled="user.perm.admin"
v-model="user.lockPassword"
/>
{{ $t("settings.lockPassword") }}
</p> </p>
<permissions :perm.sync="user.perm" /> <permissions :perm.sync="user.perm" />
<commands v-if="isExecEnabled" :commands.sync="user.commands" /> <commands v-if="isExecEnabled" :commands.sync="user.commands" />
<div v-if="!isDefault"> <div v-if="!isDefault">
<h3>{{ $t('settings.rules') }}</h3> <h3>{{ $t("settings.rules") }}</h3>
<p class="small">{{ $t('settings.rulesHelp') }}</p> <p class="small">{{ $t("settings.rulesHelp") }}</p>
<rules :rules.sync="user.rules" /> <rules :rules.sync="user.rules" />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import Languages from './Languages' import Languages from "./Languages";
import Rules from './Rules' import Rules from "./Rules";
import Permissions from './Permissions' import Permissions from "./Permissions";
import Commands from './Commands' import Commands from "./Commands";
import { enableExec } from '@/utils/constants' import { enableExec } from "@/utils/constants";
export default { export default {
name: 'user', name: "user",
components: { components: {
Permissions, Permissions,
Languages, Languages,
Rules, Rules,
Commands Commands,
}, },
props: [ 'user', 'isNew', 'isDefault' ], props: ["user", "isNew", "isDefault"],
computed: { computed: {
passwordPlaceholder() { passwordPlaceholder() {
return this.isNew ? '' : this.$t('settings.avoidChanges') return this.isNew ? "" : this.$t("settings.avoidChanges");
}, },
isExecEnabled: () => enableExec isExecEnabled: () => enableExec,
}, },
watch: { watch: {
'user.perm.admin': function () { "user.perm.admin": function () {
if (!this.user.perm.admin) return if (!this.user.perm.admin) return;
this.user.lockPassword = false this.user.lockPassword = false;
} },
} },
} };
</script> </script>

View File

@ -1,116 +1,116 @@
import Vue from 'vue' import Vue from "vue";
import VueI18n from 'vue-i18n' import VueI18n from "vue-i18n";
import ar from './ar.json' import ar from "./ar.json";
import de from './de.json' import de from "./de.json";
import en from './en.json' import en from "./en.json";
import es from './es.json' import es from "./es.json";
import fr from './fr.json' import fr from "./fr.json";
import is from './is.json' import is from "./is.json";
import it from './it.json' import it from "./it.json";
import ja from './ja.json' import ja from "./ja.json";
import ko from './ko.json' import ko from "./ko.json";
import nlBE from './nl-be.json' import nlBE from "./nl-be.json";
import pl from './pl.json' import pl from "./pl.json";
import pt from './pt.json' import pt from "./pt.json";
import ptBR from './pt-br.json' import ptBR from "./pt-br.json";
import ro from './ro.json' import ro from "./ro.json";
import ru from './ru.json' import ru from "./ru.json";
import svSE from './sv-se.json' import svSE from "./sv-se.json";
import zhCN from './zh-cn.json' import zhCN from "./zh-cn.json";
import zhTW from './zh-tw.json' import zhTW from "./zh-tw.json";
Vue.use(VueI18n) Vue.use(VueI18n);
export function detectLocale() { export function detectLocale() {
let locale = (navigator.language || navigator.browserLangugae).toLowerCase() let locale = (navigator.language || navigator.browserLangugae).toLowerCase();
switch (true) { switch (true) {
case /^ar.*/i.test(locale): case /^ar.*/i.test(locale):
locale = 'ar' locale = "ar";
break break;
case /^es.*/i.test(locale): case /^es.*/i.test(locale):
locale = 'es' locale = "es";
break break;
case /^en.*/i.test(locale): case /^en.*/i.test(locale):
locale = 'en' locale = "en";
break break;
case /^it.*/i.test(locale): case /^it.*/i.test(locale):
locale = 'it' locale = "it";
break break;
case /^fr.*/i.test(locale): case /^fr.*/i.test(locale):
locale = 'fr' locale = "fr";
break break;
case /^pt.*/i.test(locale): case /^pt.*/i.test(locale):
locale = 'pt' locale = "pt";
break break;
case /^pt-BR.*/i.test(locale): case /^pt-BR.*/i.test(locale):
locale = 'pt-br' locale = "pt-br";
break break;
case /^ja.*/i.test(locale): case /^ja.*/i.test(locale):
locale = 'ja' locale = "ja";
break break;
case /^zh-CN/i.test(locale): case /^zh-CN/i.test(locale):
locale = 'zh-cn' locale = "zh-cn";
break break;
case /^zh-TW/i.test(locale): case /^zh-TW/i.test(locale):
locale = 'zh-tw' locale = "zh-tw";
break break;
case /^zh.*/i.test(locale): case /^zh.*/i.test(locale):
locale = 'zh-cn' locale = "zh-cn";
break break;
case /^de.*/i.test(locale): case /^de.*/i.test(locale):
locale = 'de' locale = "de";
break break;
case /^ru.*/i.test(locale): case /^ru.*/i.test(locale):
locale = 'ru' locale = "ru";
break break;
case /^pl.*/i.test(locale): case /^pl.*/i.test(locale):
locale = 'pl' locale = "pl";
break break;
case /^ko.*/i.test(locale): case /^ko.*/i.test(locale):
locale = 'ko' locale = "ko";
break break;
default: default:
locale = 'en' locale = "en";
} }
return locale return locale;
} }
const removeEmpty = (obj) => const removeEmpty = (obj) =>
Object.keys(obj) Object.keys(obj)
.filter((k) => obj[k] !== null && obj[k] !== undefined && obj[k] !== '') // Remove undef. and null and empty.string. .filter((k) => obj[k] !== null && obj[k] !== undefined && obj[k] !== "") // Remove undef. and null and empty.string.
.reduce( .reduce(
(newObj, k) => (newObj, k) =>
typeof obj[k] === 'object' typeof obj[k] === "object"
? Object.assign(newObj, { [k]: removeEmpty(obj[k]) }) // Recurse. ? Object.assign(newObj, { [k]: removeEmpty(obj[k]) }) // Recurse.
: Object.assign(newObj, { [k]: obj[k] }), // Copy value. : Object.assign(newObj, { [k]: obj[k] }), // Copy value.
{}, {}
); );
const i18n = new VueI18n({ const i18n = new VueI18n({
locale: detectLocale(), locale: detectLocale(),
fallbackLocale: 'en', fallbackLocale: "en",
messages: { messages: {
'ar': removeEmpty(ar), ar: removeEmpty(ar),
'de': removeEmpty(de), de: removeEmpty(de),
'en': en, en: en,
'es': removeEmpty(es), es: removeEmpty(es),
'fr': removeEmpty(fr), fr: removeEmpty(fr),
'is': removeEmpty(is), is: removeEmpty(is),
'it': removeEmpty(it), it: removeEmpty(it),
'ja': removeEmpty(ja), ja: removeEmpty(ja),
'ko': removeEmpty(ko), ko: removeEmpty(ko),
'nl-be': removeEmpty(nlBE), "nl-be": removeEmpty(nlBE),
'pl': removeEmpty(pl), pl: removeEmpty(pl),
'pt-br': removeEmpty(ptBR), "pt-br": removeEmpty(ptBR),
'pt': removeEmpty(pt), pt: removeEmpty(pt),
'ru': removeEmpty(ru), ru: removeEmpty(ru),
'ro': removeEmpty(ro), ro: removeEmpty(ro),
'sv-se': removeEmpty(svSE), "sv-se": removeEmpty(svSE),
'zh-cn': removeEmpty(zhCN), "zh-cn": removeEmpty(zhCN),
'zh-tw': removeEmpty(zhTW) "zh-tw": removeEmpty(zhTW),
} },
}) });
export default i18n export default i18n;

View File

@ -1,43 +1,43 @@
import { sync } from 'vuex-router-sync' import { sync } from "vuex-router-sync";
import store from '@/store' import store from "@/store";
import router from '@/router' import router from "@/router";
import i18n from '@/i18n' import i18n from "@/i18n";
import Vue from '@/utils/vue' import Vue from "@/utils/vue";
import { recaptcha, loginPage } from '@/utils/constants' import { recaptcha, loginPage } from "@/utils/constants";
import { login, validateLogin } from '@/utils/auth' import { login, validateLogin } from "@/utils/auth";
import App from '@/App' import App from "@/App";
sync(store, router) sync(store, router);
async function start() { async function start() {
if (loginPage) { if (loginPage) {
await validateLogin() await validateLogin();
} else { } else {
await login('', '', '') await login("", "", "");
} }
if (recaptcha) { if (recaptcha) {
await new Promise (resolve => { await new Promise((resolve) => {
const check = () => { const check = () => {
if (typeof window.grecaptcha === 'undefined') { if (typeof window.grecaptcha === "undefined") {
setTimeout(check, 100) setTimeout(check, 100);
} else { } else {
resolve() resolve();
}
} }
};
check() check();
}) });
} }
new Vue({ new Vue({
el: '#app', el: "#app",
store, store,
router, router,
i18n, i18n,
template: '<App/>', template: "<App/>",
components: { App } components: { App },
}) });
} }
start() start();

View File

@ -1,166 +1,166 @@
import Vue from 'vue' import Vue from "vue";
import Router from 'vue-router' import Router from "vue-router";
import Login from '@/views/Login' import Login from "@/views/Login";
import Layout from '@/views/Layout' import Layout from "@/views/Layout";
import Files from '@/views/Files' import Files from "@/views/Files";
import Share from '@/views/Share' import Share from "@/views/Share";
import Users from '@/views/settings/Users' import Users from "@/views/settings/Users";
import User from '@/views/settings/User' import User from "@/views/settings/User";
import Settings from '@/views/Settings' import Settings from "@/views/Settings";
import GlobalSettings from '@/views/settings/Global' import GlobalSettings from "@/views/settings/Global";
import ProfileSettings from '@/views/settings/Profile' import ProfileSettings from "@/views/settings/Profile";
import Shares from '@/views/settings/Shares' import Shares from "@/views/settings/Shares";
import Errors from '@/views/Errors' import Errors from "@/views/Errors";
import store from '@/store' import store from "@/store";
import { baseURL } from '@/utils/constants' import { baseURL } from "@/utils/constants";
Vue.use(Router) Vue.use(Router);
const router = new Router({ const router = new Router({
base: baseURL, base: baseURL,
mode: 'history', mode: "history",
routes: [ routes: [
{ {
path: '/login', path: "/login",
name: 'Login', name: "Login",
component: Login, component: Login,
beforeEnter: (to, from, next) => { beforeEnter: (to, from, next) => {
if (store.getters.isLogged) { if (store.getters.isLogged) {
return next({ path: '/files' }) return next({ path: "/files" });
} }
document.title = 'Login' document.title = "Login";
next() next();
} },
}, },
{ {
path: '/*', path: "/*",
component: Layout, component: Layout,
children: [ children: [
{ {
path: '/share/*', path: "/share/*",
name: 'Share', name: "Share",
component: Share component: Share,
}, },
{ {
path: '/files/*', path: "/files/*",
name: 'Files', name: "Files",
component: Files, component: Files,
meta: { meta: {
requiresAuth: true requiresAuth: true,
} },
}, },
{ {
path: '/settings', path: "/settings",
name: 'Settings', name: "Settings",
component: Settings, component: Settings,
redirect: { redirect: {
path: '/settings/profile' path: "/settings/profile",
}, },
meta: { meta: {
requiresAuth: true requiresAuth: true,
}, },
children: [ children: [
{ {
path: '/settings/profile', path: "/settings/profile",
name: 'Profile Settings', name: "Profile Settings",
component: ProfileSettings component: ProfileSettings,
}, },
{ {
path: '/settings/shares', path: "/settings/shares",
name: 'Shares', name: "Shares",
component: Shares component: Shares,
}, },
{ {
path: '/settings/global', path: "/settings/global",
name: 'Global Settings', name: "Global Settings",
component: GlobalSettings, component: GlobalSettings,
meta: { meta: {
requiresAdmin: true requiresAdmin: true,
} },
}, },
{ {
path: '/settings/users', path: "/settings/users",
name: 'Users', name: "Users",
component: Users, component: Users,
meta: { meta: {
requiresAdmin: true requiresAdmin: true,
} },
}, },
{ {
path: '/settings/users/*', path: "/settings/users/*",
name: 'User', name: "User",
component: User, component: User,
meta: { meta: {
requiresAdmin: true requiresAdmin: true,
} },
} },
] ],
}, },
{ {
path: '/403', path: "/403",
name: 'Forbidden', name: "Forbidden",
component: Errors, component: Errors,
props: { props: {
errorCode: 403, errorCode: 403,
showHeader: true showHeader: true,
} },
}, },
{ {
path: '/404', path: "/404",
name: 'Not Found', name: "Not Found",
component: Errors, component: Errors,
props: { props: {
errorCode: 404, errorCode: 404,
showHeader: true showHeader: true,
} },
}, },
{ {
path: '/500', path: "/500",
name: 'Internal Server Error', name: "Internal Server Error",
component: Errors, component: Errors,
props: { props: {
errorCode: 500, errorCode: 500,
showHeader: true showHeader: true,
} },
}, },
{ {
path: '/files', path: "/files",
redirect: { redirect: {
path: '/files/' path: "/files/",
} },
}, },
{ {
path: '/*', path: "/*",
redirect: to => `/files${to.path}` redirect: (to) => `/files${to.path}`,
} },
] ],
} },
] ],
}) });
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
document.title = to.name document.title = to.name;
if (to.matched.some(record => record.meta.requiresAuth)) { if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!store.getters.isLogged) { if (!store.getters.isLogged) {
next({ next({
path: '/login', path: "/login",
query: { redirect: to.fullPath } query: { redirect: to.fullPath },
}) });
return return;
} }
if (to.matched.some(record => record.meta.requiresAdmin)) { if (to.matched.some((record) => record.meta.requiresAdmin)) {
if (!store.state.user.perm.admin) { if (!store.state.user.perm.admin) {
next({ path: '/403' }) next({ path: "/403" });
return return;
} }
} }
} }
next() next();
}) });
export default router export default router;

View File

@ -1,16 +1,16 @@
const getters = { const getters = {
isLogged: state => state.user !== null, isLogged: (state) => state.user !== null,
isFiles: state => !state.loading && state.route.name === 'Files', isFiles: (state) => !state.loading && state.route.name === "Files",
isListing: (state, getters) => getters.isFiles && state.req.isDir, isListing: (state, getters) => getters.isFiles && state.req.isDir,
selectedCount: state => state.selected.length, selectedCount: (state) => state.selected.length,
progress : state => { progress: (state) => {
if (state.upload.progress.length == 0) { if (state.upload.progress.length == 0) {
return 0; return 0;
} }
let sum = state.upload.progress.reduce((acc, val) => acc + val) let sum = state.upload.progress.reduce((acc, val) => acc + val);
return Math.ceil(sum / state.upload.size * 100); return Math.ceil((sum / state.upload.size) * 100);
} },
} };
export default getters export default getters;

View File

@ -1,20 +1,20 @@
import Vue from 'vue' import Vue from "vue";
import Vuex from 'vuex' import Vuex from "vuex";
import mutations from './mutations' import mutations from "./mutations";
import getters from './getters' import getters from "./getters";
import upload from './modules/upload' import upload from "./modules/upload";
Vue.use(Vuex) Vue.use(Vuex);
const state = { const state = {
user: null, user: null,
req: {}, req: {},
oldReq: {}, oldReq: {},
clipboard: { clipboard: {
key: '', key: "",
items: [] items: [],
}, },
jwt: '', jwt: "",
progress: 0, progress: 0,
loading: false, loading: false,
reload: false, reload: false,
@ -22,13 +22,13 @@ const state = {
multiple: false, multiple: false,
show: null, show: null,
showShell: false, showShell: false,
showConfirm: null showConfirm: null,
} };
export default new Vuex.Store({ export default new Vuex.Store({
strict: true, strict: true,
state, state,
getters, getters,
mutations, mutations,
modules: { upload } modules: { upload },
}) });

View File

@ -1,7 +1,7 @@
import Vue from 'vue' import Vue from "vue";
import { files as api } from '@/api' import { files as api } from "@/api";
import throttle from 'lodash.throttle' import throttle from "lodash.throttle";
import buttons from '@/utils/buttons' import buttons from "@/utils/buttons";
const UPLOADS_LIMIT = 5; const UPLOADS_LIMIT = 5;
@ -10,93 +10,100 @@ const state = {
size: 0, size: 0,
progress: [], progress: [],
queue: [], queue: [],
uploads: {} uploads: {},
} };
const mutations = { const mutations = {
setProgress(state, { id, loaded }) { setProgress(state, { id, loaded }) {
Vue.set(state.progress, id, loaded) Vue.set(state.progress, id, loaded);
}, },
reset: (state) => { reset: (state) => {
state.id = 0 state.id = 0;
state.size = 0 state.size = 0;
state.progress = [] state.progress = [];
}, },
addJob: (state, item) => { addJob: (state, item) => {
state.queue.push(item) state.queue.push(item);
state.size += item.file.size state.size += item.file.size;
state.id++ state.id++;
}, },
moveJob(state) { moveJob(state) {
const item = state.queue[0] const item = state.queue[0];
state.queue.shift() state.queue.shift();
Vue.set(state.uploads, item.id, item) Vue.set(state.uploads, item.id, item);
}, },
removeJob(state, id) { removeJob(state, id) {
delete state.uploads[id] delete state.uploads[id];
} },
} };
const beforeUnload = (event) => { const beforeUnload = (event) => {
event.preventDefault() event.preventDefault();
event.returnValue = '' event.returnValue = "";
} };
const actions = { const actions = {
upload: (context, item) => { upload: (context, item) => {
let uploadsCount = Object.keys(context.state.uploads).length; let uploadsCount = Object.keys(context.state.uploads).length;
let isQueueEmpty = context.state.queue.length == 0 let isQueueEmpty = context.state.queue.length == 0;
let isUploadsEmpty = uploadsCount == 0 let isUploadsEmpty = uploadsCount == 0;
if (isQueueEmpty && isUploadsEmpty) { if (isQueueEmpty && isUploadsEmpty) {
window.addEventListener('beforeunload', beforeUnload) window.addEventListener("beforeunload", beforeUnload);
buttons.loading('upload') buttons.loading("upload");
} }
context.commit('addJob', item) context.commit("addJob", item);
context.dispatch('processUploads') context.dispatch("processUploads");
}, },
finishUpload: (context, item) => { finishUpload: (context, item) => {
context.commit('setProgress', { id: item.id, loaded: item.file.size }) context.commit("setProgress", { id: item.id, loaded: item.file.size });
context.commit('removeJob', item.id) context.commit("removeJob", item.id);
context.dispatch('processUploads') context.dispatch("processUploads");
}, },
processUploads: async (context) => { processUploads: async (context) => {
let uploadsCount = Object.keys(context.state.uploads).length; let uploadsCount = Object.keys(context.state.uploads).length;
let isBellowLimit = uploadsCount < UPLOADS_LIMIT let isBellowLimit = uploadsCount < UPLOADS_LIMIT;
let isQueueEmpty = context.state.queue.length == 0 let isQueueEmpty = context.state.queue.length == 0;
let isUploadsEmpty = uploadsCount == 0 let isUploadsEmpty = uploadsCount == 0;
let isFinished = isQueueEmpty && isUploadsEmpty let isFinished = isQueueEmpty && isUploadsEmpty;
let canProcess = isBellowLimit && !isQueueEmpty let canProcess = isBellowLimit && !isQueueEmpty;
if (isFinished) { if (isFinished) {
window.removeEventListener('beforeunload', beforeUnload) window.removeEventListener("beforeunload", beforeUnload);
buttons.success('upload') buttons.success("upload");
context.commit('reset') context.commit("reset");
context.commit('setReload', true, { root: true }) context.commit("setReload", true, { root: true });
} }
if (canProcess) { if (canProcess) {
const item = context.state.queue[0]; const item = context.state.queue[0];
context.commit('moveJob') context.commit("moveJob");
if (item.file.isDir) { if (item.file.isDir) {
await api.post(item.path).catch(Vue.prototype.$showError) await api.post(item.path).catch(Vue.prototype.$showError);
} else { } else {
let onUpload = throttle( let onUpload = throttle(
(event) => context.commit('setProgress', { id: item.id, loaded: event.loaded }), (event) =>
100, { leading: true, trailing: false } context.commit("setProgress", {
) id: item.id,
loaded: event.loaded,
}),
100,
{ leading: true, trailing: false }
);
await api.post(item.path, item.file, item.overwrite, onUpload).catch(Vue.prototype.$showError) await api
.post(item.path, item.file, item.overwrite, onUpload)
.catch(Vue.prototype.$showError);
} }
context.dispatch('finishUpload', item) context.dispatch("finishUpload", item);
}
}
} }
},
};
export default { state, mutations, actions, namespaced: true } export default { state, mutations, actions, namespaced: true };

View File

@ -1,83 +1,87 @@
import * as i18n from '@/i18n' import * as i18n from "@/i18n";
import moment from 'moment' import moment from "moment";
const mutations = { const mutations = {
closeHovers: state => { closeHovers: (state) => {
state.show = null state.show = null;
state.showConfirm = null state.showConfirm = null;
}, },
toggleShell: (state) => { toggleShell: (state) => {
state.showShell = !state.showShell state.showShell = !state.showShell;
}, },
showHover: (state, value) => { showHover: (state, value) => {
if (typeof value !== 'object') { if (typeof value !== "object") {
state.show = value state.show = value;
return return;
} }
state.show = value.prompt state.show = value.prompt;
state.showConfirm = value.confirm state.showConfirm = value.confirm;
}, },
showError: (state) => { showError: (state) => {
state.show = 'error' state.show = "error";
}, },
showSuccess: (state) => { showSuccess: (state) => {
state.show = 'success' state.show = "success";
},
setLoading: (state, value) => {
state.loading = value;
},
setReload: (state, value) => {
state.reload = value;
}, },
setLoading: (state, value) => { state.loading = value },
setReload: (state, value) => { state.reload = value },
setUser: (state, value) => { setUser: (state, value) => {
if (value === null) { if (value === null) {
state.user = null state.user = null;
return return;
} }
let locale = value.locale let locale = value.locale;
if (locale === '') { if (locale === "") {
locale = i18n.detectLocale() locale = i18n.detectLocale();
} }
moment.locale(locale) moment.locale(locale);
i18n.default.locale = locale i18n.default.locale = locale;
state.user = value state.user = value;
}, },
setJWT: (state, value) => (state.jwt = value), setJWT: (state, value) => (state.jwt = value),
multiple: (state, value) => (state.multiple = value), multiple: (state, value) => (state.multiple = value),
addSelected: (state, value) => (state.selected.push(value)), addSelected: (state, value) => state.selected.push(value),
removeSelected: (state, value) => { removeSelected: (state, value) => {
let i = state.selected.indexOf(value) let i = state.selected.indexOf(value);
if (i === -1) return if (i === -1) return;
state.selected.splice(i, 1) state.selected.splice(i, 1);
}, },
resetSelected: (state) => { resetSelected: (state) => {
state.selected = [] state.selected = [];
}, },
updateUser: (state, value) => { updateUser: (state, value) => {
if (typeof value !== 'object') return if (typeof value !== "object") return;
for (let field in value) { for (let field in value) {
if (field === 'locale') { if (field === "locale") {
moment.locale(value[field]) moment.locale(value[field]);
i18n.default.locale = value[field] i18n.default.locale = value[field];
} }
state.user[field] = value[field] state.user[field] = value[field];
} }
}, },
updateRequest: (state, value) => { updateRequest: (state, value) => {
state.oldReq = state.req state.oldReq = state.req;
state.req = value state.req = value;
}, },
updateClipboard: (state, value) => { updateClipboard: (state, value) => {
state.clipboard.key = value.key state.clipboard.key = value.key;
state.clipboard.items = value.items state.clipboard.items = value.items;
state.clipboard.path = value.path state.clipboard.path = value.path;
}, },
resetClipboard: (state) => { resetClipboard: (state) => {
state.clipboard.key = '' state.clipboard.key = "";
state.clipboard.items = [] state.clipboard.items = [];
} },
} };
export default mutations export default mutations;

View File

@ -1,26 +1,26 @@
import store from '@/store' import store from "@/store";
import router from '@/router' import router from "@/router";
import { Base64 } from 'js-base64' import { Base64 } from "js-base64";
import { baseURL } from '@/utils/constants' import { baseURL } from "@/utils/constants";
export function parseToken(token) { export function parseToken(token) {
const parts = token.split('.') const parts = token.split(".");
if (parts.length !== 3) { if (parts.length !== 3) {
throw new Error('token malformed') throw new Error("token malformed");
} }
const data = JSON.parse(Base64.decode(parts[1])) const data = JSON.parse(Base64.decode(parts[1]));
localStorage.setItem('jwt', token) localStorage.setItem("jwt", token);
store.commit('setJWT', token) store.commit("setJWT", token);
store.commit('setUser', data.user) store.commit("setUser", data.user);
} }
export async function validateLogin() { export async function validateLogin() {
try { try {
if (localStorage.getItem('jwt')) { if (localStorage.getItem("jwt")) {
await renew(localStorage.getItem('jwt')) await renew(localStorage.getItem("jwt"));
} }
} catch (_) { } catch (_) {
console.warn('Invalid JWT token in storage') // eslint-disable-line console.warn('Invalid JWT token in storage') // eslint-disable-line
@ -28,61 +28,61 @@ export async function validateLogin () {
} }
export async function login(username, password, recaptcha) { export async function login(username, password, recaptcha) {
const data = { username, password, recaptcha } const data = { username, password, recaptcha };
const res = await fetch(`${baseURL}/api/login`, { const res = await fetch(`${baseURL}/api/login`, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify(data) body: JSON.stringify(data),
}) });
const body = await res.text() const body = await res.text();
if (res.status === 200) { if (res.status === 200) {
parseToken(body) parseToken(body);
} else { } else {
throw new Error(body) throw new Error(body);
} }
} }
export async function renew(jwt) { export async function renew(jwt) {
const res = await fetch(`${baseURL}/api/renew`, { const res = await fetch(`${baseURL}/api/renew`, {
method: 'POST', method: "POST",
headers: { headers: {
'X-Auth': jwt, "X-Auth": jwt,
} },
}) });
const body = await res.text() const body = await res.text();
if (res.status === 200) { if (res.status === 200) {
parseToken(body) parseToken(body);
} else { } else {
throw new Error(body) throw new Error(body);
} }
} }
export async function signup(username, password) { export async function signup(username, password) {
const data = { username, password } const data = { username, password };
const res = await fetch(`${baseURL}/api/signup`, { const res = await fetch(`${baseURL}/api/signup`, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify(data) body: JSON.stringify(data),
}) });
if (res.status !== 200) { if (res.status !== 200) {
throw new Error(res.status) throw new Error(res.status);
} }
} }
export function logout() { export function logout() {
store.commit('setJWT', '') store.commit("setJWT", "");
store.commit('setUser', null) store.commit("setUser", null);
localStorage.setItem('jwt', null) localStorage.setItem("jwt", null);
router.push({path: '/login'}) router.push({ path: "/login" });
} }

View File

@ -1,70 +1,70 @@
function loading(button) { function loading(button) {
let el = document.querySelector(`#${button}-button > i`) let el = 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
return return;
} }
if (el.innerHTML == 'autorenew' || el.innerHTML == 'done') { if (el.innerHTML == "autorenew" || el.innerHTML == "done") {
return return;
} }
el.dataset.icon = el.innerHTML el.dataset.icon = el.innerHTML;
el.style.opacity = 0 el.style.opacity = 0;
setTimeout(() => { setTimeout(() => {
el.classList.add('spin') el.classList.add("spin");
el.innerHTML = 'autorenew' el.innerHTML = "autorenew";
el.style.opacity = 1 el.style.opacity = 1;
}, 100) }, 100);
} }
function done(button) { function done(button) {
let el = document.querySelector(`#${button}-button > i`) let el = 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
return return;
} }
el.style.opacity = 0 el.style.opacity = 0;
setTimeout(() => { setTimeout(() => {
el.classList.remove('spin') el.classList.remove("spin");
el.innerHTML = el.dataset.icon el.innerHTML = el.dataset.icon;
el.style.opacity = 1 el.style.opacity = 1;
}, 100) }, 100);
} }
function success(button) { function success(button) {
let el = document.querySelector(`#${button}-button > i`) let el = 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
return return;
} }
el.style.opacity = 0 el.style.opacity = 0;
setTimeout(() => { setTimeout(() => {
el.classList.remove('spin') el.classList.remove("spin");
el.innerHTML = 'done' el.innerHTML = "done";
el.style.opacity = 1 el.style.opacity = 1;
setTimeout(() => { setTimeout(() => {
el.style.opacity = 0 el.style.opacity = 0;
setTimeout(() => { setTimeout(() => {
el.innerHTML = el.dataset.icon el.innerHTML = el.dataset.icon;
el.style.opacity = 1 el.style.opacity = 1;
}, 100) }, 100);
}, 500) }, 500);
}, 100) }, 100);
} }
export default { export default {
loading, loading,
done, done,
success success,
} };

View File

@ -1,19 +1,19 @@
const name = window.FileBrowser.Name || 'File Browser' const name = window.FileBrowser.Name || "File Browser";
const disableExternal = window.FileBrowser.DisableExternal const disableExternal = window.FileBrowser.DisableExternal;
const baseURL = window.FileBrowser.BaseURL const baseURL = window.FileBrowser.BaseURL;
const staticURL = window.FileBrowser.StaticURL const staticURL = window.FileBrowser.StaticURL;
const recaptcha = window.FileBrowser.ReCaptcha const recaptcha = window.FileBrowser.ReCaptcha;
const recaptchaKey = window.FileBrowser.ReCaptchaKey const recaptchaKey = window.FileBrowser.ReCaptchaKey;
const signup = window.FileBrowser.Signup const signup = window.FileBrowser.Signup;
const version = window.FileBrowser.Version const version = window.FileBrowser.Version;
const logoURL = `${staticURL}/img/logo.svg` const logoURL = `${staticURL}/img/logo.svg`;
const noAuth = window.FileBrowser.NoAuth const noAuth = window.FileBrowser.NoAuth;
const authMethod = window.FileBrowser.AuthMethod const authMethod = window.FileBrowser.AuthMethod;
const loginPage = window.FileBrowser.LoginPage const loginPage = window.FileBrowser.LoginPage;
const theme = window.FileBrowser.Theme const theme = window.FileBrowser.Theme;
const enableThumbs = window.FileBrowser.EnableThumbs const enableThumbs = window.FileBrowser.EnableThumbs;
const resizePreview = window.FileBrowser.ResizePreview const resizePreview = window.FileBrowser.ResizePreview;
const enableExec = window.FileBrowser.EnableExec const enableExec = window.FileBrowser.EnableExec;
export { export {
name, name,
@ -30,5 +30,5 @@ export {
theme, theme,
enableThumbs, enableThumbs,
resizePreview, resizePreview,
enableExec enableExec,
} };

View File

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

View File

@ -1,28 +1,28 @@
export default function getRule(rules) { export default function getRule(rules) {
for (let i = 0; i < rules.length; i++) { for (let i = 0; i < rules.length; i++) {
rules[i] = rules[i].toLowerCase() rules[i] = rules[i].toLowerCase();
} }
let result = null let result = null;
let find = Array.prototype.find let 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) => {
let found = false let found = false;
if (cssRule instanceof window.CSSStyleRule) { if (cssRule instanceof window.CSSStyleRule) {
for (let i = 0; i < rules.length; i++) { for (let i = 0; i < rules.length; i++) {
if (cssRule.selectorText.toLowerCase() === rules[i]) { if (cssRule.selectorText.toLowerCase() === rules[i]) {
found = true found = true;
} }
} }
} }
return found return found;
}) });
return result != null return result != null;
}) });
return result return result;
} }

View File

@ -1,124 +1,127 @@
import store from '@/store' import store from "@/store";
import url from '@/utils/url' import url from "@/utils/url";
export function checkConflict(files, items) { export function checkConflict(files, items) {
if (typeof items === 'undefined' || items === null) { if (typeof items === "undefined" || items === null) {
items = [] items = [];
} }
let folder_upload = files[0].fullPath !== undefined let 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] let file = files[i];
let name = file.name let name = file.name;
if (folder_upload) { if (folder_upload) {
let dirs = file.fullPath.split("/") let 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) { let res = items.findIndex(function hasConflict(element) {
return (element.name === this) return element.name === this;
}, name) }, name);
if (res >= 0) { if (res >= 0) {
conflict = true conflict = true;
break break;
} }
} }
return conflict return conflict;
} }
export function scanFiles(dt) { export function scanFiles(dt) {
return new Promise((resolve) => { return new Promise((resolve) => {
let reading = 0 let reading = 0;
const contents = [] const contents = [];
if (dt.items !== undefined) { if (dt.items !== undefined) {
for (let item of dt.items) { for (let item of dt.items) {
if (item.kind === "file" && typeof item.webkitGetAsEntry === "function") { if (
const entry = item.webkitGetAsEntry() item.kind === "file" &&
readEntry(entry) typeof item.webkitGetAsEntry === "function"
) {
const entry = item.webkitGetAsEntry();
readEntry(entry);
} }
} }
} else { } else {
resolve(dt.files) resolve(dt.files);
} }
function readEntry(entry, directory = "") { function readEntry(entry, directory = "") {
if (entry.isFile) { if (entry.isFile) {
reading++ reading++;
entry.file(file => { entry.file((file) => {
reading-- reading--;
file.fullPath = `${directory}${file.name}` file.fullPath = `${directory}${file.name}`;
contents.push(file) contents.push(file);
if (reading === 0) { if (reading === 0) {
resolve(contents) resolve(contents);
} }
}) });
} else if (entry.isDirectory) { } else if (entry.isDirectory) {
const dir = { const dir = {
isDir: true, isDir: true,
size: 0, size: 0,
fullPath: `${directory}${entry.name}` fullPath: `${directory}${entry.name}`,
} };
contents.push(dir) contents.push(dir);
readReaderContent(entry.createReader(), `${directory}${entry.name}`) readReaderContent(entry.createReader(), `${directory}${entry.name}`);
} }
} }
function readReaderContent(reader, directory) { function readReaderContent(reader, directory) {
reading++ reading++;
reader.readEntries(function (entries) { reader.readEntries(function (entries) {
reading-- reading--;
if (entries.length > 0) { if (entries.length > 0) {
for (const entry of entries) { for (const entry of entries) {
readEntry(entry, `${directory}/`) readEntry(entry, `${directory}/`);
} }
readReaderContent(reader, `${directory}/`) readReaderContent(reader, `${directory}/`);
} }
if (reading === 0) { if (reading === 0) {
resolve(contents) resolve(contents);
} }
}) });
} }
}) });
} }
export function handleFiles(files, base, overwrite = false) { export function handleFiles(files, base, overwrite = false) {
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
let id = store.state.upload.id let id = store.state.upload.id;
let path = base let path = base;
let file = files[i] let file = files[i];
if (file.fullPath !== undefined) { if (file.fullPath !== undefined) {
path += url.encodePath(file.fullPath) path += url.encodePath(file.fullPath);
} else { } else {
path += url.encodeRFC5987ValueChars(file.name) path += url.encodeRFC5987ValueChars(file.name);
} }
if (file.isDir) { if (file.isDir) {
path += '/' path += "/";
} }
const item = { const item = {
id, id,
path, path,
file, file,
overwrite overwrite,
} };
store.dispatch('upload/upload', item); store.dispatch("upload/upload", item);
} }
} }

View File

@ -1,31 +1,36 @@
function removeLastDir(url) { function removeLastDir(url) {
var arr = url.split('/') var arr = url.split("/");
if (arr.pop() === '') { if (arr.pop() === "") {
arr.pop() arr.pop();
} }
return arr.join('/') return arr.join("/");
} }
// this code borrow from mozilla // this code borrow from mozilla
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples
function encodeRFC5987ValueChars(str) { function encodeRFC5987ValueChars(str) {
return encodeURIComponent(str). return (
encodeURIComponent(str)
// Note that although RFC3986 reserves "!", RFC5987 does not, // Note that although RFC3986 reserves "!", RFC5987 does not,
// so we do not need to escape it // so we do not need to escape it
replace(/['()]/g, escape). // i.e., %27 %28 %29 .replace(/['()]/g, escape) // i.e., %27 %28 %29
replace(/\*/g, '%2A'). .replace(/\*/g, "%2A")
// The following are not required for percent-encoding per RFC5987, // The following are not required for percent-encoding per RFC5987,
// so we can allow for a little better readability over the wire: |`^ // so we can allow for a little better readability over the wire: |`^
replace(/%(?:7C|60|5E)/g, unescape); .replace(/%(?:7C|60|5E)/g, unescape)
);
} }
function encodePath(str) { function encodePath(str) {
return str.split('/').map(v => encodeURIComponent(v)).join('/') return str
.split("/")
.map((v) => encodeURIComponent(v))
.join("/");
} }
export default { export default {
encodeRFC5987ValueChars: encodeRFC5987ValueChars, encodeRFC5987ValueChars: encodeRFC5987ValueChars,
removeLastDir: removeLastDir, removeLastDir: removeLastDir,
encodePath: encodePath encodePath: encodePath,
} };

View File

@ -1,58 +1,66 @@
import Vue from 'vue' import Vue from "vue";
import Noty from 'noty' import Noty from "noty";
import VueLazyload from 'vue-lazyload' import VueLazyload from "vue-lazyload";
import i18n from '@/i18n' import i18n from "@/i18n";
import { disableExternal } from '@/utils/constants' import { disableExternal } from "@/utils/constants";
Vue.use(VueLazyload) Vue.use(VueLazyload);
Vue.config.productionTip = true Vue.config.productionTip = true;
const notyDefault = { const notyDefault = {
type: 'info', type: "info",
layout: 'bottomRight', layout: "bottomRight",
timeout: 1000, timeout: 1000,
progressBar: true progressBar: true,
} };
Vue.prototype.$noty = (opts) => { Vue.prototype.$noty = (opts) => {
new Noty(Object.assign({}, notyDefault, opts)).show() new Noty(Object.assign({}, notyDefault, opts)).show();
} };
Vue.prototype.$showSuccess = (message) => { Vue.prototype.$showSuccess = (message) => {
new Noty(Object.assign({}, notyDefault, { new Noty(
Object.assign({}, notyDefault, {
text: message, text: message,
type: 'success' type: "success",
})).show() })
} ).show();
};
Vue.prototype.$showError = (error, displayReport = true) => { Vue.prototype.$showError = (error, displayReport = true) => {
let btns = [ let btns = [
Noty.button(i18n.t('buttons.close'), '', function () { Noty.button(i18n.t("buttons.close"), "", function () {
n.close() n.close();
}) }),
] ];
if (!disableExternal && displayReport) { if (!disableExternal && displayReport) {
btns.unshift(Noty.button(i18n.t('buttons.reportIssue'), '', function () { btns.unshift(
window.open('https://github.com/filebrowser/filebrowser/issues/new/choose') Noty.button(i18n.t("buttons.reportIssue"), "", function () {
})) window.open(
} "https://github.com/filebrowser/filebrowser/issues/new/choose"
);
let n = new Noty(Object.assign({}, notyDefault, {
text: error.message || error,
type: 'error',
timeout: null,
buttons: btns
}))
n.show()
}
Vue.directive('focus', {
inserted: function (el) {
el.focus()
}
}) })
);
}
export default Vue let n = new Noty(
Object.assign({}, notyDefault, {
text: error.message || error,
type: "error",
timeout: null,
buttons: btns,
})
);
n.show();
};
Vue.directive("focus", {
inserted: function (el) {
el.focus();
},
});
export default Vue;

View File

@ -10,37 +10,34 @@
</template> </template>
<script> <script>
import HeaderBar from "@/components/header/HeaderBar";
import HeaderBar from '@/components/header/HeaderBar'
const errors = { const errors = {
403: { 403: {
icon: 'error', icon: "error",
message: 'errors.forbidden' message: "errors.forbidden",
}, },
404: { 404: {
icon: 'gps_off', icon: "gps_off",
message: 'errors.notFound' message: "errors.notFound",
}, },
500: { 500: {
icon: 'error_outline', icon: "error_outline",
message: 'errors.internal' message: "errors.internal",
} },
} };
export default { export default {
name: 'errors', name: "errors",
components: { components: {
HeaderBar HeaderBar,
}, },
props: [ props: ["errorCode", "showHeader"],
'errorCode', 'showHeader'
],
data: function () { data: function () {
return { return {
icon: errors[this.errorCode].icon, icon: errors[this.errorCode].icon,
message: this.$t(errors[this.errorCode].message) message: this.$t(errors[this.errorCode].message),
} };
} },
} };
</script> </script>

View File

@ -8,137 +8,137 @@
<component v-else-if="currentView" :is="currentView"></component> <component v-else-if="currentView" :is="currentView"></component>
<div v-else> <div v-else>
<h2 class="message"> <h2 class="message">
<span>{{ $t('files.loading') }}</span> <span>{{ $t("files.loading") }}</span>
</h2> </h2>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { files as api } from '@/api' import { files as api } from "@/api";
import { mapState, mapMutations } from 'vuex' import { mapState, mapMutations } from "vuex";
import HeaderBar from '@/components/header/HeaderBar' import HeaderBar from "@/components/header/HeaderBar";
import Breadcrumbs from '@/components/Breadcrumbs' import Breadcrumbs from "@/components/Breadcrumbs";
import Errors from '@/views/Errors' import Errors from "@/views/Errors";
import Preview from '@/views/files/Preview' import Preview from "@/views/files/Preview";
import Listing from '@/views/files/Listing' import Listing from "@/views/files/Listing";
function clean(path) { function clean(path) {
return path.endsWith('/') ? path.slice(0, -1) : path return path.endsWith("/") ? path.slice(0, -1) : path;
} }
export default { export default {
name: 'files', name: "files",
components: { components: {
HeaderBar, HeaderBar,
Breadcrumbs, Breadcrumbs,
Errors, Errors,
Preview, Preview,
Listing, Listing,
Editor: () => import('@/views/files/Editor'), Editor: () => import("@/views/files/Editor"),
}, },
data: function () { data: function () {
return { return {
error: null, error: null,
width: window.innerWidth width: window.innerWidth,
} };
}, },
computed: { computed: {
...mapState([ ...mapState(["req", "reload", "loading", "show"]),
'req',
'reload',
'loading',
'show'
]),
currentView() { currentView() {
if (this.req.type == undefined) { if (this.req.type == undefined) {
return null return null;
} }
if (this.req.isDir) { if (this.req.isDir) {
return 'listing' return "listing";
} else if(this.req.type === 'text' || this.req.type === 'textImmutable') { } else if (
return 'editor' this.req.type === "text" ||
this.req.type === "textImmutable"
) {
return "editor";
} else { } else {
return 'preview' return "preview";
} }
}, },
errorCode() { errorCode() {
return (this.error.message === '404' || this.error.message === '403') ? parseInt(this.error.message) : 500 return this.error.message === "404" || this.error.message === "403"
} ? parseInt(this.error.message)
: 500;
},
}, },
created() { created() {
this.fetchData() this.fetchData();
}, },
watch: { watch: {
'$route': 'fetchData', $route: "fetchData",
'reload': function (value) { reload: function (value) {
if (value === true) { if (value === true) {
this.fetchData() this.fetchData();
}
} }
}, },
},
mounted() { mounted() {
window.addEventListener('keydown', this.keyEvent) window.addEventListener("keydown", this.keyEvent);
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('keydown', this.keyEvent) window.removeEventListener("keydown", this.keyEvent);
}, },
destroyed() { destroyed() {
if (this.$store.state.showShell) { if (this.$store.state.showShell) {
this.$store.commit('toggleShell') this.$store.commit("toggleShell");
} }
this.$store.commit('updateRequest', {}) this.$store.commit("updateRequest", {});
}, },
methods: { methods: {
...mapMutations([ 'setLoading' ]), ...mapMutations(["setLoading"]),
async fetchData() { async fetchData() {
// Reset view information. // Reset view information.
this.$store.commit('setReload', false) this.$store.commit("setReload", false);
this.$store.commit('resetSelected') this.$store.commit("resetSelected");
this.$store.commit('multiple', false) this.$store.commit("multiple", false);
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
// Set loading to true and reset the error. // Set loading to true and reset the error.
this.setLoading(true) this.setLoading(true);
this.error = null this.error = null;
let url = this.$route.path let url = this.$route.path;
if (url === '') url = '/' if (url === "") url = "/";
if (url[0] !== '/') url = '/' + url if (url[0] !== "/") url = "/" + url;
try { try {
const res = await api.fetch(url) const res = await api.fetch(url);
if (clean(res.path) !== clean(`/${this.$route.params.pathMatch}`)) { if (clean(res.path) !== clean(`/${this.$route.params.pathMatch}`)) {
return return;
} }
this.$store.commit('updateRequest', res) this.$store.commit("updateRequest", res);
document.title = res.name document.title = res.name;
} catch (e) { } catch (e) {
this.error = e this.error = e;
} finally { } finally {
this.setLoading(false) this.setLoading(false);
} }
}, },
keyEvent(event) { keyEvent(event) {
if (this.show !== null) { if (this.show !== null) {
// Esc! // Esc!
if (event.keyCode === 27) { if (event.keyCode === 27) {
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
} }
return return;
} }
// F1! // F1!
if (event.keyCode === 112) { if (event.keyCode === 112) {
event.preventDefault() event.preventDefault();
this.$store.commit('showHover', 'help') this.$store.commit("showHover", "help");
}
}
}
} }
},
},
};
</script> </script>

View File

@ -13,30 +13,31 @@
</template> </template>
<script> <script>
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from "vuex";
import Sidebar from '@/components/Sidebar' import Sidebar from "@/components/Sidebar";
import Prompts from '@/components/prompts/Prompts' import Prompts from "@/components/prompts/Prompts";
import Shell from '@/components/Shell' import Shell from "@/components/Shell";
import { enableExec } from '@/utils/constants' import { enableExec } from "@/utils/constants";
export default { export default {
name: 'layout', name: "layout",
components: { components: {
Sidebar, Sidebar,
Prompts, Prompts,
Shell Shell,
}, },
computed: { computed: {
...mapGetters([ 'isLogged', 'progress' ]), ...mapGetters(["isLogged", "progress"]),
...mapState([ 'user' ]), ...mapState(["user"]),
isExecEnabled: () => enableExec isExecEnabled: () => enableExec,
}, },
watch: { watch: {
'$route': function () { $route: function () {
this.$store.commit('resetSelected') this.$store.commit("resetSelected");
this.$store.commit('multiple', false) this.$store.commit("multiple", false);
if (this.$store.state.show !== 'success') this.$store.commit('closeHovers') if (this.$store.state.show !== "success")
} this.$store.commit("closeHovers");
} },
} },
};
</script> </script>

View File

@ -1,97 +1,127 @@
<template> <template>
<div id="login" :class="{ recaptcha: recaptcha }"> <div id="login" :class="{ recaptcha: recaptcha }">
<form @submit="submit"> <form @submit="submit">
<img :src="logoURL" alt="File Browser"> <img :src="logoURL" alt="File Browser" />
<h1>{{ name }}</h1> <h1>{{ name }}</h1>
<div v-if="error !== ''" class="wrong">{{ error }}</div> <div v-if="error !== ''" class="wrong">{{ error }}</div>
<input class="input input--block" type="text" v-model="username" :placeholder="$t('login.username')"> <input
<input class="input input--block" type="password" v-model="password" :placeholder="$t('login.password')"> class="input input--block"
<input class="input input--block" v-if="createMode" type="password" v-model="passwordConfirm" :placeholder="$t('login.passwordConfirm')" /> type="text"
v-model="username"
:placeholder="$t('login.username')"
/>
<input
class="input input--block"
type="password"
v-model="password"
:placeholder="$t('login.password')"
/>
<input
class="input input--block"
v-if="createMode"
type="password"
v-model="passwordConfirm"
:placeholder="$t('login.passwordConfirm')"
/>
<div v-if="recaptcha" id="recaptcha"></div> <div v-if="recaptcha" id="recaptcha"></div>
<input class="button button--block" type="submit" :value="createMode ? $t('login.signup') : $t('login.submit')"> <input
class="button button--block"
type="submit"
:value="createMode ? $t('login.signup') : $t('login.submit')"
/>
<p @click="toggleMode" v-if="signup">{{ createMode ? $t('login.loginInstead') : $t('login.createAnAccount') }}</p> <p @click="toggleMode" v-if="signup">
{{
createMode ? $t("login.loginInstead") : $t("login.createAnAccount")
}}
</p>
</form> </form>
</div> </div>
</template> </template>
<script> <script>
import * as auth from '@/utils/auth' import * as auth from "@/utils/auth";
import { name, logoURL, recaptcha, recaptchaKey, signup } from '@/utils/constants' import {
name,
logoURL,
recaptcha,
recaptchaKey,
signup,
} from "@/utils/constants";
export default { export default {
name: 'login', name: "login",
computed: { computed: {
signup: () => signup, signup: () => signup,
name: () => name, name: () => name,
logoURL: () => logoURL logoURL: () => logoURL,
}, },
data: function () { data: function () {
return { return {
createMode: false, createMode: false,
error: '', error: "",
username: '', username: "",
password: '', password: "",
recaptcha: recaptcha, recaptcha: recaptcha,
passwordConfirm: '' passwordConfirm: "",
} };
}, },
mounted() { mounted() {
if (!recaptcha) return if (!recaptcha) return;
window.grecaptcha.ready(function () { window.grecaptcha.ready(function () {
window.grecaptcha.render('recaptcha', { window.grecaptcha.render("recaptcha", {
sitekey: recaptchaKey sitekey: recaptchaKey,
}) });
}) });
}, },
methods: { methods: {
toggleMode() { toggleMode() {
this.createMode = !this.createMode this.createMode = !this.createMode;
}, },
async submit(event) { async submit(event) {
event.preventDefault() event.preventDefault();
event.stopPropagation() event.stopPropagation();
let redirect = this.$route.query.redirect let redirect = this.$route.query.redirect;
if (redirect === '' || redirect === undefined || redirect === null) { if (redirect === "" || redirect === undefined || redirect === null) {
redirect = '/files/' redirect = "/files/";
} }
let captcha = '' let captcha = "";
if (recaptcha) { if (recaptcha) {
captcha = window.grecaptcha.getResponse() captcha = window.grecaptcha.getResponse();
if (captcha === '') { if (captcha === "") {
this.error = this.$t('login.wrongCredentials') this.error = this.$t("login.wrongCredentials");
return return;
} }
} }
if (this.createMode) { if (this.createMode) {
if (this.password !== this.passwordConfirm) { if (this.password !== this.passwordConfirm) {
this.error = this.$t('login.passwordsDontMatch') this.error = this.$t("login.passwordsDontMatch");
return return;
} }
} }
try { try {
if (this.createMode) { if (this.createMode) {
await auth.signup(this.username, this.password) await auth.signup(this.username, this.password);
} }
await auth.login(this.username, this.password, captcha) await auth.login(this.username, this.password, captcha);
this.$router.push({ path: redirect }) this.$router.push({ path: redirect });
} catch (e) { } catch (e) {
if (e.message == 409) { if (e.message == 409) {
this.error = this.$t('login.usernameTaken') this.error = this.$t("login.usernameTaken");
} else { } else {
this.error = this.$t('login.wrongCredentials') this.error = this.$t("login.wrongCredentials");
}
}
}
} }
} }
},
},
};
</script> </script>

View File

@ -5,10 +5,35 @@
<div id="nav"> <div id="nav">
<div class="wrapper"> <div class="wrapper">
<ul> <ul>
<router-link to="/settings/profile"><li :class="{ active: $route.path === '/settings/profile' }">{{ $t('settings.profileSettings') }}</li></router-link> <router-link to="/settings/profile"
<router-link to="/settings/shares"><li :class="{ active: $route.path === '/settings/shares' }">{{ $t('settings.shareManagement') }}</li></router-link> ><li :class="{ active: $route.path === '/settings/profile' }">
<router-link to="/settings/global"><li :class="{ active: $route.path === '/settings/global' }" v-if="user.perm.admin">{{ $t('settings.globalSettings') }}</li></router-link> {{ $t("settings.profileSettings") }}
<router-link to="/settings/users"><li :class="{ active: $route.path === '/settings/users' || $route.name === 'User' }" v-if="user.perm.admin">{{ $t('settings.userManagement') }}</li></router-link> </li></router-link
>
<router-link to="/settings/shares"
><li :class="{ active: $route.path === '/settings/shares' }">
{{ $t("settings.shareManagement") }}
</li></router-link
>
<router-link to="/settings/global"
><li
:class="{ active: $route.path === '/settings/global' }"
v-if="user.perm.admin"
>
{{ $t("settings.globalSettings") }}
</li></router-link
>
<router-link to="/settings/users"
><li
:class="{
active:
$route.path === '/settings/users' || $route.name === 'User',
}"
v-if="user.perm.admin"
>
{{ $t("settings.userManagement") }}
</li></router-link
>
</ul> </ul>
</div> </div>
</div> </div>
@ -18,17 +43,17 @@
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
import HeaderBar from '@/components/header/HeaderBar' import HeaderBar from "@/components/header/HeaderBar";
export default { export default {
name: 'settings', name: "settings",
components: { components: {
HeaderBar HeaderBar,
}, },
computed: { computed: {
...mapState([ 'user' ]) ...mapState(["user"]),
} },
} };
</script> </script>

View File

@ -3,8 +3,18 @@
<header-bar showMenu showLogo> <header-bar showMenu showLogo>
<title /> <title />
<action v-if="selectedCount" icon="file_download" :label="$t('buttons.download')" @action="download" :counter="selectedCount" /> <action
<action icon="check_circle" :label="$t('buttons.selectMultiple')" @action="toggleMultipleSelection" /> v-if="selectedCount"
icon="file_download"
:label="$t('buttons.download')"
@action="download"
:counter="selectedCount"
/>
<action
icon="check_circle"
:label="$t('buttons.selectMultiple')"
@action="toggleMultipleSelection"
/>
</header-bar> </header-bar>
<breadcrumbs :base="'/share/' + hash" /> <breadcrumbs :base="'/share/' + hash" />
@ -13,33 +23,43 @@
<div class="share"> <div class="share">
<div class="share__box share__box__info"> <div class="share__box share__box__info">
<div class="share__box__header"> <div class="share__box__header">
{{ req.isDir ? $t('download.downloadFolder') : $t('download.downloadFile') }} {{
req.isDir
? $t("download.downloadFolder")
: $t("download.downloadFile")
}}
</div> </div>
<div class="share__box__element share__box__center share__box__icon"> <div class="share__box__element share__box__center share__box__icon">
<i class="material-icons">{{ icon }}</i> <i class="material-icons">{{ icon }}</i>
</div> </div>
<div class="share__box__element"> <div class="share__box__element">
<strong>{{ $t('prompts.displayName') }}</strong> {{ req.name }} <strong>{{ $t("prompts.displayName") }}</strong> {{ req.name }}
</div> </div>
<div class="share__box__element"> <div class="share__box__element">
<strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime }} <strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
</div> </div>
<div class="share__box__element"> <div class="share__box__element">
<strong>{{ $t('prompts.size') }}:</strong> {{ humanSize }} <strong>{{ $t("prompts.size") }}:</strong> {{ humanSize }}
</div> </div>
<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">{{ $t('buttons.download') }}</a> <a target="_blank" :href="link" class="button button--flat">{{
$t("buttons.download")
}}</a>
</div> </div>
<div class="share__box__element share__box__center"> <div class="share__box__element share__box__center">
<qrcode-vue :value="fullLink" size="200" level="M"></qrcode-vue> <qrcode-vue :value="fullLink" 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"> <div id="listing" class="list">
<item v-for="(item) in req.items.slice(0, this.showLimit)" <item
v-for="item in req.items.slice(0, this.showLimit)"
:key="base64(item.name)" :key="base64(item.name)"
v-bind:index="item.index" v-bind:index="item.index"
v-bind:name="item.name" v-bind:name="item.name"
@ -48,7 +68,8 @@
v-bind:modified="item.modified" v-bind:modified="item.modified"
v-bind:type="item.type" v-bind:type="item.type"
v-bind:size="item.size" v-bind:size="item.size"
readOnly> readOnly
>
</item> </item>
<div v-if="req.items.length > showLimit" class="item"> <div v-if="req.items.length > showLimit" class="item">
<div> <div>
@ -56,18 +77,31 @@
</div> </div>
</div> </div>
<div :class="{ active: $store.state.multiple }" id="multiple-selection"> <div
<p>{{ $t('files.multipleSelectionEnabled') }}</p> :class="{ active: $store.state.multiple }"
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" :title="$t('files.clear')" :aria-label="$t('files.clear')" class="action"> id="multiple-selection"
>
<p>{{ $t("files.multipleSelectionEnabled") }}</p>
<div
@click="$store.commit('multiple', false)"
tabindex="0"
role="button"
: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>
</h2> </h2>
</div> </div>
</div> </div>
@ -75,19 +109,31 @@
<div v-if="error"> <div v-if="error">
<div v-if="error.message === '401'"> <div v-if="error.message === '401'">
<div class="card floating" id="password"> <div class="card floating" id="password">
<div v-if="attemptedPasswordLogin" class="share__wrong__password">{{ $t('login.wrongCredentials') }}</div> <div v-if="attemptedPasswordLogin" class="share__wrong__password">
{{ $t("login.wrongCredentials") }}
</div>
<div class="card-title"> <div class="card-title">
<h2>{{ $t('login.password') }}</h2> <h2>{{ $t("login.password") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<input v-focus type="password" :placeholder="$t('login.password')" v-model="password" @keyup.enter="fetchData"> <input
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" <button
class="button button--flat"
@click="fetchData" @click="fetchData"
:aria-label="$t('buttons.submit')" :aria-label="$t('buttons.submit')"
:title="$t('buttons.submit')">{{ $t('buttons.submit') }}</button> :title="$t('buttons.submit')"
>
{{ $t("buttons.submit") }}
</button>
</div> </div>
</div> </div>
</div> </div>
@ -97,120 +143,122 @@
</template> </template>
<script> <script>
import {mapState, mapMutations, mapGetters} from 'vuex'; import { mapState, mapMutations, mapGetters } from "vuex";
import { pub as api } from '@/api' import { pub as api } from "@/api";
import { baseURL } from '@/utils/constants' import { baseURL } from "@/utils/constants";
import filesize from 'filesize' import filesize from "filesize";
import moment from 'moment' import moment from "moment";
import HeaderBar from '@/components/header/HeaderBar' import HeaderBar from "@/components/header/HeaderBar";
import Action from '@/components/header/Action' import Action from "@/components/header/Action";
import Breadcrumbs from '@/components/Breadcrumbs' import Breadcrumbs from "@/components/Breadcrumbs";
import Errors from '@/views/Errors' import Errors from "@/views/Errors";
import QrcodeVue from 'qrcode.vue' import QrcodeVue from "qrcode.vue";
import Item from "@/components/files/ListingItem" import Item from "@/components/files/ListingItem";
export default { export default {
name: 'share', name: "share",
components: { components: {
HeaderBar, HeaderBar,
Action, Action,
Breadcrumbs, Breadcrumbs,
Item, Item,
QrcodeVue, QrcodeVue,
Errors Errors,
}, },
data: () => ({ data: () => ({
error: null, error: null,
showLimit: 500, showLimit: 500,
password: '', password: "",
attemptedPasswordLogin: false, attemptedPasswordLogin: false,
hash: null, hash: null,
token: null token: null,
}), }),
watch: { watch: {
'$route': 'fetchData' $route: "fetchData",
}, },
created: async function () { created: async function () {
const hash = this.$route.params.pathMatch.split('/')[0] const hash = this.$route.params.pathMatch.split("/")[0];
this.hash = hash this.hash = hash;
await this.fetchData() await this.fetchData();
}, },
mounted() { mounted() {
window.addEventListener('keydown', this.keyEvent) window.addEventListener("keydown", this.keyEvent);
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('keydown', this.keyEvent) window.removeEventListener("keydown", this.keyEvent);
}, },
computed: { computed: {
...mapState(['req', 'loading', 'multiple', 'selected']), ...mapState(["req", "loading", "multiple", "selected"]),
...mapGetters(['selectedCount', 'selectedCount']), ...mapGetters(["selectedCount", "selectedCount"]),
icon: function () { icon: function () {
if (this.req.isDir) return 'folder' if (this.req.isDir) return "folder";
if (this.req.type === 'image') return 'insert_photo' if (this.req.type === "image") return "insert_photo";
if (this.req.type === 'audio') return 'volume_up' if (this.req.type === "audio") return "volume_up";
if (this.req.type === 'video') return 'movie' if (this.req.type === "video") return "movie";
return 'insert_drive_file' return "insert_drive_file";
}, },
link: function () { link: function () {
let queryArg = ''; let queryArg = "";
if (this.token !== ''){ if (this.token !== "") {
queryArg = `?token=${this.token}` queryArg = `?token=${this.token}`;
} }
const path = this.$route.path.split('/').splice(2).join('/') const path = this.$route.path.split("/").splice(2).join("/");
return `${baseURL}/api/public/dl/${path}${queryArg}` return `${baseURL}/api/public/dl/${path}${queryArg}`;
}, },
fullLink: function () { fullLink: function () {
return window.location.origin + this.link return window.location.origin + this.link;
}, },
humanSize: function () { humanSize: function () {
if (this.req.isDir) { if (this.req.isDir) {
return this.req.items.length return this.req.items.length;
} }
return filesize(this.req.size) return filesize(this.req.size);
}, },
humanTime: function () { humanTime: function () {
return moment(this.req.modified).fromNow() return moment(this.req.modified).fromNow();
}, },
errorCode() { errorCode() {
return (this.error.message === '404' || this.error.message === '403') ? parseInt(this.error.message) : 500 return this.error.message === "404" || this.error.message === "403"
} ? parseInt(this.error.message)
: 500;
},
}, },
methods: { methods: {
...mapMutations([ 'resetSelected', 'updateRequest', 'setLoading' ]), ...mapMutations(["resetSelected", "updateRequest", "setLoading"]),
base64: function (name) { base64: function (name) {
return window.btoa(unescape(encodeURIComponent(name))) return window.btoa(unescape(encodeURIComponent(name)));
}, },
fetchData: async function () { fetchData: async function () {
// Reset view information. // Reset view information.
this.$store.commit('setReload', false) this.$store.commit("setReload", false);
this.$store.commit('resetSelected') this.$store.commit("resetSelected");
this.$store.commit('multiple', false) this.$store.commit("multiple", false);
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
// Set loading to true and reset the error. // Set loading to true and reset the error.
this.setLoading(true) this.setLoading(true);
this.error = null this.error = null;
if (this.password !== ''){ if (this.password !== "") {
this.attemptedPasswordLogin = true this.attemptedPasswordLogin = true;
} }
let url = this.$route.path let url = this.$route.path;
if (url === '') url = '/' if (url === "") url = "/";
if (url[0] !== '/') url = '/' + url if (url[0] !== "/") url = "/" + url;
try { try {
let file = await api.fetch(url, this.password) let file = await api.fetch(url, this.password);
this.token = file.token || '' this.token = file.token || "";
this.updateRequest(file) this.updateRequest(file);
this.setLoading(false) this.setLoading(false);
} catch (e) { } catch (e) {
this.error = e this.error = e;
} }
}, },
keyEvent(event) { keyEvent(event) {
@ -219,34 +267,39 @@ export default {
// If we're on a listing, unselect all // If we're on a listing, unselect all
// files and folders. // files and folders.
if (this.selectedCount > 0) { if (this.selectedCount > 0) {
this.resetSelected() this.resetSelected();
} }
} }
}, },
toggleMultipleSelection() { toggleMultipleSelection() {
this.$store.commit('multiple', !this.multiple) this.$store.commit("multiple", !this.multiple);
}, },
download() { download() {
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) { if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
api.download(null, this.hash, this.token, this.req.items[this.selected[0]].path) api.download(
return null,
this.hash,
this.token,
this.req.items[this.selected[0]].path
);
return;
} }
this.$store.commit('showHover', { this.$store.commit("showHover", {
prompt: 'download', prompt: "download",
confirm: (format) => { confirm: (format) => {
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
let files = [] let files = [];
for (let i of this.selected) { for (let i of this.selected) {
files.push(this.req.items[i].path) files.push(this.req.items[i].path);
} }
api.download(format, this.hash, this.token, ...files) api.download(format, this.hash, this.token, ...files);
} },
}) });
} },
} },
} };
</script> </script>

View File

@ -5,7 +5,12 @@
<title>{{ req.name }}</title> <title>{{ req.name }}</title>
<template #actions> <template #actions>
<action id="save-button" icon="save" :label="$t('buttons.save')" @action="save()" /> <action
id="save-button"
icon="save"
:label="$t('buttons.save')"
@action="save()"
/>
</template> </template>
</header-bar> </header-bar>
@ -16,120 +21,120 @@
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
import { files as api } from '@/api' import { files as api } from "@/api";
import { theme } from '@/utils/constants' import { theme } from "@/utils/constants";
import buttons from '@/utils/buttons' import buttons from "@/utils/buttons";
import url from '@/utils/url' import url from "@/utils/url";
import ace from 'ace-builds/src-min-noconflict/ace.js' import ace from "ace-builds/src-min-noconflict/ace.js";
import modelist from 'ace-builds/src-min-noconflict/ext-modelist.js' import modelist from "ace-builds/src-min-noconflict/ext-modelist.js";
import 'ace-builds/webpack-resolver' import "ace-builds/webpack-resolver";
import HeaderBar from '@/components/header/HeaderBar' import HeaderBar from "@/components/header/HeaderBar";
import Action from '@/components/header/Action' import Action from "@/components/header/Action";
import Breadcrumbs from '@/components/Breadcrumbs' import Breadcrumbs from "@/components/Breadcrumbs";
export default { export default {
name: 'editor', name: "editor",
components: { components: {
HeaderBar, HeaderBar,
Action, Action,
Breadcrumbs Breadcrumbs,
}, },
data: function () { data: function () {
return {} return {};
}, },
computed: { computed: {
...mapState(['req', 'user']), ...mapState(["req", "user"]),
breadcrumbs() { breadcrumbs() {
let parts = this.$route.path.split('/') let parts = this.$route.path.split("/");
if (parts[0] === '') { if (parts[0] === "") {
parts.shift() parts.shift();
} }
if (parts[parts.length - 1] === '') { if (parts[parts.length - 1] === "") {
parts.pop() parts.pop();
} }
let breadcrumbs = [] let breadcrumbs = [];
for (let i = 0; i < parts.length; i++) { for (let i = 0; i < parts.length; i++) {
breadcrumbs.push({ name: decodeURIComponent(parts[i]) }) breadcrumbs.push({ name: decodeURIComponent(parts[i]) });
} }
breadcrumbs.shift() breadcrumbs.shift();
if (breadcrumbs.length > 3) { if (breadcrumbs.length > 3) {
while (breadcrumbs.length !== 4) { while (breadcrumbs.length !== 4) {
breadcrumbs.shift() breadcrumbs.shift();
} }
breadcrumbs[0].name = '...' breadcrumbs[0].name = "...";
} }
return breadcrumbs return breadcrumbs;
} },
}, },
created() { created() {
window.addEventListener('keydown', this.keyEvent) window.addEventListener("keydown", this.keyEvent);
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('keydown', this.keyEvent) window.removeEventListener("keydown", this.keyEvent);
this.editor.destroy(); this.editor.destroy();
}, },
mounted: function () { mounted: function () {
const fileContent = this.req.content || ''; const fileContent = this.req.content || "";
this.editor = ace.edit('editor', { this.editor = ace.edit("editor", {
value: fileContent, value: fileContent,
showPrintMargin: false, showPrintMargin: false,
readOnly: this.req.type === 'textImmutable', readOnly: this.req.type === "textImmutable",
theme: 'ace/theme/chrome', theme: "ace/theme/chrome",
mode: modelist.getModeForPath(this.req.name).mode, mode: modelist.getModeForPath(this.req.name).mode,
wrap: true wrap: true,
}) });
if (theme == 'dark') { if (theme == "dark") {
this.editor.setTheme("ace/theme/twilight"); this.editor.setTheme("ace/theme/twilight");
} }
}, },
methods: { methods: {
back() { back() {
let uri = url.removeLastDir(this.$route.path) + '/' let uri = url.removeLastDir(this.$route.path) + "/";
this.$router.push({ path: uri }) this.$router.push({ path: uri });
}, },
keyEvent(event) { keyEvent(event) {
if (!event.ctrlKey && !event.metaKey) { if (!event.ctrlKey && !event.metaKey) {
return return;
} }
if (String.fromCharCode(event.which).toLowerCase() !== 's') { if (String.fromCharCode(event.which).toLowerCase() !== "s") {
return return;
} }
event.preventDefault() event.preventDefault();
this.save() this.save();
}, },
async save() { async save() {
const button = 'save' const button = "save";
buttons.loading('save') buttons.loading("save");
try { try {
await api.put(this.$route.path, this.editor.getValue()) await api.put(this.$route.path, this.editor.getValue());
buttons.success(button) buttons.success(button);
} catch (e) { } catch (e) {
buttons.done(button) buttons.done(button);
this.$showError(e) this.$showError(e);
} }
}, },
close() { close() {
this.$store.commit('updateRequest', {}) this.$store.commit("updateRequest", {});
let uri = url.removeLastDir(this.$route.path) + '/' let uri = url.removeLastDir(this.$route.path) + "/";
this.$router.push({ path: uri }) this.$router.push({ path: uri });
} },
} },
} };
</script> </script>

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,45 @@
<template> <template>
<div id="previewer" @mousemove="toggleNavigation" @touchstart="toggleNavigation"> <div
id="previewer"
@mousemove="toggleNavigation"
@touchstart="toggleNavigation"
>
<header-bar> <header-bar>
<action icon="close" :label="$t('buttons.close')" @action="close()" /> <action icon="close" :label="$t('buttons.close')" @action="close()" />
<title>{{ name }}</title> <title>{{ name }}</title>
<action :disabled="loading" v-if="isResizeEnabled && req.type === 'image'" :icon="fullSize ? 'photo_size_select_large' : 'hd'" @action="toggleSize" /> <action
:disabled="loading"
v-if="isResizeEnabled && req.type === 'image'"
:icon="fullSize ? 'photo_size_select_large' : 'hd'"
@action="toggleSize"
/>
<template #actions> <template #actions>
<action :disabled="loading" icon="mode_edit" :label="$t('buttons.rename')" show="rename" /> <action
<action :disabled="loading" icon="delete" :label="$t('buttons.delete')" @action="deleteFile" id="delete-button" /> :disabled="loading"
<action :disabled="loading" icon="file_download" :label="$t('buttons.download')" @action="download" /> icon="mode_edit"
<action :disabled="loading" icon="info" :label="$t('buttons.info')" show="info" /> :label="$t('buttons.rename')"
show="rename"
/>
<action
:disabled="loading"
icon="delete"
:label="$t('buttons.delete')"
@action="deleteFile"
id="delete-button"
/>
<action
:disabled="loading"
icon="file_download"
:label="$t('buttons.download')"
@action="download"
/>
<action
:disabled="loading"
icon="info"
:label="$t('buttons.info')"
show="info"
/>
</template> </template>
</header-bar> </header-bar>
@ -31,221 +61,249 @@
v-for="(sub, index) in subtitles" v-for="(sub, index) in subtitles"
:key="index" :key="index"
:src="sub" :src="sub"
:label="'Subtitle ' + index" :default="index === 0"> :label="'Subtitle ' + index"
Sorry, your browser doesn't support embedded videos, :default="index === 0"
but don't worry, you can <a :href="downloadUrl">download it</a> />
Sorry, your browser doesn't support embedded videos, but don't worry,
you can <a :href="downloadUrl">download it</a>
and watch it with your favorite video player! and watch it with your favorite video player!
</video> </video>
<object v-else-if="req.extension.toLowerCase() == '.pdf'" class="pdf" :data="raw"></object> <object
v-else-if="req.extension.toLowerCase() == '.pdf'"
class="pdf"
:data="raw"
></object>
<a v-else-if="req.type == 'blob'" :href="downloadUrl"> <a v-else-if="req.type == 'blob'" :href="downloadUrl">
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2> <h2 class="message">
{{ $t("buttons.download") }}
<i class="material-icons">file_download</i>
</h2>
</a> </a>
</div> </div>
</template> </template>
<button @click="prev" @mouseover="hoverNav = true" @mouseleave="hoverNav = false" :class="{ hidden: !hasPrevious || !showNav }" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')"> <button
@click="prev"
@mouseover="hoverNav = true"
@mouseleave="hoverNav = false"
:class="{ hidden: !hasPrevious || !showNav }"
:aria-label="$t('buttons.previous')"
:title="$t('buttons.previous')"
>
<i class="material-icons">chevron_left</i> <i class="material-icons">chevron_left</i>
</button> </button>
<button @click="next" @mouseover="hoverNav = true" @mouseleave="hoverNav = false" :class="{ hidden: !hasNext || !showNav }" :aria-label="$t('buttons.next')" :title="$t('buttons.next')"> <button
@click="next"
@mouseover="hoverNav = true"
@mouseleave="hoverNav = false"
:class="{ hidden: !hasNext || !showNav }"
:aria-label="$t('buttons.next')"
:title="$t('buttons.next')"
>
<i class="material-icons">chevron_right</i> <i class="material-icons">chevron_right</i>
</button> </button>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
import { files as api } from '@/api' import { files as api } from "@/api";
import { baseURL, resizePreview } from '@/utils/constants' import { baseURL, resizePreview } from "@/utils/constants";
import url from '@/utils/url' import url from "@/utils/url";
import throttle from 'lodash.throttle' import throttle from "lodash.throttle";
import HeaderBar from '@/components/header/HeaderBar' import HeaderBar from "@/components/header/HeaderBar";
import Action from '@/components/header/Action' import Action from "@/components/header/Action";
import ExtendedImage from '@/components/files/ExtendedImage' import ExtendedImage from "@/components/files/ExtendedImage";
const mediaTypes = [ const mediaTypes = ["image", "video", "audio", "blob"];
"image",
"video",
"audio",
"blob"
]
export default { export default {
name: 'preview', name: "preview",
components: { components: {
HeaderBar, HeaderBar,
Action, Action,
ExtendedImage ExtendedImage,
}, },
data: function () { data: function () {
return { return {
previousLink: '', previousLink: "",
nextLink: '', nextLink: "",
listing: null, listing: null,
name: '', name: "",
subtitles: [], subtitles: [],
fullSize: false, fullSize: false,
showNav: true, showNav: true,
navTimeout: null, navTimeout: null,
hoverNav: false hoverNav: false,
} };
}, },
computed: { computed: {
...mapState(['req', 'user', 'oldReq', 'jwt', 'loading', 'show']), ...mapState(["req", "user", "oldReq", "jwt", "loading", "show"]),
hasPrevious() { hasPrevious() {
return (this.previousLink !== '') return this.previousLink !== "";
}, },
hasNext() { hasNext() {
return (this.nextLink !== '') return this.nextLink !== "";
}, },
downloadUrl() { downloadUrl() {
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}` return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${
this.jwt
}`;
}, },
previewUrl() { previewUrl() {
// reload the image when the file is replaced // reload the image when the file is replaced
const key = Date.parse(this.req.modified) const key = Date.parse(this.req.modified);
if (this.req.type === 'image' && !this.fullSize) { if (this.req.type === "image" && !this.fullSize) {
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}&k=${key}` return `${baseURL}/api/preview/big${url.encodePath(
this.req.path
)}?auth=${this.jwt}&k=${key}`;
} }
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}&k=${key}` return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${
this.jwt
}&k=${key}`;
}, },
raw() { raw() {
return `${this.previewUrl}&inline=true` return `${this.previewUrl}&inline=true`;
}, },
showMore() { showMore() {
return this.$store.state.show === 'more' return this.$store.state.show === "more";
}, },
isResizeEnabled() { isResizeEnabled() {
return resizePreview return resizePreview;
} },
}, },
watch: { watch: {
$route: function () { $route: function () {
this.updatePreview() this.updatePreview();
this.toggleNavigation() this.toggleNavigation();
} },
}, },
async mounted() { async mounted() {
window.addEventListener('keydown', this.key) window.addEventListener("keydown", this.key);
this.listing = this.oldReq.items this.listing = this.oldReq.items;
this.updatePreview() this.updatePreview();
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('keydown', this.key) window.removeEventListener("keydown", this.key);
}, },
methods: { methods: {
deleteFile() { deleteFile() {
this.$store.commit('showHover', { this.$store.commit("showHover", {
prompt: 'delete', prompt: "delete",
confirm: () => { confirm: () => {
this.listing = this.listing.filter(item => item.name !== this.name) this.listing = this.listing.filter((item) => item.name !== this.name);
if (this.hasNext) { if (this.hasNext) {
this.next() this.next();
} else if (!this.hasPrevious && !this.hasNext) { } else if (!this.hasPrevious && !this.hasNext) {
this.close() this.close();
} else { } else {
this.prev() this.prev();
} }
} },
}) });
}, },
prev() { prev() {
this.hoverNav = false this.hoverNav = false;
this.$router.push({ path: this.previousLink }) this.$router.push({ path: this.previousLink });
}, },
next() { next() {
this.hoverNav = false this.hoverNav = false;
this.$router.push({ path: this.nextLink }) this.$router.push({ path: this.nextLink });
}, },
key(event) { key(event) {
if (this.show !== null) { if (this.show !== null) {
return return;
} }
if (event.which === 13 || event.which === 39) { // right arrow if (event.which === 13 || event.which === 39) {
if (this.hasNext) this.next() // right arrow
} else if (event.which === 37) { // left arrow if (this.hasNext) this.next();
if (this.hasPrevious) this.prev() } else if (event.which === 37) {
} else if (event.which === 27) { // esc // left arrow
this.close() if (this.hasPrevious) this.prev();
} else if (event.which === 27) {
// esc
this.close();
} }
}, },
async updatePreview() { async updatePreview() {
if (this.req.subtitles) { if (this.req.subtitles) {
this.subtitles = this.req.subtitles.map(sub => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`) this.subtitles = this.req.subtitles.map(
(sub) => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`
);
} }
let dirs = this.$route.fullPath.split("/") let dirs = this.$route.fullPath.split("/");
this.name = decodeURIComponent(dirs[dirs.length - 1]) this.name = decodeURIComponent(dirs[dirs.length - 1]);
if (!this.listing) { if (!this.listing) {
try { try {
const path = url.removeLastDir(this.$route.path) const path = url.removeLastDir(this.$route.path);
const res = await api.fetch(path) const res = await api.fetch(path);
this.listing = res.items this.listing = res.items;
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
} }
this.previousLink = '' this.previousLink = "";
this.nextLink = '' this.nextLink = "";
for (let i = 0; i < this.listing.length; i++) { for (let i = 0; i < this.listing.length; i++) {
if (this.listing[i].name !== this.name) { if (this.listing[i].name !== this.name) {
continue continue;
} }
for (let j = i - 1; j >= 0; j--) { for (let j = i - 1; j >= 0; j--) {
if (mediaTypes.includes(this.listing[j].type)) { if (mediaTypes.includes(this.listing[j].type)) {
this.previousLink = this.listing[j].url this.previousLink = this.listing[j].url;
break break;
} }
} }
for (let j = i + 1; j < this.listing.length; j++) { for (let j = i + 1; j < this.listing.length; j++) {
if (mediaTypes.includes(this.listing[j].type)) { if (mediaTypes.includes(this.listing[j].type)) {
this.nextLink = this.listing[j].url this.nextLink = this.listing[j].url;
break break;
} }
} }
return return;
} }
}, },
openMore() { openMore() {
this.$store.commit('showHover', 'more') this.$store.commit("showHover", "more");
}, },
resetPrompts() { resetPrompts() {
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
}, },
toggleSize() { toggleSize() {
this.fullSize = !this.fullSize this.fullSize = !this.fullSize;
}, },
toggleNavigation: throttle(function () { toggleNavigation: throttle(function () {
this.showNav = true this.showNav = true;
if (this.navTimeout) { if (this.navTimeout) {
clearTimeout(this.navTimeout) clearTimeout(this.navTimeout);
} }
this.navTimeout = setTimeout(() => { this.navTimeout = setTimeout(() => {
this.showNav = false || this.hoverNav this.showNav = false || this.hoverNav;
this.navTimeout = null this.navTimeout = null;
}, 1500); }, 1500);
}, 500), }, 500),
close() { close() {
this.$store.commit('updateRequest', {}) this.$store.commit("updateRequest", {});
let uri = url.removeLastDir(this.$route.path) + '/' let uri = url.removeLastDir(this.$route.path) + "/";
this.$router.push({ path: uri }) this.$router.push({ path: uri });
}, },
download() { download() {
api.download(null, this.$route.path) api.download(null, this.$route.path);
} },
} },
} };
</script> </script>

View File

@ -3,54 +3,93 @@
<div class="column"> <div class="column">
<form class="card" @submit.prevent="save"> <form class="card" @submit.prevent="save">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('settings.globalSettings') }}</h2> <h2>{{ $t("settings.globalSettings") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p><input type="checkbox" v-model="settings.signup"> {{ $t('settings.allowSignup') }}</p> <p>
<input type="checkbox" v-model="settings.signup" />
{{ $t("settings.allowSignup") }}
</p>
<p><input type="checkbox" v-model="settings.createUserDir"> {{ $t('settings.createUserDir') }}</p> <p>
<input type="checkbox" v-model="settings.createUserDir" />
{{ $t("settings.createUserDir") }}
</p>
<h3>{{ $t('settings.rules') }}</h3> <h3>{{ $t("settings.rules") }}</h3>
<p class="small">{{ $t('settings.globalRules') }}</p> <p class="small">{{ $t("settings.globalRules") }}</p>
<rules :rules.sync="settings.rules" /> <rules :rules.sync="settings.rules" />
<div v-if="isExecEnabled"> <div v-if="isExecEnabled">
<h3>{{ $t('settings.executeOnShell') }}</h3> <h3>{{ $t("settings.executeOnShell") }}</h3>
<p class="small">{{ $t('settings.executeOnShellDescription') }}</p> <p class="small">{{ $t("settings.executeOnShellDescription") }}</p>
<input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." v-model="settings.shell" /> <input
class="input input--block"
type="text"
placeholder="bash -c, cmd /c, ..."
v-model="settings.shell"
/>
</div> </div>
<h3>{{ $t('settings.branding') }}</h3> <h3>{{ $t("settings.branding") }}</h3>
<i18n path="settings.brandingHelp" tag="p" class="small"> <i18n path="settings.brandingHelp" tag="p" class="small">
<a class="link" target="_blank" href="https://filebrowser.org/configuration/custom-branding">{{ $t('settings.documentation') }}</a> <a
class="link"
target="_blank"
href="https://filebrowser.org/configuration/custom-branding"
>{{ $t("settings.documentation") }}</a
>
</i18n> </i18n>
<p> <p>
<input type="checkbox" v-model="settings.branding.disableExternal" id="branding-links" /> <input
{{ $t('settings.disableExternalLinks') }} type="checkbox"
v-model="settings.branding.disableExternal"
id="branding-links"
/>
{{ $t("settings.disableExternalLinks") }}
</p> </p>
<p> <p>
<label for="theme">{{ $t('settings.themes.title') }}</label> <label for="theme">{{ $t("settings.themes.title") }}</label>
<themes class="input input--block" :theme.sync="settings.branding.theme" id="theme"></themes> <themes
class="input input--block"
:theme.sync="settings.branding.theme"
id="theme"
></themes>
</p> </p>
<p> <p>
<label for="branding-name">{{ $t('settings.instanceName') }}</label> <label for="branding-name">{{ $t("settings.instanceName") }}</label>
<input class="input input--block" type="text" v-model="settings.branding.name" id="branding-name" /> <input
class="input input--block"
type="text"
v-model="settings.branding.name"
id="branding-name"
/>
</p> </p>
<p> <p>
<label for="branding-files">{{ $t('settings.brandingDirectoryPath') }}</label> <label for="branding-files">{{
<input class="input input--block" type="text" v-model="settings.branding.files" id="branding-files" /> $t("settings.brandingDirectoryPath")
}}</label>
<input
class="input input--block"
type="text"
v-model="settings.branding.files"
id="branding-files"
/>
</p> </p>
</div> </div>
<div class="card-action"> <div class="card-action">
<input class="button button--flat" type="submit" :value="$t('buttons.update')"> <input
class="button button--flat"
type="submit"
:value="$t('buttons.update')"
/>
</div> </div>
</form> </form>
</div> </div>
@ -58,17 +97,25 @@
<div class="column"> <div class="column">
<form class="card" @submit.prevent="save"> <form class="card" @submit.prevent="save">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('settings.userDefaults') }}</h2> <h2>{{ $t("settings.userDefaults") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p class="small">{{ $t('settings.defaultUserDescription') }}</p> <p class="small">{{ $t("settings.defaultUserDescription") }}</p>
<user-form :isNew="false" :isDefault="true" :user.sync="settings.defaults" /> <user-form
:isNew="false"
:isDefault="true"
:user.sync="settings.defaults"
/>
</div> </div>
<div class="card-action"> <div class="card-action">
<input class="button button--flat" type="submit" :value="$t('buttons.update')"> <input
class="button button--flat"
type="submit"
:value="$t('buttons.update')"
/>
</div> </div>
</form> </form>
</div> </div>
@ -76,30 +123,46 @@
<div class="column"> <div class="column">
<form v-if="isExecEnabled" class="card" @submit.prevent="save"> <form v-if="isExecEnabled" class="card" @submit.prevent="save">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('settings.commandRunner') }}</h2> <h2>{{ $t("settings.commandRunner") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<i18n path="settings.commandRunnerHelp" tag="p" class="small"> <i18n path="settings.commandRunnerHelp" tag="p" class="small">
<code>FILE</code> <code>FILE</code>
<code>SCOPE</code> <code>SCOPE</code>
<a class="link" target="_blank" href="https://filebrowser.org/configuration/command-runner">{{ $t('settings.documentation') }}</a> <a
class="link"
target="_blank"
href="https://filebrowser.org/configuration/command-runner"
>{{ $t("settings.documentation") }}</a
>
</i18n> </i18n>
<div v-for="command in settings.commands" :key="command.name" class="collapsible"> <div
<input :id="command.name" type="checkbox"> v-for="command in settings.commands"
:key="command.name"
class="collapsible"
>
<input :id="command.name" type="checkbox" />
<label :for="command.name"> <label :for="command.name">
<p>{{ capitalize(command.name) }}</p> <p>{{ capitalize(command.name) }}</p>
<i class="material-icons">arrow_drop_down</i> <i class="material-icons">arrow_drop_down</i>
</label> </label>
<div class="collapse"> <div class="collapse">
<textarea class="input input--block input--textarea" v-model.trim="command.value"></textarea> <textarea
class="input input--block input--textarea"
v-model.trim="command.value"
></textarea>
</div> </div>
</div> </div>
</div> </div>
<div class="card-action"> <div class="card-action">
<input class="button button--flat" type="submit" :value="$t('buttons.update')"> <input
class="button button--flat"
type="submit"
:value="$t('buttons.update')"
/>
</div> </div>
</form> </form>
</div> </div>
@ -107,80 +170,84 @@
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
import { settings as api } from '@/api' import { settings as api } from "@/api";
import UserForm from '@/components/settings/UserForm' import UserForm from "@/components/settings/UserForm";
import Rules from '@/components/settings/Rules' import Rules from "@/components/settings/Rules";
import Themes from '@/components/settings/Themes' import Themes from "@/components/settings/Themes";
import { enableExec } from '@/utils/constants' import { enableExec } from "@/utils/constants";
export default { export default {
name: 'settings', name: "settings",
components: { components: {
Themes, Themes,
UserForm, UserForm,
Rules Rules,
}, },
data: function () { data: function () {
return { return {
originalSettings: null, originalSettings: null,
settings: null settings: null,
} };
}, },
computed: { computed: {
...mapState([ 'user' ]), ...mapState(["user"]),
isExecEnabled: () => enableExec isExecEnabled: () => enableExec,
}, },
async created() { async created() {
try { try {
const original = await api.get() const original = await api.get();
let settings = { ...original, commands: [] } let settings = { ...original, commands: [] };
for (const key in original.commands) { for (const key in original.commands) {
settings.commands.push({ settings.commands.push({
name: key, name: key,
value: original.commands[key].join('\n') value: original.commands[key].join("\n"),
}) });
} }
settings.shell = settings.shell.join(' ') settings.shell = settings.shell.join(" ");
this.originalSettings = original this.originalSettings = original;
this.settings = settings this.settings = settings;
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
}, },
methods: { methods: {
capitalize (name, where = '_') { capitalize(name, where = "_") {
if (where === 'caps') where = /(?=[A-Z])/ if (where === "caps") where = /(?=[A-Z])/;
let splitted = name.split(where) let splitted = name.split(where);
name = '' name = "";
for (let i = 0; i < splitted.length; i++) { for (let i = 0; i < splitted.length; i++) {
name += splitted[i].charAt(0).toUpperCase() + splitted[i].slice(1) + ' ' name +=
splitted[i].charAt(0).toUpperCase() + splitted[i].slice(1) + " ";
} }
return name.slice(0, -1) return name.slice(0, -1);
}, },
async save() { async save() {
let settings = { let settings = {
...this.settings, ...this.settings,
shell: this.settings.shell.trim().split(' ').filter(s => s !== ''), shell: this.settings.shell
commands: {} .trim()
} .split(" ")
.filter((s) => s !== ""),
commands: {},
};
for (const { name, value } of this.settings.commands) { for (const { name, value } of this.settings.commands) {
settings.commands[name] = value.split('\n').filter(cmd => cmd !== '') settings.commands[name] = value.split("\n").filter((cmd) => cmd !== "");
} }
try { try {
await api.update(settings) await api.update(settings);
this.$showSuccess(this.$t('settings.settingsUpdated')) this.$showSuccess(this.$t("settings.settingsUpdated"));
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
}
}
}
} }
},
},
};
</script> </script>

View File

@ -3,18 +3,31 @@
<div class="column"> <div class="column">
<form class="card" @submit="updateSettings"> <form class="card" @submit="updateSettings">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('settings.profileSettings') }}</h2> <h2>{{ $t("settings.profileSettings") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<p><input type="checkbox" v-model="hideDotfiles"> {{ $t('settings.hideDotfiles') }}</p> <p>
<p><input type="checkbox" v-model="singleClick"> {{ $t('settings.singleClick') }}</p> <input type="checkbox" v-model="hideDotfiles" />
<h3>{{ $t('settings.language') }}</h3> {{ $t("settings.hideDotfiles") }}
<languages class="input input--block" :locale.sync="locale"></languages> </p>
<p>
<input type="checkbox" v-model="singleClick" />
{{ $t("settings.singleClick") }}
</p>
<h3>{{ $t("settings.language") }}</h3>
<languages
class="input input--block"
:locale.sync="locale"
></languages>
</div> </div>
<div class="card-action"> <div class="card-action">
<input class="button button--flat" type="submit" :value="$t('buttons.update')"> <input
class="button button--flat"
type="submit"
:value="$t('buttons.update')"
/>
</div> </div>
</form> </form>
</div> </div>
@ -22,16 +35,32 @@
<div class="column"> <div class="column">
<form class="card" v-if="!user.lockPassword" @submit="updatePassword"> <form class="card" v-if="!user.lockPassword" @submit="updatePassword">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('settings.changePassword') }}</h2> <h2>{{ $t("settings.changePassword") }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
<input :class="passwordClass" type="password" :placeholder="$t('settings.newPassword')" v-model="password" name="password"> <input
<input :class="passwordClass" type="password" :placeholder="$t('settings.newPasswordConfirm')" v-model="passwordConf" name="password"> :class="passwordClass"
type="password"
:placeholder="$t('settings.newPassword')"
v-model="password"
name="password"
/>
<input
:class="passwordClass"
type="password"
:placeholder="$t('settings.newPasswordConfirm')"
v-model="passwordConf"
name="password"
/>
</div> </div>
<div class="card-action"> <div class="card-action">
<input class="button button--flat" type="submit" :value="$t('buttons.update')"> <input
class="button button--flat"
type="submit"
:value="$t('buttons.update')"
/>
</div> </div>
</form> </form>
</div> </div>
@ -39,75 +68,80 @@
</template> </template>
<script> <script>
import { mapState, mapMutations } from 'vuex' import { mapState, mapMutations } from "vuex";
import { users as api } from '@/api' import { users as api } from "@/api";
import Languages from '@/components/settings/Languages' import Languages from "@/components/settings/Languages";
export default { export default {
name: 'settings', name: "settings",
components: { components: {
Languages Languages,
}, },
data: function () { data: function () {
return { return {
password: '', password: "",
passwordConf: '', passwordConf: "",
hideDotfiles: false, hideDotfiles: false,
singleClick: false, singleClick: false,
locale: '' locale: "",
} };
}, },
computed: { computed: {
...mapState([ 'user' ]), ...mapState(["user"]),
passwordClass() { passwordClass() {
const baseClass = 'input input--block' const baseClass = "input input--block";
if (this.password === '' && this.passwordConf === '') { if (this.password === "" && this.passwordConf === "") {
return baseClass return baseClass;
} }
if (this.password === this.passwordConf) { if (this.password === this.passwordConf) {
return `${baseClass} input--green` return `${baseClass} input--green`;
} }
return `${baseClass} input--red` return `${baseClass} input--red`;
} },
}, },
created() { created() {
this.locale = this.user.locale this.locale = this.user.locale;
this.hideDotfiles = this.user.hideDotfiles this.hideDotfiles = this.user.hideDotfiles;
this.singleClick = this.user.singleClick this.singleClick = this.user.singleClick;
}, },
methods: { methods: {
...mapMutations([ 'updateUser' ]), ...mapMutations(["updateUser"]),
async updatePassword(event) { async updatePassword(event) {
event.preventDefault() event.preventDefault();
if (this.password !== this.passwordConf || this.password === '') { if (this.password !== this.passwordConf || this.password === "") {
return return;
} }
try { try {
const data = { id: this.user.id, password: this.password } const data = { id: this.user.id, password: this.password };
await api.update(data, ['password']) await api.update(data, ["password"]);
this.updateUser(data) this.updateUser(data);
this.$showSuccess(this.$t('settings.passwordUpdated')) this.$showSuccess(this.$t("settings.passwordUpdated"));
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
}, },
async updateSettings(event) { async updateSettings(event) {
event.preventDefault() event.preventDefault();
try { try {
const data = { id: this.user.id, locale: this.locale, hideDotfiles: this.hideDotfiles, singleClick: this.singleClick } const data = {
await api.update(data, ['locale', 'hideDotfiles', 'singleClick']) id: this.user.id,
this.updateUser(data) locale: this.locale,
this.$showSuccess(this.$t('settings.settingsUpdated')) hideDotfiles: this.hideDotfiles,
singleClick: this.singleClick,
};
await api.update(data, ["locale", "hideDotfiles", "singleClick"]);
this.updateUser(data);
this.$showSuccess(this.$t("settings.settingsUpdated"));
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
}
}
}
} }
},
},
};
</script> </script>

View File

@ -3,37 +3,51 @@
<div class="column"> <div class="column">
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('settings.shareManagement') }}</h2> <h2>{{ $t("settings.shareManagement") }}</h2>
</div> </div>
<div class="card-content full"> <div class="card-content full">
<table> <table>
<tr> <tr>
<th>{{ $t('settings.path') }}</th> <th>{{ $t("settings.path") }}</th>
<th>{{ $t('settings.shareDuration') }}</th> <th>{{ $t("settings.shareDuration") }}</th>
<th v-if="user.perm.admin">{{ $t('settings.username') }}</th> <th v-if="user.perm.admin">{{ $t("settings.username") }}</th>
<th></th> <th></th>
<th></th> <th></th>
</tr> </tr>
<tr v-for="link in links" :key="link.hash"> <tr v-for="link in links" :key="link.hash">
<td><a :href="buildLink(link.hash)" target="_blank">{{ link.path }}</a></td>
<td> <td>
<template v-if="link.expire !== 0">{{ humanTime(link.expire) }}</template> <a :href="buildLink(link.hash)" target="_blank">{{
<template v-else>{{ $t('permanent') }}</template> link.path
}}</a>
</td>
<td>
<template v-if="link.expire !== 0">{{
humanTime(link.expire)
}}</template>
<template v-else>{{ $t("permanent") }}</template>
</td> </td>
<td v-if="user.perm.admin">{{ link.username }}</td> <td v-if="user.perm.admin">{{ link.username }}</td>
<td class="small"> <td class="small">
<button class="action" <button
class="action"
@click="deleteLink($event, link)" @click="deleteLink($event, link)"
:aria-label="$t('buttons.delete')" :aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button> :title="$t('buttons.delete')"
>
<i class="material-icons">delete</i>
</button>
</td> </td>
<td class="small"> <td class="small">
<button class="action copy-clipboard" <button
class="action copy-clipboard"
:data-clipboard-text="buildLink(link.hash)" :data-clipboard-text="buildLink(link.hash)"
:aria-label="$t('buttons.copyToClipboard')" :aria-label="$t('buttons.copyToClipboard')"
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button> :title="$t('buttons.copyToClipboard')"
>
<i class="material-icons">content_paste</i>
</button>
</td> </td>
</tr> </tr>
</table> </table>
@ -44,63 +58,67 @@
</template> </template>
<script> <script>
import { share as api, users } from '@/api' import { share as api, users } from "@/api";
import moment from 'moment' import moment from "moment";
import {baseURL} from "@/utils/constants" import { baseURL } from "@/utils/constants";
import Clipboard from 'clipboard' import Clipboard from "clipboard";
import { mapState } from "vuex"; import { mapState } from "vuex";
export default { export default {
name: 'shares', name: "shares",
computed: mapState([ 'user' ]), computed: mapState(["user"]),
data: function () { data: function () {
return { return {
links: [], links: [],
clip: null clip: null,
} };
}, },
async created() { async created() {
try { try {
let links = await api.list() let links = await api.list();
if (this.user.perm.admin) { if (this.user.perm.admin) {
let userMap = new Map() let userMap = new Map();
for (let user of await users.getAll()) userMap.set(user.id, user.username) for (let user of await users.getAll())
for (let link of links) link.username = userMap.has(link.userID) ? userMap.get(link.userID) : '' userMap.set(user.id, user.username);
for (let link of links)
link.username = userMap.has(link.userID)
? userMap.get(link.userID)
: "";
} }
this.links = links this.links = links;
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
}, },
mounted() { mounted() {
this.clip = new Clipboard('.copy-clipboard') this.clip = new Clipboard(".copy-clipboard");
this.clip.on('success', () => { this.clip.on("success", () => {
this.$showSuccess(this.$t('success.linkCopied')) this.$showSuccess(this.$t("success.linkCopied"));
}) });
}, },
beforeDestroy() { beforeDestroy() {
this.clip.destroy() this.clip.destroy();
}, },
methods: { methods: {
deleteLink: async function (event, link) { deleteLink: async function (event, link) {
event.preventDefault() event.preventDefault();
this.$store.commit('showHover', { this.$store.commit("showHover", {
prompt: 'share-delete', prompt: "share-delete",
confirm: () => { confirm: () => {
this.$store.commit('closeHovers') this.$store.commit("closeHovers");
api.remove(link.hash) api.remove(link.hash);
this.links = this.links.filter(item => item.hash !== link.hash) this.links = this.links.filter((item) => item.hash !== link.hash);
} },
}) });
}, },
humanTime(time) { humanTime(time) {
return moment(time * 1000).fromNow() return moment(time * 1000).fromNow();
}, },
buildLink(hash) { buildLink(hash) {
return `${window.location.origin}${baseURL}/share/${hash}` return `${window.location.origin}${baseURL}/share/${hash}`;
} },
} },
} };
</script> </script>

View File

@ -3,8 +3,8 @@
<div class="column"> <div class="column">
<form v-if="loaded" @submit="save" class="card"> <form v-if="loaded" @submit="save" class="card">
<div class="card-title"> <div class="card-title">
<h2 v-if="user.id === 0">{{ $t('settings.newUser') }}</h2> <h2 v-if="user.id === 0">{{ $t("settings.newUser") }}</h2>
<h2 v-else>{{ $t('settings.user') }} {{ user.username }}</h2> <h2 v-else>{{ $t("settings.user") }} {{ user.username }}</h2>
</div> </div>
<div class="card-content"> <div class="card-content">
@ -18,11 +18,15 @@
type="button" type="button"
class="button button--flat button--red" class="button button--flat button--red"
:aria-label="$t('buttons.delete')" :aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button> :title="$t('buttons.delete')"
>
{{ $t("buttons.delete") }}
</button>
<input <input
class="button button--flat" class="button button--flat"
type="submit" type="submit"
:value="$t('buttons.save')"> :value="$t('buttons.save')"
/>
</div> </div>
</form> </form>
</div> </div>
@ -33,16 +37,17 @@
</div> </div>
<div class="card-action"> <div class="card-action">
<button class="button button--flat button--grey" <button
class="button button--flat button--grey"
@click="closeHovers" @click="closeHovers"
v-focus v-focus
:aria-label="$t('buttons.cancel')" :aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"> :title="$t('buttons.cancel')"
{{ $t('buttons.cancel') }} >
{{ $t("buttons.cancel") }}
</button> </button>
<button class="button button--flat" <button class="button button--flat" @click="deleteUser">
@click="deleteUser"> {{ $t("buttons.delete") }}
{{ $t('buttons.delete') }}
</button> </button>
</div> </div>
</div> </div>
@ -50,101 +55,103 @@
</template> </template>
<script> <script>
import { mapMutations } from 'vuex' import { mapMutations } from "vuex";
import { users as api, settings } from '@/api' import { users as api, settings } from "@/api";
import UserForm from '@/components/settings/UserForm' import UserForm from "@/components/settings/UserForm";
import deepClone from 'lodash.clonedeep' import deepClone from "lodash.clonedeep";
export default { export default {
name: 'user', name: "user",
components: { components: {
UserForm UserForm,
}, },
data: () => { data: () => {
return { return {
originalUser: null, originalUser: null,
user: {}, user: {},
loaded: false loaded: false,
} };
}, },
created() { created() {
this.fetchData() this.fetchData();
}, },
computed: { computed: {
isNew() { isNew() {
return this.$route.path === '/settings/users/new' return this.$route.path === "/settings/users/new";
} },
}, },
watch: { watch: {
'$route': 'fetchData', $route: "fetchData",
'user.perm.admin': function () { "user.perm.admin": function () {
if (!this.user.perm.admin) return if (!this.user.perm.admin) return;
this.user.lockPassword = false this.user.lockPassword = false;
} },
}, },
methods: { methods: {
...mapMutations([ 'closeHovers', 'showHover', 'setUser' ]), ...mapMutations(["closeHovers", "showHover", "setUser"]),
async fetchData() { async fetchData() {
try { try {
if (this.isNew) { if (this.isNew) {
let { defaults } = await settings.get() let { defaults } = await settings.get();
this.user = { this.user = {
...defaults, ...defaults,
username: '', username: "",
passsword: '', passsword: "",
rules: [], rules: [],
lockPassword: false, lockPassword: false,
id: 0 id: 0,
} };
} else { } else {
const id = this.$route.params.pathMatch const id = this.$route.params.pathMatch;
this.user = { ...await api.get(id) } this.user = { ...(await api.get(id)) };
} }
this.loaded = true this.loaded = true;
} catch (e) { } catch (e) {
this.$router.push({ path: '/settings/users/new' }) this.$router.push({ path: "/settings/users/new" });
} }
}, },
deletePrompt() { deletePrompt() {
this.showHover('deleteUser') this.showHover("deleteUser");
}, },
async deleteUser(event) { async deleteUser(event) {
event.preventDefault() event.preventDefault();
try { try {
await api.remove(this.user.id) await api.remove(this.user.id);
this.$router.push({ path: '/settings/users' }) this.$router.push({ path: "/settings/users" });
this.$showSuccess(this.$t('settings.userDeleted')) this.$showSuccess(this.$t("settings.userDeleted"));
} catch (e) { } catch (e) {
(e.message === "403") ? this.$showError(this.$t("errors.forbidden"), false) : this.$showError(e) e.message === "403"
? this.$showError(this.$t("errors.forbidden"), false)
: this.$showError(e);
} }
}, },
async save(event) { async save(event) {
event.preventDefault() event.preventDefault();
let user = { let user = {
...this.originalUser, ...this.originalUser,
...this.user ...this.user,
} };
try { try {
if (this.isNew) { if (this.isNew) {
const loc = await api.create(user) const loc = await api.create(user);
this.$router.push({ path: loc }) this.$router.push({ path: loc });
this.$showSuccess(this.$t('settings.userCreated')) this.$showSuccess(this.$t("settings.userCreated"));
} else { } else {
await api.update(user) await api.update(user);
if (user.id === this.$store.state.user.id) { if (user.id === this.$store.state.user.id) {
this.setUser({ ...deepClone(user) }) this.setUser({ ...deepClone(user) });
} }
this.$showSuccess(this.$t('settings.userUpdated')) this.$showSuccess(this.$t("settings.userUpdated"));
} }
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
}
}
}
} }
},
},
};
</script> </script>

View File

@ -3,25 +3,34 @@
<div class="column"> <div class="column">
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('settings.users') }}</h2> <h2>{{ $t("settings.users") }}</h2>
<router-link to="/settings/users/new"><button class="button">{{ $t('buttons.new') }}</button></router-link> <router-link to="/settings/users/new"
><button class="button">
{{ $t("buttons.new") }}
</button></router-link
>
</div> </div>
<div class="card-content full"> <div class="card-content full">
<table> <table>
<tr> <tr>
<th>{{ $t('settings.username') }}</th> <th>{{ $t("settings.username") }}</th>
<th>{{ $t('settings.admin') }}</th> <th>{{ $t("settings.admin") }}</th>
<th>{{ $t('settings.scope') }}</th> <th>{{ $t("settings.scope") }}</th>
<th></th> <th></th>
</tr> </tr>
<tr v-for="user in users" :key="user.id"> <tr v-for="user in users" :key="user.id">
<td>{{ user.username }}</td> <td>{{ user.username }}</td>
<td><i v-if="user.perm.admin" class="material-icons">done</i><i v-else class="material-icons">close</i></td> <td>
<i v-if="user.perm.admin" class="material-icons">done</i
><i v-else class="material-icons">close</i>
</td>
<td>{{ user.scope }}</td> <td>{{ user.scope }}</td>
<td class="small"> <td class="small">
<router-link :to="'/settings/users/' + user.id"><i class="material-icons">mode_edit</i></router-link> <router-link :to="'/settings/users/' + user.id"
><i class="material-icons">mode_edit</i></router-link
>
</td> </td>
</tr> </tr>
</table> </table>
@ -32,21 +41,21 @@
</template> </template>
<script> <script>
import { users as api } from '@/api' import { users as api } from "@/api";
export default { export default {
name: 'users', name: "users",
data: function () { data: function () {
return { return {
users: [] users: [],
} };
}, },
async created() { async created() {
try { try {
this.users = await api.getAll() this.users = await api.getAll();
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
}
}
} }
},
};
</script> </script>

View File

@ -1,5 +1,5 @@
module.exports = { module.exports = {
runtimeCompiler: true, runtimeCompiler: true,
publicPath: '[{[ .StaticURL ]}]', publicPath: "[{[ .StaticURL ]}]",
parallel: 2, parallel: 2,
} };