Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4cb813ddf | ||
|
|
4d0a68e787 | ||
|
|
a744bd224f | ||
|
|
da1fe7c9d7 | ||
|
|
7fabadc871 | ||
|
|
c3079d30e2 | ||
|
|
6a31af6c0a | ||
|
|
21d361ad30 | ||
|
|
d574fb6d1a | ||
|
|
bb4bb508a9 | ||
|
|
edd808f124 | ||
|
|
cdcd9a313a | ||
|
|
d0c3aeace1 | ||
|
|
9484454584 | ||
|
|
bd3c1941ff | ||
|
|
01f7842a18 | ||
|
|
38f7788255 | ||
|
|
c1fb4004f7 | ||
|
|
584b706b1e | ||
|
|
ecdd684bf1 | ||
|
|
36af01daa6 | ||
|
|
d0c3b8033d | ||
|
|
aa00c1c89c | ||
|
|
a404fb043d | ||
|
|
95fec7f694 | ||
|
|
5994224468 | ||
|
|
374bbd3ec1 | ||
|
|
2c97573301 | ||
|
|
70eba7ecc9 | ||
|
|
7a4d0c0c08 | ||
|
|
8838a09cf5 |
@@ -1,4 +1,5 @@
|
||||
*
|
||||
!docker/*
|
||||
!healthcheck.sh
|
||||
!docker_config.json
|
||||
!filebrowser
|
||||
16
.github/workflows/main.yaml
vendored
16
.github/workflows/main.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: '18'
|
||||
- run: make lint-frontend
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.20.6
|
||||
go-version: 1.21.0
|
||||
- run: make lint-backend
|
||||
lint-commits:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: '18'
|
||||
- run: make lint-commits
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: '18'
|
||||
- run: make test-frontend
|
||||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.20.6
|
||||
go-version: 1.21.0
|
||||
- run: make test-backend
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -76,10 +76,10 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.20.6
|
||||
go-version: 1.21.0
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: '18'
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
@@ -95,6 +95,6 @@ jobs:
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -29,3 +29,6 @@ yarn-error.log*
|
||||
*.sw*
|
||||
bin/
|
||||
build/
|
||||
|
||||
/frontend/dist/*
|
||||
!/frontend/dist/.gitkeep
|
||||
|
||||
@@ -3,33 +3,33 @@ project_name: filebrowser
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
build:
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X github.com/filebrowser/filebrowser/v2/version.Version={{ .Version }} -X github.com/filebrowser/filebrowser/v2/version.CommitSHA={{ .ShortCommit }}
|
||||
main: main.go
|
||||
binary: filebrowser
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm
|
||||
- arm64
|
||||
- riscv64
|
||||
goarm:
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X github.com/filebrowser/filebrowser/v2/version.Version={{ .Version }} -X github.com/filebrowser/filebrowser/v2/version.CommitSHA={{ .ShortCommit }}
|
||||
main: main.go
|
||||
binary: filebrowser
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm
|
||||
- arm64
|
||||
- riscv64
|
||||
goarm:
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
|
||||
archives:
|
||||
-
|
||||
@@ -186,7 +186,7 @@ docker_manifests:
|
||||
- "filebrowser/filebrowser:v{{ .Major }}-arm64-s6"
|
||||
brews:
|
||||
- name: filebrowser
|
||||
tap:
|
||||
repository:
|
||||
owner: filebrowser
|
||||
name: homebrew-tap
|
||||
folder: Formula
|
||||
|
||||
59
CHANGELOG.md
59
CHANGELOG.md
@@ -2,6 +2,65 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [2.26.0](https://github.com/filebrowser/filebrowser/compare/v2.25.0...v2.26.0) (2023-11-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add modern greek translation ([#2778](https://github.com/filebrowser/filebrowser/issues/2778)) ([c3079d3](https://github.com/filebrowser/filebrowser/commit/c3079d30e22385d7e677f172324cd9cbab6487ce))
|
||||
* make user session timeout configurable ([#2753](https://github.com/filebrowser/filebrowser/issues/2753)) ([7fabadc](https://github.com/filebrowser/filebrowser/commit/7fabadc871ea91ea22fe9454e2ca4b33e5c211be))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid the front-end calling api/renew loop ([#2792](https://github.com/filebrowser/filebrowser/issues/2792)) ([edd808f](https://github.com/filebrowser/filebrowser/commit/edd808f124f4ada99bcbe4bca98ddbe20e5a424c))
|
||||
* disable static resource files listing ([da1fe7c](https://github.com/filebrowser/filebrowser/commit/da1fe7c9d76a9c6a25bfa19ebd6cf8023eff5d62))
|
||||
* display file size as base 2 (KiB instead of KB) ([#2779](https://github.com/filebrowser/filebrowser/issues/2779)) ([cdcd9a3](https://github.com/filebrowser/filebrowser/commit/cdcd9a313aa50c2e6806a182b6838462d42dcafe))
|
||||
* goreleaser yaml ([4d0a68e](https://github.com/filebrowser/filebrowser/commit/4d0a68e7875274f4c939f2bfa15739a9b0ecf70a))
|
||||
* revert fetchURL changes in auth (Fixes [#2729](https://github.com/filebrowser/filebrowser/issues/2729)) ([#2739](https://github.com/filebrowser/filebrowser/issues/2739)) ([bd3c194](https://github.com/filebrowser/filebrowser/commit/bd3c1941ff8289a5dae877e08f7e25fa9b2a92c5))
|
||||
* solve docker build failed issue ([#2797](https://github.com/filebrowser/filebrowser/issues/2797)) ([6a31af6](https://github.com/filebrowser/filebrowser/commit/6a31af6c0a144128af865d802c8039fa5250e946))
|
||||
|
||||
|
||||
### Build
|
||||
|
||||
* **deps-dev:** bump postcss from 8.4.27 to 8.4.31 in /frontend ([#2749](https://github.com/filebrowser/filebrowser/issues/2749)) ([21d361a](https://github.com/filebrowser/filebrowser/commit/21d361ad308d109d2a6b323597019aaa09ce1781))
|
||||
* **deps:** bump @babel/traverse in /frontend ([#2775](https://github.com/filebrowser/filebrowser/issues/2775)) ([bb4bb50](https://github.com/filebrowser/filebrowser/commit/bb4bb508a9d71516e8fa80b3a6285fe002a059d2))
|
||||
* **deps:** bump golang.org/x/image from 0.5.0 to 0.10.0 ([#2800](https://github.com/filebrowser/filebrowser/issues/2800)) ([a744bd2](https://github.com/filebrowser/filebrowser/commit/a744bd224f0ff1efc53ab94481fa76ef68788df1))
|
||||
* **deps:** bump golang.org/x/net from 0.11.0 to 0.17.0 ([#2758](https://github.com/filebrowser/filebrowser/issues/2758)) ([d574fb6](https://github.com/filebrowser/filebrowser/commit/d574fb6d1af41ec31778b0f402674e5111a7875d))
|
||||
* fix deprecated goreleaser config options ([38f7788](https://github.com/filebrowser/filebrowser/commit/38f77882559133b9ff330cfb955a9d4ea4728cf8))
|
||||
|
||||
## [2.25.0](https://github.com/filebrowser/filebrowser/compare/v2.24.2...v2.25.0) (2023-09-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add new folder button to move/create dialogs ([#2667](https://github.com/filebrowser/filebrowser/issues/2667)) ([5994224](https://github.com/filebrowser/filebrowser/commit/599422446849fa37d5ab448bbf464afb7304b99d))
|
||||
* added shell resizing ([#2648](https://github.com/filebrowser/filebrowser/issues/2648)) ([584b706](https://github.com/filebrowser/filebrowser/commit/584b706b1e310297acc2580c60442ff5c11ae432))
|
||||
* implement abort upload functionality ([#2673](https://github.com/filebrowser/filebrowser/issues/2673)) ([a404fb0](https://github.com/filebrowser/filebrowser/commit/a404fb043da2573bf04385863b2d34b1f918b8e1))
|
||||
* implement upload speed calculation and ETA estimation ([#2677](https://github.com/filebrowser/filebrowser/issues/2677)) ([ecdd684](https://github.com/filebrowser/filebrowser/commit/ecdd684bf1d537a4591caa38348102b61dd51e5d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* refactor path resolution logic for project root ([#2674](https://github.com/filebrowser/filebrowser/issues/2674)) ([95fec7f](https://github.com/filebrowser/filebrowser/commit/95fec7f69430c108e5cf95c428db9d671cd97a94))
|
||||
* tus upload with cloudflare proxy ([36af01d](https://github.com/filebrowser/filebrowser/commit/36af01daa6e04005ce3d18985eebaeef06f7393d)), closes [#2593](https://github.com/filebrowser/filebrowser/issues/2593)
|
||||
|
||||
|
||||
### Refactorings
|
||||
|
||||
* migrate frontend tooling to vite 4 ([#2645](https://github.com/filebrowser/filebrowser/issues/2645)) ([8838a09](https://github.com/filebrowser/filebrowser/commit/8838a09cf5104deac22b6143050588040c6825e6))
|
||||
|
||||
|
||||
### Build
|
||||
|
||||
* bump go version to 1.21.0 ([#2672](https://github.com/filebrowser/filebrowser/issues/2672)) ([2c97573](https://github.com/filebrowser/filebrowser/commit/2c97573301a1b13179678fb7f9bd8316539ecdff))
|
||||
* bump node version to 18 ([#2671](https://github.com/filebrowser/filebrowser/issues/2671)) ([70eba7e](https://github.com/filebrowser/filebrowser/commit/70eba7ecc9d19545c0899ae40eb3897a7c48562f))
|
||||
|
||||
|
||||
### Performance improvements
|
||||
|
||||
* **backend:** optimize subtitles detection performance ([#2637](https://github.com/filebrowser/filebrowser/issues/2637)) ([374bbd3](https://github.com/filebrowser/filebrowser/commit/374bbd3ec199fddbe491ab2b74e520a10a73e54b))
|
||||
|
||||
### [2.24.2](https://github.com/filebrowser/filebrowser/compare/v2.24.1...v2.24.2) (2023-08-08)
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
|
||||
filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. It can be used as a standalone app.
|
||||
|
||||
## Demo
|
||||
|
||||
url: https://demo.filebrowser.org/
|
||||
|
||||
credentials: `demo`/`demo`
|
||||
|
||||
## Features
|
||||
|
||||
Please refer to our docs at [https://filebrowser.org/features](https://filebrowser.org/features)
|
||||
|
||||
@@ -29,20 +29,21 @@ const PermDir = 0755
|
||||
// FileInfo describes a file.
|
||||
type FileInfo struct {
|
||||
*Listing
|
||||
Fs afero.Fs `json:"-"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Extension string `json:"extension"`
|
||||
ModTime time.Time `json:"modified"`
|
||||
Mode os.FileMode `json:"mode"`
|
||||
IsDir bool `json:"isDir"`
|
||||
IsSymlink bool `json:"isSymlink"`
|
||||
Type string `json:"type"`
|
||||
Subtitles []string `json:"subtitles,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Checksums map[string]string `json:"checksums,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Fs afero.Fs `json:"-"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Extension string `json:"extension"`
|
||||
ModTime time.Time `json:"modified"`
|
||||
Mode os.FileMode `json:"mode"`
|
||||
IsDir bool `json:"isDir"`
|
||||
IsSymlink bool `json:"isSymlink"`
|
||||
Type string `json:"type"`
|
||||
Subtitles []string `json:"subtitles,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Checksums map[string]string `json:"checksums,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
currentDir []os.FileInfo `json:"-"`
|
||||
}
|
||||
|
||||
// FileOptions are the options when getting a file info.
|
||||
@@ -294,13 +295,21 @@ func (i *FileInfo) detectSubtitles() {
|
||||
// detect multiple languages. Base*.vtt
|
||||
// TODO: give subtitles descriptive names (lang) and track attributes
|
||||
parentDir := strings.TrimRight(i.Path, i.Name)
|
||||
dir, err := afero.ReadDir(i.Fs, parentDir)
|
||||
if err == nil {
|
||||
base := strings.TrimSuffix(i.Name, ext)
|
||||
for _, f := range dir {
|
||||
if !f.IsDir() && strings.HasPrefix(f.Name(), base) && strings.HasSuffix(f.Name(), ".vtt") {
|
||||
i.Subtitles = append(i.Subtitles, path.Join(parentDir, f.Name()))
|
||||
}
|
||||
var dir []os.FileInfo
|
||||
if len(i.currentDir) > 0 {
|
||||
dir = i.currentDir
|
||||
} else {
|
||||
var err error
|
||||
dir, err = afero.ReadDir(i.Fs, parentDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
base := strings.TrimSuffix(i.Name, ext)
|
||||
for _, f := range dir {
|
||||
if !f.IsDir() && strings.HasPrefix(f.Name(), base) && strings.HasSuffix(f.Name(), ".vtt") {
|
||||
i.Subtitles = append(i.Subtitles, path.Join(parentDir, f.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,15 +349,16 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
||||
}
|
||||
|
||||
file := &FileInfo{
|
||||
Fs: i.Fs,
|
||||
Name: name,
|
||||
Size: f.Size(),
|
||||
ModTime: f.ModTime(),
|
||||
Mode: f.Mode(),
|
||||
IsDir: f.IsDir(),
|
||||
IsSymlink: isSymlink,
|
||||
Extension: filepath.Ext(name),
|
||||
Path: fPath,
|
||||
Fs: i.Fs,
|
||||
Name: name,
|
||||
Size: f.Size(),
|
||||
ModTime: f.ModTime(),
|
||||
Mode: f.Mode(),
|
||||
IsDir: f.IsDir(),
|
||||
IsSymlink: isSymlink,
|
||||
Extension: filepath.Ext(name),
|
||||
Path: fPath,
|
||||
currentDir: dir,
|
||||
}
|
||||
|
||||
if file.IsDir {
|
||||
|
||||
20
frontend/.eslintrc.json
Normal file
20
frontend/.eslintrc.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended",
|
||||
"@vue/eslint-config-prettier"
|
||||
],
|
||||
"rules": {
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/no-reserved-component-names": "warn",
|
||||
"vue/no-mutating-props": "warn"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
}
|
||||
}
|
||||
2
frontend/.prettierignore
Normal file
2
frontend/.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
# Ignore artifacts:
|
||||
dist
|
||||
3
frontend/.prettierrc.json
Normal file
3
frontend/.prettierrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/app"],
|
||||
};
|
||||
4
frontend/dist/.gitignore
vendored
4
frontend/dist/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
0
frontend/dist/.gitkeep
vendored
Normal file
0
frontend/dist/.gitkeep
vendored
Normal file
192
frontend/index.html
Normal file
192
frontend/index.html
Normal file
@@ -0,0 +1,192 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, user-scalable=no"
|
||||
/>
|
||||
|
||||
<title>File Browser</title>
|
||||
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/img/icons/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/img/icons/favicon-16x16.png"
|
||||
/>
|
||||
|
||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||
<link
|
||||
rel="manifest"
|
||||
id="manifestPlaceholder"
|
||||
crossorigin="use-credentials"
|
||||
/>
|
||||
<meta name="theme-color" content="#2979ff" />
|
||||
|
||||
<!-- Add to home screen for Safari on iOS/iPadOS -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<meta name="apple-mobile-web-app-title" content="assets" />
|
||||
<link rel="apple-touch-icon" href="/img/icons/apple-touch-icon.png" />
|
||||
|
||||
<!-- Add to home screen for Windows -->
|
||||
<meta
|
||||
name="msapplication-TileImage"
|
||||
content="/img/icons/mstile-144x144.png"
|
||||
/>
|
||||
<meta name="msapplication-TileColor" content="#2979ff" />
|
||||
|
||||
<!-- Inject Some Variables and generate the manifest json -->
|
||||
<script>
|
||||
// We can assign JSON directly
|
||||
window.FileBrowser = {
|
||||
AuthMethod: "json",
|
||||
BaseURL: "",
|
||||
CSS: false,
|
||||
Color: "",
|
||||
DisableExternal: false,
|
||||
DisableUsedPercentage: false,
|
||||
EnableExec: true,
|
||||
EnableThumbs: true,
|
||||
LoginPage: true,
|
||||
Name: "",
|
||||
NoAuth: false,
|
||||
ReCaptcha: false,
|
||||
ResizePreview: true,
|
||||
Signup: false,
|
||||
StaticURL: "",
|
||||
Theme: "",
|
||||
TusSettings: { chunkSize: 10485760, retryCount: 5 },
|
||||
Version: "(untracked)",
|
||||
};
|
||||
// Global function to prepend static url
|
||||
window.__prependStaticUrl = (url) => {
|
||||
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, "")}`;
|
||||
};
|
||||
var dynamicManifest = {
|
||||
name: window.FileBrowser.Name || "File Browser",
|
||||
short_name: window.FileBrowser.Name || "File Browser",
|
||||
icons: [
|
||||
{
|
||||
src: window.__prependStaticUrl(
|
||||
"/img/icons/android-chrome-192x192.png"
|
||||
),
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: window.__prependStaticUrl(
|
||||
"/img/icons/android-chrome-512x512.png"
|
||||
),
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
start_url: window.location.origin + window.FileBrowser.BaseURL,
|
||||
display: "standalone",
|
||||
background_color: "#ffffff",
|
||||
theme_color: window.FileBrowser.Color || "#455a64",
|
||||
};
|
||||
|
||||
const stringManifest = JSON.stringify(dynamicManifest);
|
||||
const blob = new Blob([stringManifest], { type: "application/json" });
|
||||
const manifestURL = URL.createObjectURL(blob);
|
||||
document
|
||||
.querySelector("#manifestPlaceholder")
|
||||
.setAttribute("href", manifestURL);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
z-index: 9999;
|
||||
transition: 0.1s ease opacity;
|
||||
-webkit-transition: 0.1s ease opacity;
|
||||
}
|
||||
|
||||
#loading.done {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#loading .spinner {
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#loading .spinner > div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #333;
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
#loading .spinner .bounce1 {
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
#loading .spinner .bounce2 {
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bouncedelay {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-bouncedelay {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<div id="loading">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
<div class="bounce3"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
10
frontend/jsconfig.json
Normal file
10
frontend/jsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
29113
frontend/package-lock.json
generated
29113
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,71 +2,59 @@
|
||||
"name": "filebrowser-frontend",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean",
|
||||
"lint": "npx vue-cli-service lint --no-fix --max-warnings=0",
|
||||
"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"
|
||||
"dev": "vite dev",
|
||||
"serve": "vite serve",
|
||||
"build": "vite build",
|
||||
"watch": "vite build --watch",
|
||||
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
|
||||
"lint": "eslint --ext .vue,.js src/",
|
||||
"lint:fix": "eslint --ext .vue,.js --fix src/",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"ace-builds": "^1.4.7",
|
||||
"clipboard": "^2.0.4",
|
||||
"core-js": "^3.9.1",
|
||||
"css-vars-ponyfill": "^2.4.3",
|
||||
"js-base64": "^2.5.1",
|
||||
"ace-builds": "^1.23.4",
|
||||
"clipboard": "^2.0.11",
|
||||
"core-js": "^3.32.0",
|
||||
"css-vars-ponyfill": "^2.4.8",
|
||||
"filesize": "^10.0.8",
|
||||
"js-base64": "^3.7.5",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"material-icons": "^1.10.5",
|
||||
"material-icons": "^1.13.9",
|
||||
"moment": "^2.29.4",
|
||||
"normalize.css": "^8.0.1",
|
||||
"noty": "^3.2.0-beta",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
"tus-js-client": "^3.1.0",
|
||||
"tus-js-client": "^3.1.1",
|
||||
"utif": "^3.1.0",
|
||||
"vue": "^2.6.10",
|
||||
"vue": "^2.7.14",
|
||||
"vue-async-computed": "^3.9.0",
|
||||
"vue-i18n": "^8.15.3",
|
||||
"vue-lazyload": "^1.3.3",
|
||||
"vue-router": "^3.1.3",
|
||||
"vue-i18n": "^8.28.2",
|
||||
"vue-lazyload": "^1.3.5",
|
||||
"vue-router": "^3.6.5",
|
||||
"vue-simple-progress": "^1.1.1",
|
||||
"vuex": "^3.1.2",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
"whatwg-fetch": "^3.6.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.1.2",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "^4.1.2",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"compression-webpack-plugin": "^6.0.3",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"prettier": "^2.2.1",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended",
|
||||
"@vue/prettier"
|
||||
],
|
||||
"rules": {},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
"@vitejs/plugin-legacy": "^4.1.1",
|
||||
"@vitejs/plugin-vue2": "^2.2.0",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.16.1",
|
||||
"jsdom": "^22.1.0",
|
||||
"postcss": "^8.4.31",
|
||||
"prettier": "^3.0.1",
|
||||
"terser": "^5.19.2",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-compression2": "^0.10.3",
|
||||
"vite-plugin-rewrite-all": "^1.0.1"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
||||
5
frontend/postcss.config.cjs
Normal file
5
frontend/postcss.config.cjs
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
0
frontend/public/.gitkeep
Normal file
0
frontend/public/.gitkeep
Normal file
@@ -1,144 +1,193 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, user-scalable=no"
|
||||
/>
|
||||
|
||||
[{[ if .ReCaptcha -]}]
|
||||
[{[ if .ReCaptcha -]}]
|
||||
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
|
||||
[{[ end ]}]
|
||||
[{[ end ]}]
|
||||
|
||||
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
|
||||
<title>
|
||||
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]
|
||||
</title>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png"
|
||||
/>
|
||||
|
||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
|
||||
<meta name="theme-color" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
|
||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||
<link
|
||||
rel="manifest"
|
||||
id="manifestPlaceholder"
|
||||
crossorigin="use-credentials"
|
||||
/>
|
||||
<meta
|
||||
name="theme-color"
|
||||
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
|
||||
/>
|
||||
|
||||
<!-- Add to home screen for Safari on iOS/iPadOS -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="assets">
|
||||
<link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png">
|
||||
<!-- Add to home screen for Safari on iOS/iPadOS -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<meta name="apple-mobile-web-app-title" content="assets" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
|
||||
/>
|
||||
|
||||
<!-- Add to home screen for Windows -->
|
||||
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png">
|
||||
<meta name="msapplication-TileColor" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
|
||||
<!-- Add to home screen for Windows -->
|
||||
<meta
|
||||
name="msapplication-TileImage"
|
||||
content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png"
|
||||
/>
|
||||
<meta
|
||||
name="msapplication-TileColor"
|
||||
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
|
||||
/>
|
||||
|
||||
<!-- Inject Some Variables and generate the manifest json -->
|
||||
<script>
|
||||
window.FileBrowser = JSON.parse('[{[ .Json ]}]');
|
||||
<!-- Inject Some Variables and generate the manifest json -->
|
||||
<script>
|
||||
// We can assign JSON directly
|
||||
window.FileBrowser = [{[ .Json ]}];
|
||||
// Global function to prepend static url
|
||||
window.__prependStaticUrl = (url) => {
|
||||
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, "")}`;
|
||||
};
|
||||
var dynamicManifest = {
|
||||
name: window.FileBrowser.Name || "File Browser",
|
||||
short_name: window.FileBrowser.Name || "File Browser",
|
||||
icons: [
|
||||
{
|
||||
src: window.__prependStaticUrl("/img/icons/android-chrome-192x192.png"),
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: window.__prependStaticUrl("/img/icons/android-chrome-512x512.png"),
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
start_url: window.location.origin + window.FileBrowser.BaseURL,
|
||||
display: "standalone",
|
||||
background_color: "#ffffff",
|
||||
theme_color: window.FileBrowser.Color || "#455a64",
|
||||
};
|
||||
|
||||
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
|
||||
var dynamicManifest = {
|
||||
"name": window.FileBrowser.Name || 'File Browser',
|
||||
"short_name": window.FileBrowser.Name || 'File Browser',
|
||||
"icons": [
|
||||
{
|
||||
"src": fullStaticURL + "/img/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": fullStaticURL + "/img/icons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
const stringManifest = JSON.stringify(dynamicManifest);
|
||||
const blob = new Blob([stringManifest], { type: "application/json" });
|
||||
const manifestURL = URL.createObjectURL(blob);
|
||||
document
|
||||
.querySelector("#manifestPlaceholder")
|
||||
.setAttribute("href", manifestURL);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
z-index: 9999;
|
||||
transition: 0.1s ease opacity;
|
||||
-webkit-transition: 0.1s ease opacity;
|
||||
}
|
||||
|
||||
#loading.done {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#loading .spinner {
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#loading .spinner > div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #333;
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
#loading .spinner .bounce1 {
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
#loading .spinner .bounce2 {
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bouncedelay {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
],
|
||||
"start_url": window.location.origin + window.FileBrowser.BaseURL,
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": window.FileBrowser.Color || "#455a64"
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
const stringManifest = JSON.stringify(dynamicManifest);
|
||||
const blob = new Blob([stringManifest], {type: 'application/json'});
|
||||
const manifestURL = URL.createObjectURL(blob);
|
||||
document.querySelector('#manifestPlaceholder').setAttribute('href', manifestURL);
|
||||
</script>
|
||||
@keyframes sk-bouncedelay {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<style>
|
||||
#loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
z-index: 9999;
|
||||
transition: .1s ease opacity;
|
||||
-webkit-transition: .1s ease opacity;
|
||||
}
|
||||
|
||||
#loading.done {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#loading .spinner {
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#loading .spinner > div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #333;
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
#loading .spinner .bounce1 {
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
#loading .spinner .bounce2 {
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bouncedelay {
|
||||
0%, 80%, 100% { -webkit-transform: scale(0) }
|
||||
40% { -webkit-transform: scale(1.0) }
|
||||
}
|
||||
|
||||
@keyframes sk-bouncedelay {
|
||||
0%, 80%, 100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
} 40% {
|
||||
-webkit-transform: scale(1.0);
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<div id="loading">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
<div class="bounce3"></div>
|
||||
<div id="loading">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
<div class="bounce3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
[{[ if .Theme -]}]
|
||||
<link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css" />
|
||||
[{[ end ]}]
|
||||
[{[ if .CSS -]}]
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
|
||||
[{[ if .Theme -]}]
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css"
|
||||
/>
|
||||
[{[ end ]}] [{[ if .CSS -]}]
|
||||
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
||||
[{[ end ]}]
|
||||
</body>
|
||||
[{[ end ]}]
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -174,6 +174,12 @@ table th {
|
||||
background: var(--surfacePrimary);
|
||||
color: var(--textPrimary);
|
||||
}
|
||||
.shell__divider {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.shell__divider:hover {
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
.shell__result {
|
||||
border-top: 1px solid var(--divider);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<script>
|
||||
// eslint-disable-next-line no-undef
|
||||
__webpack_public_path__ = window.FileBrowser.StaticURL + "/";
|
||||
// __webpack_public_path__ = window.FileBrowser.StaticURL + "/";
|
||||
|
||||
export default {
|
||||
name: "app",
|
||||
|
||||
@@ -6,6 +6,12 @@ import { fetchURL } from "./utils";
|
||||
|
||||
const RETRY_BASE_DELAY = 1000;
|
||||
const RETRY_MAX_DELAY = 20000;
|
||||
const SPEED_UPDATE_INTERVAL = 1000;
|
||||
const ALPHA = 0.2;
|
||||
const ONE_MINUS_ALPHA = 1 - ALPHA;
|
||||
const RECENT_SPEEDS_LIMIT = 5;
|
||||
const MB_DIVISOR = 1024 * 1024;
|
||||
const CURRENT_UPLOAD_LIST = {};
|
||||
|
||||
export async function upload(
|
||||
filePath,
|
||||
@@ -34,19 +40,47 @@ export async function upload(
|
||||
"X-Auth": store.state.jwt,
|
||||
},
|
||||
onError: function (error) {
|
||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
||||
}
|
||||
delete CURRENT_UPLOAD_LIST[filePath];
|
||||
reject("Upload failed: " + error);
|
||||
},
|
||||
onProgress: function (bytesUploaded) {
|
||||
// Emulate ProgressEvent.loaded which is used by calling functions
|
||||
// loaded is specified in bytes (https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent/loaded)
|
||||
let fileData = CURRENT_UPLOAD_LIST[filePath];
|
||||
fileData.currentBytesUploaded = bytesUploaded;
|
||||
|
||||
if (!fileData.hasStarted) {
|
||||
fileData.hasStarted = true;
|
||||
fileData.lastProgressTimestamp = Date.now();
|
||||
|
||||
fileData.interval = setInterval(() => {
|
||||
calcProgress(filePath);
|
||||
}, SPEED_UPDATE_INTERVAL);
|
||||
}
|
||||
if (typeof onupload === "function") {
|
||||
onupload({ loaded: bytesUploaded });
|
||||
}
|
||||
},
|
||||
onSuccess: function () {
|
||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
||||
}
|
||||
delete CURRENT_UPLOAD_LIST[filePath];
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
CURRENT_UPLOAD_LIST[filePath] = {
|
||||
upload: upload,
|
||||
recentSpeeds: [],
|
||||
initialBytesUploaded: 0,
|
||||
currentBytesUploaded: 0,
|
||||
currentAverageSpeed: 0,
|
||||
lastProgressTimestamp: null,
|
||||
sumOfRecentSpeeds: 0,
|
||||
hasStarted: false,
|
||||
interval: null,
|
||||
};
|
||||
upload.start();
|
||||
});
|
||||
}
|
||||
@@ -88,3 +122,74 @@ export async function useTus(content) {
|
||||
function isTusSupported() {
|
||||
return tus.isSupported === true;
|
||||
}
|
||||
|
||||
function computeETA(state) {
|
||||
if (state.speedMbyte === 0) {
|
||||
return Infinity;
|
||||
}
|
||||
const totalSize = state.sizes.reduce((acc, size) => acc + size, 0);
|
||||
const uploadedSize = state.progress.reduce(
|
||||
(acc, progress) => acc + progress,
|
||||
0
|
||||
);
|
||||
const remainingSize = totalSize - uploadedSize;
|
||||
const speedBytesPerSecond = state.speedMbyte * 1024 * 1024;
|
||||
return remainingSize / speedBytesPerSecond;
|
||||
}
|
||||
|
||||
function computeGlobalSpeedAndETA() {
|
||||
let totalSpeed = 0;
|
||||
let totalCount = 0;
|
||||
|
||||
for (let filePath in CURRENT_UPLOAD_LIST) {
|
||||
totalSpeed += CURRENT_UPLOAD_LIST[filePath].currentAverageSpeed;
|
||||
totalCount++;
|
||||
}
|
||||
|
||||
if (totalCount === 0) return { speed: 0, eta: Infinity };
|
||||
|
||||
const averageSpeed = totalSpeed / totalCount;
|
||||
const averageETA = computeETA(store.state.upload, averageSpeed);
|
||||
|
||||
return { speed: averageSpeed, eta: averageETA };
|
||||
}
|
||||
|
||||
function calcProgress(filePath) {
|
||||
let fileData = CURRENT_UPLOAD_LIST[filePath];
|
||||
|
||||
let elapsedTime = (Date.now() - fileData.lastProgressTimestamp) / 1000;
|
||||
let bytesSinceLastUpdate =
|
||||
fileData.currentBytesUploaded - fileData.initialBytesUploaded;
|
||||
let currentSpeed = bytesSinceLastUpdate / MB_DIVISOR / elapsedTime;
|
||||
|
||||
if (fileData.recentSpeeds.length >= RECENT_SPEEDS_LIMIT) {
|
||||
fileData.sumOfRecentSpeeds -= fileData.recentSpeeds.shift();
|
||||
}
|
||||
|
||||
fileData.recentSpeeds.push(currentSpeed);
|
||||
fileData.sumOfRecentSpeeds += currentSpeed;
|
||||
|
||||
let avgRecentSpeed =
|
||||
fileData.sumOfRecentSpeeds / fileData.recentSpeeds.length;
|
||||
fileData.currentAverageSpeed =
|
||||
ALPHA * avgRecentSpeed + ONE_MINUS_ALPHA * fileData.currentAverageSpeed;
|
||||
|
||||
const { speed, eta } = computeGlobalSpeedAndETA();
|
||||
store.commit("setUploadSpeed", speed);
|
||||
store.commit("setETA", eta);
|
||||
|
||||
fileData.initialBytesUploaded = fileData.currentBytesUploaded;
|
||||
fileData.lastProgressTimestamp = Date.now();
|
||||
}
|
||||
|
||||
export function abortAllUploads() {
|
||||
for (let filePath in CURRENT_UPLOAD_LIST) {
|
||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
||||
}
|
||||
if (CURRENT_UPLOAD_LIST[filePath].upload) {
|
||||
CURRENT_UPLOAD_LIST[filePath].upload.abort(true);
|
||||
}
|
||||
delete CURRENT_UPLOAD_LIST[filePath];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,10 +90,10 @@ export default {
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
show(val, old) {
|
||||
this.active = val === "search";
|
||||
currentPrompt(val, old) {
|
||||
this.active = val?.prompt === "search";
|
||||
|
||||
if (old === "search" && !this.active) {
|
||||
if (old?.prompt === "search" && !this.active) {
|
||||
if (this.reload) {
|
||||
this.setReload(true);
|
||||
}
|
||||
@@ -116,8 +116,8 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(["user", "show"]),
|
||||
...mapGetters(["isListing"]),
|
||||
...mapState(["user"]),
|
||||
...mapGetters(["isListing", "currentPrompt"]),
|
||||
boxes() {
|
||||
return boxes;
|
||||
},
|
||||
|
||||
@@ -1,37 +1,54 @@
|
||||
<template>
|
||||
<div
|
||||
@click="focus"
|
||||
class="shell"
|
||||
ref="scrollable"
|
||||
:class="{ ['shell--hidden']: !showShell }"
|
||||
:style="{ height: `${this.shellHeight}em` }"
|
||||
>
|
||||
<div v-for="(c, index) in content" :key="index" class="shell__result">
|
||||
<div class="shell__prompt">
|
||||
<i class="material-icons">chevron_right</i>
|
||||
<div
|
||||
@pointerdown="startDrag()"
|
||||
@pointerup="stopDrag()"
|
||||
class="shell__divider"
|
||||
:style="this.shellDrag ? { background: `${checkTheme()}` } : ''"
|
||||
></div>
|
||||
<div @click="focus" class="shell__content" ref="scrollable">
|
||||
<div v-for="(c, index) in content" :key="index" class="shell__result">
|
||||
<div class="shell__prompt">
|
||||
<i class="material-icons">chevron_right</i>
|
||||
</div>
|
||||
<pre class="shell__text">{{ c.text }}</pre>
|
||||
</div>
|
||||
<pre class="shell__text">{{ c.text }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="shell__result" :class="{ 'shell__result--hidden': !canInput }">
|
||||
<div class="shell__prompt">
|
||||
<i class="material-icons">chevron_right</i>
|
||||
<div
|
||||
class="shell__result"
|
||||
:class="{ 'shell__result--hidden': !canInput }"
|
||||
>
|
||||
<div class="shell__prompt">
|
||||
<i class="material-icons">chevron_right</i>
|
||||
</div>
|
||||
<pre
|
||||
tabindex="0"
|
||||
ref="input"
|
||||
class="shell__text"
|
||||
contenteditable="true"
|
||||
@keydown.prevent.38="historyUp"
|
||||
@keydown.prevent.40="historyDown"
|
||||
@keypress.prevent.enter="submit"
|
||||
/>
|
||||
</div>
|
||||
<pre
|
||||
tabindex="0"
|
||||
ref="input"
|
||||
class="shell__text"
|
||||
contenteditable="true"
|
||||
@keydown.prevent.38="historyUp"
|
||||
@keydown.prevent.40="historyDown"
|
||||
@keypress.prevent.enter="submit"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@pointerup="stopDrag()"
|
||||
class="shell__overlay"
|
||||
v-show="this.shellDrag"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations, mapState, mapGetters } from "vuex";
|
||||
import { commands } from "@/api";
|
||||
import { throttle } from "lodash";
|
||||
import { theme } from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
name: "shell",
|
||||
@@ -51,9 +68,55 @@ export default {
|
||||
history: [],
|
||||
historyPos: 0,
|
||||
canInput: true,
|
||||
shellDrag: false,
|
||||
shellHeight: 25,
|
||||
fontsize: parseFloat(getComputedStyle(document.documentElement).fontSize),
|
||||
}),
|
||||
mounted() {
|
||||
window.addEventListener("resize", this.resize);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("resize", this.resize);
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["toggleShell"]),
|
||||
checkTheme() {
|
||||
if (theme == "dark") {
|
||||
return "rgba(255, 255, 255, 0.4)";
|
||||
}
|
||||
return "rgba(127, 127, 127, 0.4)";
|
||||
},
|
||||
startDrag() {
|
||||
document.addEventListener("pointermove", this.handleDrag);
|
||||
this.shellDrag = true;
|
||||
},
|
||||
stopDrag() {
|
||||
document.removeEventListener("pointermove", this.handleDrag);
|
||||
this.shellDrag = false;
|
||||
},
|
||||
handleDrag: throttle(function (event) {
|
||||
const top = window.innerHeight / this.fontsize - 4;
|
||||
const userPos = (window.innerHeight - event.clientY) / this.fontsize;
|
||||
const bottom =
|
||||
2.25 +
|
||||
document.querySelector(".shell__divider").offsetHeight / this.fontsize;
|
||||
|
||||
if (userPos <= top && userPos >= bottom) {
|
||||
this.shellHeight = userPos.toFixed(2);
|
||||
}
|
||||
}, 32),
|
||||
resize: throttle(function () {
|
||||
const top = window.innerHeight / this.fontsize - 4;
|
||||
const bottom =
|
||||
2.25 +
|
||||
document.querySelector(".shell__divider").offsetHeight / this.fontsize;
|
||||
|
||||
if (this.shellHeight > top) {
|
||||
this.shellHeight = top;
|
||||
} else if (this.shellHeight < bottom) {
|
||||
this.shellHeight = bottom;
|
||||
}
|
||||
}, 32),
|
||||
scroll: function () {
|
||||
this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight;
|
||||
},
|
||||
|
||||
@@ -133,9 +133,9 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState(["user"]),
|
||||
...mapGetters(["isLogged"]),
|
||||
...mapGetters(["isLogged", "currentPrompt"]),
|
||||
active() {
|
||||
return this.$store.state.show === "sidebar";
|
||||
return this.currentPrompt?.prompt === "sidebar";
|
||||
},
|
||||
signup: () => signup,
|
||||
version: () => version,
|
||||
|
||||
@@ -10,12 +10,7 @@
|
||||
@mouseup="mouseUp"
|
||||
@wheel="wheelMove"
|
||||
>
|
||||
<img
|
||||
src=""
|
||||
class="image-ex-img image-ex-img-center"
|
||||
ref="imgex"
|
||||
@load="onLoad"
|
||||
/>
|
||||
<img class="image-ex-img image-ex-img-center" ref="imgex" @load="onLoad" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<script>
|
||||
import { enableThumbs } from "@/utils/constants";
|
||||
import { mapMutations, mapGetters, mapState } from "vuex";
|
||||
import filesize from "filesize";
|
||||
import { filesize } from "@/utils";
|
||||
import moment from "moment";
|
||||
import { files as api } from "@/api";
|
||||
import * as upload from "@/utils/upload";
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<slot />
|
||||
|
||||
<div id="dropdown" :class="{ active: this.$store.state.show === 'more' }">
|
||||
<div id="dropdown" :class="{ active: this.currentPromptName === 'more' }">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<div
|
||||
class="overlay"
|
||||
v-show="this.$store.state.show == 'more'"
|
||||
v-show="this.currentPromptName == 'more'"
|
||||
@click="$store.commit('closeHovers')"
|
||||
/>
|
||||
</header>
|
||||
@@ -34,7 +34,8 @@
|
||||
<script>
|
||||
import { logoURL } from "@/utils/constants";
|
||||
|
||||
import Action from "@/components/header/Action";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "header-bar",
|
||||
@@ -52,6 +53,9 @@ export default {
|
||||
this.$store.commit("showHover", "sidebar");
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["currentPromptName"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,33 +6,50 @@
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t("prompts.copyMessage") }}</p>
|
||||
<file-list @update:selected="(val) => (dest = val)"></file-list>
|
||||
<file-list ref="fileList" @update:selected="(val) => (dest = val)">
|
||||
</file-list>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="copy"
|
||||
:aria-label="$t('buttons.copy')"
|
||||
:title="$t('buttons.copy')"
|
||||
>
|
||||
{{ $t("buttons.copy") }}
|
||||
</button>
|
||||
<div
|
||||
class="card-action"
|
||||
style="display: flex; align-items: center; justify-content: space-between;"
|
||||
>
|
||||
<template v-if="user.perm.create">
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="$refs.fileList.createDir()"
|
||||
:aria-label="$t('sidebar.newFolder')"
|
||||
:title="$t('sidebar.newFolder')"
|
||||
style="justify-self: left;"
|
||||
>
|
||||
<span>{{ $t("sidebar.newFolder") }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<div>
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="copy"
|
||||
:aria-label="$t('buttons.copy')"
|
||||
:title="$t('buttons.copy')"
|
||||
>
|
||||
{{ $t("buttons.copy") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import FileList from "./FileList";
|
||||
import FileList from "./FileList.vue";
|
||||
import { files as api } from "@/api";
|
||||
import buttons from "@/utils/buttons";
|
||||
import * as upload from "@/utils/upload";
|
||||
@@ -46,7 +63,7 @@ export default {
|
||||
dest: null,
|
||||
};
|
||||
},
|
||||
computed: mapState(["req", "selected"]),
|
||||
computed: mapState(["req", "selected", "user"]),
|
||||
methods: {
|
||||
copy: async function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -37,8 +37,8 @@ import buttons from "@/utils/buttons";
|
||||
export default {
|
||||
name: "delete",
|
||||
computed: {
|
||||
...mapGetters(["isListing", "selectedCount"]),
|
||||
...mapState(["req", "selected", "showConfirm"]),
|
||||
...mapGetters(["isListing", "selectedCount", "currentPrompt"]),
|
||||
...mapState(["req", "selected"]),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["closeHovers"]),
|
||||
@@ -50,7 +50,7 @@ export default {
|
||||
await api.remove(this.$route.path);
|
||||
buttons.success("delete");
|
||||
|
||||
this.showConfirm();
|
||||
this.currentPrompt?.confirm();
|
||||
this.closeHovers();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
v-for="(ext, format) in formats"
|
||||
:key="format"
|
||||
class="button button--block"
|
||||
@click="showConfirm(format)"
|
||||
@click="currentPrompt.confirm(format)"
|
||||
v-focus
|
||||
>
|
||||
{{ ext }}
|
||||
@@ -21,7 +21,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "download",
|
||||
@@ -38,6 +38,8 @@ export default {
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: mapState(["showConfirm"]),
|
||||
computed: {
|
||||
...mapGetters(["currentPrompt"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -133,6 +133,17 @@ export default {
|
||||
this.selected = event.currentTarget.dataset.url;
|
||||
this.$emit("update:selected", this.selected);
|
||||
},
|
||||
createDir: async function () {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "newDir",
|
||||
action: null,
|
||||
confirm: null,
|
||||
props: {
|
||||
redirect: false,
|
||||
base: this.current === this.$route.path ? null : this.current,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import filesize from "filesize";
|
||||
import { filesize } from "@/utils";
|
||||
import moment from "moment";
|
||||
import { files as api } from "@/api";
|
||||
|
||||
@@ -142,7 +142,7 @@ export default {
|
||||
try {
|
||||
const hash = await api.checksum(link, algo);
|
||||
// eslint-disable-next-line
|
||||
event.target.innerHTML = hash
|
||||
event.target.innerHTML = hash;
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
}
|
||||
|
||||
@@ -5,34 +5,51 @@
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<file-list @update:selected="(val) => (dest = val)"></file-list>
|
||||
<file-list ref="fileList" @update:selected="(val) => (dest = val)">
|
||||
</file-list>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="move"
|
||||
:disabled="$route.path === dest"
|
||||
:aria-label="$t('buttons.move')"
|
||||
:title="$t('buttons.move')"
|
||||
>
|
||||
{{ $t("buttons.move") }}
|
||||
</button>
|
||||
<div
|
||||
class="card-action"
|
||||
style="display: flex; align-items: center; justify-content: space-between;"
|
||||
>
|
||||
<template v-if="user.perm.create">
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="$refs.fileList.createDir()"
|
||||
:aria-label="$t('sidebar.newFolder')"
|
||||
:title="$t('sidebar.newFolder')"
|
||||
style="justify-self: left;"
|
||||
>
|
||||
<span>{{ $t("sidebar.newFolder") }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<div>
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="move"
|
||||
:disabled="$route.path === dest"
|
||||
:aria-label="$t('buttons.move')"
|
||||
:title="$t('buttons.move')"
|
||||
>
|
||||
{{ $t("buttons.move") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import FileList from "./FileList";
|
||||
import FileList from "./FileList.vue";
|
||||
import { files as api } from "@/api";
|
||||
import buttons from "@/utils/buttons";
|
||||
import * as upload from "@/utils/upload";
|
||||
@@ -46,7 +63,7 @@ export default {
|
||||
dest: null,
|
||||
};
|
||||
},
|
||||
computed: mapState(["req", "selected"]),
|
||||
computed: mapState(["req", "selected", "user"]),
|
||||
methods: {
|
||||
move: async function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -43,6 +43,16 @@ import url from "@/utils/url";
|
||||
|
||||
export default {
|
||||
name: "new-dir",
|
||||
props: {
|
||||
redirect: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
base: {
|
||||
type: [String, null],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
name: "",
|
||||
@@ -57,7 +67,11 @@ export default {
|
||||
if (this.new === "") return;
|
||||
|
||||
// Build the path of the new directory.
|
||||
let uri = this.isFiles ? this.$route.path + "/" : "/";
|
||||
let uri;
|
||||
|
||||
if (this.base) uri = this.base;
|
||||
else if (this.isFiles) uri = this.$route.path + "/";
|
||||
else uri = "/";
|
||||
|
||||
if (!this.isListing) {
|
||||
uri = url.removeLastDir(uri) + "/";
|
||||
@@ -65,10 +79,14 @@ export default {
|
||||
|
||||
uri += encodeURIComponent(this.name) + "/";
|
||||
uri = uri.replace("//", "/");
|
||||
|
||||
try {
|
||||
await api.post(uri);
|
||||
this.$router.push({ path: uri });
|
||||
if (this.redirect) {
|
||||
this.$router.push({ path: uri });
|
||||
} else if (!this.base) {
|
||||
const res = await api.fetch(url.removeLastDir(uri) + "/");
|
||||
this.$store.commit("updateRequest", res);
|
||||
}
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<component ref="currentComponent" :is="currentComponent"></component>
|
||||
<component
|
||||
v-if="showOverlay"
|
||||
:ref="currentPromptName"
|
||||
:is="currentPromptName"
|
||||
v-bind="currentPrompt.props"
|
||||
>
|
||||
</component>
|
||||
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Help from "./Help";
|
||||
import Info from "./Info";
|
||||
import Delete from "./Delete";
|
||||
import Rename from "./Rename";
|
||||
import Download from "./Download";
|
||||
import Move from "./Move";
|
||||
import Copy from "./Copy";
|
||||
import NewFile from "./NewFile";
|
||||
import NewDir from "./NewDir";
|
||||
import Replace from "./Replace";
|
||||
import ReplaceRename from "./ReplaceRename";
|
||||
import Share from "./Share";
|
||||
import Upload from "./Upload";
|
||||
import ShareDelete from "./ShareDelete";
|
||||
import { mapState } from "vuex";
|
||||
import Help from "./Help.vue";
|
||||
import Info from "./Info.vue";
|
||||
import Delete from "./Delete.vue";
|
||||
import Rename from "./Rename.vue";
|
||||
import Download from "./Download.vue";
|
||||
import Move from "./Move.vue";
|
||||
import Copy from "./Copy.vue";
|
||||
import NewFile from "./NewFile.vue";
|
||||
import NewDir from "./NewDir.vue";
|
||||
import Replace from "./Replace.vue";
|
||||
import ReplaceRename from "./ReplaceRename.vue";
|
||||
import Share from "./Share.vue";
|
||||
import Upload from "./Upload.vue";
|
||||
import ShareDelete from "./ShareDelete.vue";
|
||||
import Sidebar from "../Sidebar.vue";
|
||||
import { mapGetters, mapState } from "vuex";
|
||||
import buttons from "@/utils/buttons";
|
||||
|
||||
export default {
|
||||
@@ -40,6 +47,7 @@ export default {
|
||||
ReplaceRename,
|
||||
Upload,
|
||||
ShareDelete,
|
||||
Sidebar
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
@@ -52,7 +60,7 @@ export default {
|
||||
},
|
||||
created() {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (this.show == null) return;
|
||||
if (this.currentPrompt == null) return;
|
||||
|
||||
let prompt = this.$refs.currentComponent;
|
||||
|
||||
@@ -64,7 +72,7 @@ export default {
|
||||
|
||||
// Enter
|
||||
if (event.keyCode == 13) {
|
||||
switch (this.show) {
|
||||
switch (this.currentPrompt.prompt) {
|
||||
case "delete":
|
||||
prompt.submit();
|
||||
break;
|
||||
@@ -82,31 +90,13 @@ export default {
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
...mapState(["show", "plugins"]),
|
||||
currentComponent: function () {
|
||||
const matched =
|
||||
[
|
||||
"info",
|
||||
"help",
|
||||
"delete",
|
||||
"rename",
|
||||
"move",
|
||||
"copy",
|
||||
"newFile",
|
||||
"newDir",
|
||||
"download",
|
||||
"replace",
|
||||
"replace-rename",
|
||||
"share",
|
||||
"upload",
|
||||
"share-delete",
|
||||
].indexOf(this.show) >= 0;
|
||||
|
||||
return (matched && this.show) || null;
|
||||
},
|
||||
...mapState(["plugins"]),
|
||||
...mapGetters(["currentPrompt", "currentPromptName"]),
|
||||
showOverlay: function () {
|
||||
return (
|
||||
this.show !== null && this.show !== "search" && this.show !== "more"
|
||||
this.currentPrompt !== null &&
|
||||
this.currentPrompt.prompt !== "search" &&
|
||||
this.currentPrompt.prompt !== "more"
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--blue"
|
||||
@click="showAction"
|
||||
@click="currentPrompt.action"
|
||||
:aria-label="$t('buttons.continue')"
|
||||
:title="$t('buttons.continue')"
|
||||
>
|
||||
@@ -27,7 +27,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--red"
|
||||
@click="showConfirm"
|
||||
@click="currentPrompt.confirm"
|
||||
:aria-label="$t('buttons.replace')"
|
||||
:title="$t('buttons.replace')"
|
||||
>
|
||||
@@ -38,10 +38,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "replace",
|
||||
computed: mapState(["showConfirm", "showAction"]),
|
||||
computed: mapGetters(["currentPrompt"]),
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--blue"
|
||||
@click="(event) => showConfirm(event, 'rename')"
|
||||
@click="(event) => currentPrompt.confirm(event, 'rename')"
|
||||
:aria-label="$t('buttons.rename')"
|
||||
:title="$t('buttons.rename')"
|
||||
>
|
||||
@@ -27,7 +27,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--red"
|
||||
@click="(event) => showConfirm(event, 'overwrite')"
|
||||
@click="(event) => currentPrompt.confirm(event, 'overwrite')"
|
||||
:aria-label="$t('buttons.replace')"
|
||||
:title="$t('buttons.replace')"
|
||||
>
|
||||
@@ -38,10 +38,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "replace-rename",
|
||||
computed: mapState(["showConfirm"]),
|
||||
computed: mapGetters(["currentPrompt"]),
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -25,16 +25,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "share-delete",
|
||||
computed: {
|
||||
...mapState(["showConfirm"]),
|
||||
...mapGetters(["currentPrompt"]),
|
||||
},
|
||||
methods: {
|
||||
submit: function () {
|
||||
this.showConfirm();
|
||||
this.currentPrompt?.confirm();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,18 @@
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("prompts.uploadFiles", { files: filesInUploadCount }) }}</h2>
|
||||
|
||||
<div class="upload-info">
|
||||
<div class="upload-speed">{{ uploadSpeed.toFixed(2) }} MB/s</div>
|
||||
<div class="upload-eta">{{ formattedETA }} remaining</div>
|
||||
</div>
|
||||
<button
|
||||
class="action"
|
||||
@click="abortAll"
|
||||
aria-label="Abort upload"
|
||||
title="Abort upload"
|
||||
>
|
||||
<i class="material-icons">{{ "cancel" }}</i>
|
||||
</button>
|
||||
<button
|
||||
class="action"
|
||||
@click="toggle"
|
||||
@@ -42,7 +53,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { mapGetters, mapMutations } from "vuex";
|
||||
import { abortAllUploads } from "@/api/tus";
|
||||
import buttons from "@/utils/buttons";
|
||||
|
||||
export default {
|
||||
name: "uploadFiles",
|
||||
@@ -52,12 +65,42 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["filesInUpload", "filesInUploadCount"]),
|
||||
...mapGetters([
|
||||
"filesInUpload",
|
||||
"filesInUploadCount",
|
||||
"uploadSpeed",
|
||||
"eta",
|
||||
]),
|
||||
...mapMutations(["resetUpload"]),
|
||||
formattedETA() {
|
||||
if (!this.eta || this.eta === Infinity) {
|
||||
return "--:--:--";
|
||||
}
|
||||
|
||||
let totalSeconds = this.eta;
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
totalSeconds %= 3600;
|
||||
const minutes = Math.floor(totalSeconds / 60);
|
||||
const seconds = Math.round(totalSeconds % 60);
|
||||
|
||||
return `${hours.toString().padStart(2, "0")}:${minutes
|
||||
.toString()
|
||||
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggle: function () {
|
||||
this.open = !this.open;
|
||||
},
|
||||
abortAll() {
|
||||
if (confirm(this.$t("upload.abortUpload"))) {
|
||||
abortAllUploads();
|
||||
buttons.done("upload");
|
||||
this.open = false;
|
||||
this.$store.commit("resetUpload");
|
||||
this.$store.commit("setReload", true);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -17,6 +17,7 @@ export default {
|
||||
hu: "hu",
|
||||
ar: "ar",
|
||||
de: "de",
|
||||
el: "el",
|
||||
en: "en",
|
||||
es: "es",
|
||||
fr: "fr",
|
||||
|
||||
@@ -67,10 +67,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Languages from "./Languages";
|
||||
import Rules from "./Rules";
|
||||
import Permissions from "./Permissions";
|
||||
import Commands from "./Commands";
|
||||
import Languages from "./Languages.vue";
|
||||
import Rules from "./Rules.vue";
|
||||
import Permissions from "./Permissions.vue";
|
||||
import Commands from "./Commands.vue";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
|
||||
@@ -2,21 +2,49 @@
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 25em;
|
||||
max-height: calc(100% - 4em);
|
||||
background: white;
|
||||
color: #212121;
|
||||
z-index: 9999;
|
||||
z-index: 9997;
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
overflow: auto;
|
||||
font-size: 1rem;
|
||||
cursor: text;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
transition: .2s ease transform;
|
||||
}
|
||||
|
||||
body.rtl .shell {
|
||||
.shell__divider {
|
||||
position: relative;
|
||||
height: 8px;
|
||||
z-index: 9999;
|
||||
background: rgba(127, 127, 127, 0.1);
|
||||
transition: 0.2s ease background;
|
||||
cursor: ns-resize;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.shell__divider:hover {
|
||||
background: rgba(127, 127, 127, 0.4);
|
||||
}
|
||||
|
||||
.shell__content {
|
||||
height: 100%;
|
||||
font-family: monospace;
|
||||
overflow: auto;
|
||||
font-size: 1rem;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.shell__overlay {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 9998;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
body.rtl .shell-content {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,173 +1,242 @@
|
||||
@import "material-icons/iconfont/filled.css";
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic-ext.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto"),
|
||||
local("Roboto-Regular"),
|
||||
url(../assets/fonts/roboto/normal-cyrillic-ext.woff2) format("woff2");
|
||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto"),
|
||||
local("Roboto-Regular"),
|
||||
url(../assets/fonts/roboto/normal-cyrillic.woff2) format("woff2");
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek-ext.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto"),
|
||||
local("Roboto-Regular"),
|
||||
url(../assets/fonts/roboto/normal-greek-ext.woff2) format("woff2");
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto"),
|
||||
local("Roboto-Regular"),
|
||||
url(../assets/fonts/roboto/normal-greek.woff2) format("woff2");
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-vietnamese.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto"),
|
||||
local("Roboto-Regular"),
|
||||
url(../assets/fonts/roboto/normal-vietnamese.woff2) format("woff2");
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
src:
|
||||
local("Roboto"),
|
||||
local("Roboto-Regular"),
|
||||
url(../assets/fonts/roboto/normal-latin-ext.woff2) format("woff2");
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
src:
|
||||
local("Roboto"),
|
||||
local("Roboto-Regular"),
|
||||
url(../assets/fonts/roboto/normal-latin.woff2) format("woff2");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC,
|
||||
U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic-ext.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto Medium"),
|
||||
local("Roboto-Medium"),
|
||||
url(../assets/fonts/roboto/medium-cyrillic-ext.woff2) format("woff2");
|
||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto Medium"),
|
||||
local("Roboto-Medium"),
|
||||
url(../assets/fonts/roboto/medium-cyrillic.woff2) format("woff2");
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek-ext.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto Medium"),
|
||||
local("Roboto-Medium"),
|
||||
url(../assets/fonts/roboto/medium-greek-ext.woff2) format("woff2");
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto Medium"),
|
||||
local("Roboto-Medium"),
|
||||
url(../assets/fonts/roboto/medium-greek.woff2) format("woff2");
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-vietnamese.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto Medium"),
|
||||
local("Roboto-Medium"),
|
||||
url(../assets/fonts/roboto/medium-vietnamese.woff2) format("woff2");
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
src:
|
||||
local("Roboto Medium"),
|
||||
local("Roboto-Medium"),
|
||||
url(../assets/fonts/roboto/medium-latin-ext.woff2) format("woff2");
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
src:
|
||||
local("Roboto Medium"),
|
||||
local("Roboto-Medium"),
|
||||
url(../assets/fonts/roboto/medium-latin.woff2) format("woff2");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC,
|
||||
U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-cyrillic-ext.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto Bold"),
|
||||
local("Roboto-Bold"),
|
||||
url(../assets/fonts/roboto/bold-cyrillic-ext.woff2) format("woff2");
|
||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-cyrillic.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto Bold"),
|
||||
local("Roboto-Bold"),
|
||||
url(../assets/fonts/roboto/bold-cyrillic.woff2) format("woff2");
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-greek-ext.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto Bold"),
|
||||
local("Roboto-Bold"),
|
||||
url(../assets/fonts/roboto/bold-greek-ext.woff2) format("woff2");
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-greek.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto Bold"),
|
||||
local("Roboto-Bold"),
|
||||
url(../assets/fonts/roboto/bold-greek.woff2) format("woff2");
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-vietnamese.woff2) format('woff2');
|
||||
src:
|
||||
local("Roboto Bold"),
|
||||
local("Roboto-Bold"),
|
||||
url(../assets/fonts/roboto/bold-vietnamese.woff2) format("woff2");
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
src:
|
||||
local("Roboto Bold"),
|
||||
local("Roboto-Bold"),
|
||||
url(../assets/fonts/roboto/bold-latin-ext.woff2) format("woff2");
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-family: "Roboto";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
src:
|
||||
local("Roboto Bold"),
|
||||
local("Roboto-Bold"),
|
||||
url(../assets/fonts/roboto/bold-latin.woff2) format("woff2");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC,
|
||||
U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
||||
@import '~material-icons/iconfont/filled.css';
|
||||
|
||||
.material-icons {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,10 @@
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.shell__divider {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
header .search-button,
|
||||
header .menu-button {
|
||||
display: inherit;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@import "~normalize.css/normalize.css";
|
||||
@import "~noty/lib/noty.css";
|
||||
@import "~noty/lib/themes/mint.css";
|
||||
@import "normalize.css/normalize.css";
|
||||
@import "noty/lib/noty.css";
|
||||
@import "noty/lib/themes/mint.css";
|
||||
@import "./_variables.css";
|
||||
@import "./_buttons.css";
|
||||
@import "./_inputs.css";
|
||||
@@ -14,6 +14,7 @@
|
||||
@import "./upload-files.css";
|
||||
@import "./dashboard.css";
|
||||
@import "./login.css";
|
||||
@import "./mobile.css";
|
||||
|
||||
.link {
|
||||
color: var(--blue);
|
||||
@@ -27,9 +28,9 @@ main .spinner {
|
||||
}
|
||||
|
||||
main .spinner > div {
|
||||
width: .8em;
|
||||
height: .8em;
|
||||
margin: 0 .1em;
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
margin: 0 0.1em;
|
||||
font-size: 1em;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 100%;
|
||||
@@ -71,7 +72,7 @@ main .spinner .bounce2 {
|
||||
transition: 0.2s ease all;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
color: #546E7A;
|
||||
color: #546e7a;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
@@ -88,12 +89,12 @@ main .spinner .bounce2 {
|
||||
|
||||
.action i {
|
||||
padding: 0.4em;
|
||||
transition: .1s ease-in-out all;
|
||||
transition: 0.1s ease-in-out all;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.action:hover {
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.action ul {
|
||||
@@ -109,8 +110,8 @@ main .spinner .bounce2 {
|
||||
|
||||
.action ul li {
|
||||
line-height: 1;
|
||||
padding: .7em;
|
||||
transition: .1s ease background-color;
|
||||
padding: 0.7em;
|
||||
transition: 0.1s ease background-color;
|
||||
}
|
||||
|
||||
.action ul li:hover {
|
||||
@@ -139,7 +140,7 @@ main .spinner .bounce2 {
|
||||
background: var(--blue);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: .75em;
|
||||
font-size: 0.75em;
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
text-align: center;
|
||||
@@ -148,7 +149,6 @@ main .spinner .bounce2 {
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
|
||||
/* PREVIEWER */
|
||||
|
||||
#previewer {
|
||||
@@ -179,7 +179,7 @@ main .spinner .bounce2 {
|
||||
}
|
||||
|
||||
#previewer header .action:hover {
|
||||
background-color: rgba(255, 255, 255, 0.3)
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
#previewer header .action span {
|
||||
@@ -220,14 +220,14 @@ main .spinner .bounce2 {
|
||||
}
|
||||
#previewer .preview .info .title i {
|
||||
display: block;
|
||||
margin-bottom: .1em;
|
||||
margin-bottom: 0.1em;
|
||||
font-size: 4em;
|
||||
}
|
||||
#previewer .preview .info .button {
|
||||
display: inline-block;
|
||||
}
|
||||
#previewer .preview .info .button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2)
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
#previewer .preview .info .button i {
|
||||
display: block;
|
||||
@@ -241,15 +241,15 @@ main .spinner .bounce2 {
|
||||
}
|
||||
|
||||
#previewer h2.message {
|
||||
color: rgba(255, 255, 255, 0.5)
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
#previewer>button {
|
||||
#previewer > button {
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
top: calc(50% + 1.85em);
|
||||
transform: translateY(-50%);
|
||||
background-color: rgba(80, 80, 80, .5);
|
||||
background-color: rgba(80, 80, 80, 0.5);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
@@ -259,20 +259,20 @@ main .spinner .bounce2 {
|
||||
transition: 0.2s ease all;
|
||||
}
|
||||
|
||||
#previewer>button.hidden {
|
||||
#previewer > button.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#previewer>button>i {
|
||||
#previewer > button > i {
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
#previewer>button:first-of-type {
|
||||
#previewer > button:first-of-type {
|
||||
left: 0.5em;
|
||||
}
|
||||
|
||||
#previewer>button:last-of-type {
|
||||
#previewer > button:last-of-type {
|
||||
right: 0.5em;
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ body.rtl .breadcrumbs .chevron {
|
||||
}
|
||||
|
||||
#editor-container .breadcrumbs span {
|
||||
font-size: .75rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
#editor-container .breadcrumbs i {
|
||||
@@ -339,7 +339,7 @@ body.rtl .breadcrumbs .chevron {
|
||||
|
||||
.noty_buttons button {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0,0,0,0.1);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 0 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
@@ -356,7 +356,7 @@ body.rtl .breadcrumbs .chevron {
|
||||
|
||||
.credits > span {
|
||||
display: block;
|
||||
margin: .3em 0;
|
||||
margin: 0.3em 0;
|
||||
}
|
||||
|
||||
.credits a,
|
||||
@@ -365,7 +365,6 @@ body.rtl .breadcrumbs .chevron {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* ANIMATIONS *
|
||||
* * * * * * * * * * * * * * * */
|
||||
@@ -393,20 +392,20 @@ body.rtl .breadcrumbs .chevron {
|
||||
.rules > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: .5rem 0;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.rules input[type="checkbox"] {
|
||||
margin-right: .2rem;
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
|
||||
.rules input[type="text"] {
|
||||
border: 1px solid#ddd;
|
||||
padding: .2rem;
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
.rules label {
|
||||
margin-right: .5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.rules button {
|
||||
@@ -414,12 +413,10 @@ body.rtl .breadcrumbs .chevron {
|
||||
}
|
||||
|
||||
.rules button.delete {
|
||||
padding: .2rem .5rem;
|
||||
margin-left: .5rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@import './mobile.css';
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* RTL overrides *
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
"toggleSidebar": "Seitenleiste anzeigen",
|
||||
"update": "Update",
|
||||
"upload": "Upload",
|
||||
"openFile": "Datei öffnen"
|
||||
"openFile": "Datei öffnen",
|
||||
"continue": "Fortfahren"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download Datei",
|
||||
@@ -147,7 +148,7 @@
|
||||
"rename": "Umbenennen",
|
||||
"renameMessage": "Fügen Sie einen Namen ein für",
|
||||
"replace": "Ersetzen",
|
||||
"replaceMessage": "Eine der Datei mit dem gleichen Namen, wie die Sie hochladen wollen, existiert bereits. Soll die vorhandene Datei ersetzt werden ?\n",
|
||||
"replaceMessage": "Eine der Datei mit dem gleichen Namen, wie die Sie hochladen wollen, existiert bereits. Soll die vorhandene Datei übersprungen oder ersetzt werden?\n",
|
||||
"schedule": "Plan",
|
||||
"scheduleMessage": "Wählen Sie ein Datum und eine Zeit für die Veröffentlichung dieses Beitrags.",
|
||||
"show": "Anzeigen",
|
||||
@@ -184,10 +185,14 @@
|
||||
"commandRunnerHelp": "Hier könne Sie Befehle eintragen, welche bei den benannten Aktionen ausgeführt werden. Sie müssen pro Zeile jeweils einen Befehl eingeben. Die Umgebungsvariable {0} und {1} sind verfügbar, wobei {0} relative zu {1} ist. Für mehr Informationen über diese Funktion und die verfügbaren Umgebungsvariablen, lesen Sie bitte die {2}.",
|
||||
"commandsUpdated": "Befehle aktualisiert!",
|
||||
"createUserDir": "Automatisches Erstellen des Home-Verzeichnisses beim Anlegen neuer Benutzer",
|
||||
"tusUploads": "Gestückelter Upload",
|
||||
"tusUploadsHelp": "File Browser unterstützt das Hochladen von gestückelten Dateien und ermöglicht so einen effizienten, zuverlässigen, fortsetzbaren und gestückelten Datei-Upload auch in unzuverlässigen Netzwerken.",
|
||||
"tusUploadsChunkSize": "Gibt die maximale Größe pro Anfrage an (direkte Uploads werden für kleinere Uploads verwendet). Bitte geben Sie eine Byte-Angabe oder eine Zeichenfolge wie 10 MB, 1 GB usw. an",
|
||||
"tusUploadsRetryCount": "Anzahl der Wiederholungsversuche, wenn das Hochladen eines Stückes fehlschlägt.",
|
||||
"customStylesheet": "Individuelles Stylesheet",
|
||||
"defaultUserDescription": "Das sind die Standardeinstellung für Benutzer",
|
||||
"disableExternalLinks": "Externe Links deaktivieren (außer Dokumentation)",
|
||||
"disableUsedDiskPercentage": "Disable used disk percentage graph",
|
||||
"disableUsedDiskPercentage": "Diagramm zur Festplattennutzung deaktivieren",
|
||||
"documentation": "Dokumentation",
|
||||
"examples": "Beispiele",
|
||||
"executeOnShell": "In Shell ausführen",
|
||||
|
||||
282
frontend/src/i18n/el.json
Normal file
282
frontend/src/i18n/el.json
Normal file
@@ -0,0 +1,282 @@
|
||||
{
|
||||
"buttons": {
|
||||
"cancel": "Ακύρωση",
|
||||
"close": "Κλείσιμο",
|
||||
"copy": "Αντιγραφή",
|
||||
"copyFile": "Αντιγραφή αρχείου",
|
||||
"copyToClipboard": "Αντιγραφή στο πρόχειρο",
|
||||
"copyDownloadLinkToClipboard": "Αντιγραφή συνδέσμου λήψης στο πρόχειρο",
|
||||
"create": "Δημιουργία",
|
||||
"delete": "Διαγραφή",
|
||||
"download": "Λήψη",
|
||||
"file": "Αρχείο",
|
||||
"folder": "Φάκελος",
|
||||
"hideDotfiles": "Απόκρυψη κρυφών αρχείων",
|
||||
"info": "Πληροφορίες",
|
||||
"more": "Περισσότερα",
|
||||
"move": "Μετακίνηση",
|
||||
"moveFile": "Μετακίνηση αρχείου",
|
||||
"new": "Νέο",
|
||||
"next": "Επόμενο",
|
||||
"ok": "Εντάξει",
|
||||
"permalink": "Λήψη μόνιμου συνδέσμου",
|
||||
"previous": "Προηγούμενο",
|
||||
"publish": "Δημοσίευση",
|
||||
"rename": "Μετονομασία",
|
||||
"replace": "Αντικατάσταση",
|
||||
"reportIssue": "Αναφορά προβλήματος",
|
||||
"save": "Αποθήκευση",
|
||||
"schedule": "Προγραμματισμός",
|
||||
"search": "Αναζήτηση",
|
||||
"select": "Επιλογή",
|
||||
"selectMultiple": "Επιλογή πολλαπλών",
|
||||
"share": "Κοινοποίηση",
|
||||
"submit": "Υποβολή",
|
||||
"switchView": "Εναλλαγή προβολής",
|
||||
"toggleSidebar": "(Απ-)ενεργοποίησης της πλευρικής μπάρας",
|
||||
"update": "Ενημέρωση",
|
||||
"upload": "Μεταφόρτωση",
|
||||
"openFile": "Άνοιγμα αρχείου",
|
||||
"continue": "Συνέχεια"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Λήψη αρχείου",
|
||||
"downloadFolder": "Λήψη φακέλου",
|
||||
"downloadSelected": "Λήψη επιλεγμένων"
|
||||
},
|
||||
"upload": {
|
||||
"abortUpload": "Είστε σίγουροι ότι θέλετε να διακόψετε τη μεταφόρτωση;"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Δεν έχετε άδεια πρόσβασης σε αυτό.",
|
||||
"internal": "Προέκυψε εσωτερικό σφάλμα.",
|
||||
"notFound": "Αυτή η τοποθεσία δεν μπορεί να βρεθεί.",
|
||||
"connection": "Ο διακομιστής δεν είναι διαθέσιμος."
|
||||
},
|
||||
"files": {
|
||||
"body": "Περιεχόμενο",
|
||||
"clear": "Καθαρισμός",
|
||||
"closePreview": "Κλείσιμο προεπισκόπησης",
|
||||
"files": "Αρχεία",
|
||||
"folders": "Φάκελοι",
|
||||
"home": "Αρχική",
|
||||
"lastModified": "Τελευταία τροποποίηση",
|
||||
"loading": "Φορτώνει…",
|
||||
"lonely": "Δεν υπάρχει τίποτα εδώ (ακόμη)…",
|
||||
"metadata": "Μεταδεδομένα",
|
||||
"multipleSelectionEnabled": "Ενεργοποιημένη επιλογή πολλαπλών",
|
||||
"name": "Όνομα",
|
||||
"size": "Μέγεθος",
|
||||
"sortByLastModified": "Ταξινόμηση κατά πρόσφατη τροποποίηση",
|
||||
"sortByName": "Ταξινόμηση κατά όνομα",
|
||||
"sortBySize": "Ταξινόμηση κατά μέγεθος",
|
||||
"noPreview": "Η προεπισκόπηση δεν είναι διαθέσιμη για αυτό το αρχείο."
|
||||
},
|
||||
"help": {
|
||||
"click": "επιλέξτε αρχείο ή φάκελο",
|
||||
"ctrl": {
|
||||
"click": "επιλογή πολλαπλών αρχείων ή φακέλων",
|
||||
"f": "ανοίγει την αναζήτηση",
|
||||
"s": "αποθηκεύει ένα αρχείο ή εκκινεί λήψη του φακέλου στον οποίο βρίσκεστε"
|
||||
},
|
||||
"del": "διαγραφή επιλεγμένων στοιχείων",
|
||||
"doubleClick": "ανοίγει ένα αρχείο ή φάκελο",
|
||||
"esc": "καθαρίζει την επιλογή ή/και κλείνει το παράθυρο",
|
||||
"f1": "αυτή η πληροφορία",
|
||||
"f2": "μετονομασία αρχείου",
|
||||
"help": "Βοήθεια"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
"hu": "Magyar",
|
||||
"ar": "العربية",
|
||||
"de": "Deutsch",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"el": "Ελληνικά",
|
||||
"fr": "Français",
|
||||
"is": "Icelandic",
|
||||
"it": "Italiano",
|
||||
"ja": "日本語",
|
||||
"ko": "한국어",
|
||||
"nlBE": "Dutch (Belgium)",
|
||||
"pl": "Polski",
|
||||
"pt": "Português",
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "Romanian",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "Swedish (Sweden)",
|
||||
"tr": "Türkçe",
|
||||
"ua": "Українська",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
"login": {
|
||||
"createAnAccount": "Δημιουργία λογαριασμού",
|
||||
"loginInstead": "Έχετε ήδη λογαριασμό",
|
||||
"password": "Κωδικός πρόσβασης",
|
||||
"passwordConfirm": "Επιβεβαίωση κωδικού πρόσβασης",
|
||||
"passwordsDontMatch": "Οι κωδικοί πρόσβασης δεν ταιριάζουν",
|
||||
"signup": "Εγγραφή",
|
||||
"submit": "Είσοδος",
|
||||
"username": "Όνομα χρήστη",
|
||||
"usernameTaken": "Το όνομα χρήστη χρησιμοποιείται ήδη",
|
||||
"wrongCredentials": "Λάθος όνομα ή/και κωδικός πρόσβασης"
|
||||
},
|
||||
"permanent": "Μόνιμο",
|
||||
"prompts": {
|
||||
"copy": "Αντιγραφή",
|
||||
"copyMessage": "Επιλέξτε τοποθεσία για αντιγραφή των αρχείων σας:",
|
||||
"deleteMessageMultiple": "Είστε σίγουροι ότι θέλετε να διαγράψετε {count} αρχεία;",
|
||||
"deleteMessageSingle": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το αρχείο/φάκελο;",
|
||||
"deleteMessageShare": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτή την κοινοποίηση ({path});",
|
||||
"deleteTitle": "Διαγραφή αρχείων",
|
||||
"displayName": "Εμφάνιση ονόματος:",
|
||||
"download": "Λήψη αρχείων",
|
||||
"downloadMessage": "Επιλέξτε τη μορφή που θέλετε να λάβετε.",
|
||||
"error": "Προέκυψε κάποιο σφάλμα",
|
||||
"fileInfo": "Πληροφορίες αρχείου",
|
||||
"filesSelected": "Επιλέχθηκαν {count} αρχεία.",
|
||||
"lastModified": "Τελευταία τροποποίηση",
|
||||
"move": "Μετακίνηση",
|
||||
"moveMessage": "Επιλέξτε νέα τοποθεσία για τα αρχεία / τους φακέλους σας:",
|
||||
"newArchetype": "Δημιουργία νέας ανάρτησης με βάση έναν αρχέτυπο. Το αρχείο σας θα δημιουργηθεί στο φάκελο περιεχομένου.",
|
||||
"newDir": "Νέος φάκελος",
|
||||
"newDirMessage": "Γράψτε το όνομα του νέου φακέλου.",
|
||||
"newFile": "Νέο αρχείο",
|
||||
"newFileMessage": "Γράψτε το όνομα του νέου αρχείου.",
|
||||
"numberDirs": "Αριθμός φακέλων",
|
||||
"numberFiles": "Αριθμός αρχείων",
|
||||
"rename": "Μετονομασία",
|
||||
"renameMessage": "Εισαγάγετε ένα νέο όνομα για το",
|
||||
"replace": "Αντικατάσταση",
|
||||
"replaceMessage": "Ένα από τα αρχεία που προσπαθείτε να μεταφορτώσετε δημιουργεί σύγκρουση με υπάρχον αρχείο λόγω του ονόματός του. Θέλετε να συνεχίσετε τη μεταφόρτωση ή να αντικαταστήσετε το υπάρχον;\n",
|
||||
"schedule": "Προγραμματισμός",
|
||||
"scheduleMessage": "Επιλέξτε μια ημερομηνία και ώρα για τον προγραμματισμό της δημοσίευσης αυτής της ανάρτησης.",
|
||||
"show": "Εμφάνιση",
|
||||
"size": "Μέγεθος",
|
||||
"upload": "Μεταφόρτωση",
|
||||
"uploadFiles": "Μεταφόρτωση {files} αρχείων…",
|
||||
"uploadMessage": "Επιλέξτε μια επιλογή για τη μεταφόρτωση.",
|
||||
"optionalPassword": "Προαιρετικός κωδικός πρόσβασης"
|
||||
},
|
||||
"search": {
|
||||
"images": "Εικόνες",
|
||||
"music": "Μουσική",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Πατήστε Enter για αναζήτηση…",
|
||||
"search": "Αναζήτηση…",
|
||||
"typeToSearch": "Πληκτρολογήστε για αναζήτηση…",
|
||||
"types": "Τύποι",
|
||||
"video": "Βίντεο"
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Διαχειριστής",
|
||||
"administrator": "Διαχειριστής",
|
||||
"allowCommands": "Εκτέλεση εντολών",
|
||||
"allowEdit": "Επεξεργασία, μετονομασία και διαγραφή αρχείων ή φακέλων",
|
||||
"allowNew": "Δημιουργία νέων αρχείων και φακέλων",
|
||||
"allowPublish": "Δημοσίευση νέων αναρτήσεων και σελίδων",
|
||||
"allowSignup": "Να επιτρέπεται η εγγραφή νέων χρηστών",
|
||||
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
|
||||
"branding": "Εξατομίκευση",
|
||||
"brandingDirectoryPath": "Διαδρομή φακέλου εξατομίκευσης",
|
||||
"brandingHelp": "Μπορείτε να προσαρμόσετε την εμφάνισης της εφαρμογής File Browser αλλάζοντας το όνομά της, αντικαθιστώντας το λογότυπό της, προσθέτοντας προσαρμοσμένα στυλ και ακόμα και απενεργοποιώντας εξωτερικούς συνδέσμους προς το GitHub.\nΓια περισσότερες πληροφορίες σχετικά με αυτές τις προσαρμογές, ελέγξτε το {0}.",
|
||||
"changePassword": "Αλλαγή κωδικού πρόσβασης",
|
||||
"commandRunner": "Εκτέλεση εντολών",
|
||||
"commandRunnerHelp": "Εδώ μπορείτε να ορίσετε εντολές που εκτελούνται στα ονομασμένα γεγονότα και δραστηριότητες. Πρέπει να γράψετε μία εντολή ανά γραμμή. Οι μεταβλητές περιβάλλοντος {0} και {1} θα είναι διαθέσιμες, και θα είναι {0} σχετικές με το {1}. Για περισσότερες πληροφορίες σχετικά με αυτή τη λειτουργία και τις διαθέσιμες μεταβλητές περιβάλλοντος, παρακαλώ διαβάστε το {2}.",
|
||||
"commandsUpdated": "Οι εντολές ενημερώθηκαν!",
|
||||
"createUserDir": "Αυτόματη δημιουργία φακέλου χρήστη κατά την προσθήκη νέου χρήστη",
|
||||
"tusUploads": "Τμηματικές μεταφορές αρχείων",
|
||||
"tusUploadsHelp": "Η εφαρμογή File Browser υποστηρίζει τμηματικές μεταφορτώσεις αρχείων, επιτρέποντας την αποδοτική, αξιόπιστη και συνεχιζόμενη μεταφόρτωση αρχείων ακόμα και σε ασταθείς συνδέσεις δικτύου.",
|
||||
"tusUploadsChunkSize": "Υποδεικνύει το μέγιστο μέγεθος ενός αιτήματος μεταφόρτωσης (για μικρότερες μεταφορές αρχείων θα χρησιμοποιηθούν απευθείας και όχι τμηματικές μεταφορτώσεις). Μπορείτε να εισάγετε έναν ακέραιο αριθμό που υποδηλώνει το μέγεθος σε bytes, ή κείμενο με αριθμό και μονάδα μέτρησης μεγέθους δεδομένων, όπως 10MB, 1GB κλπ.",
|
||||
"tusUploadsRetryCount": "Αριθμός επαναληπτικών δοκιμών που θα πραγματοποιηθούν αν αποτύχει η μεταφόρτωση ενός τμήματος.",
|
||||
"userHomeBasePath": "Βασική διαδρομή αρχείων για τους φακέλους των χρηστών",
|
||||
"userScopeGenerationPlaceholder": "Η εμβέλεια εφαρμογής θα δημιουργηθεί αυτόματα",
|
||||
"createUserHomeDirectory": "Δημιουργία φακέλου χρήστη",
|
||||
"customStylesheet": "Προσαρμοσμένο στυλ εμφάνισης (stylesheet)",
|
||||
"defaultUserDescription": "Αυτές είναι οι προεπιλεγμένες ρυθμίσεις για νέους χρήστες.",
|
||||
"disableExternalLinks": "Απενεργοποίηση εξωτερικών συνδέσμων (εκτός από συνδέσμους προς τις οδηγίες χρήσης)",
|
||||
"disableUsedDiskPercentage": "Απενεργοποίηση γραφήματος ποσοστού χρήσης χώρου αποθήκευσης",
|
||||
"documentation": "οδηγίες χρήσης",
|
||||
"examples": "Παραδείγματα",
|
||||
"executeOnShell": "Εκτέλεση στο κέλυφος",
|
||||
"executeOnShellDescription": "Από προεπιλογή, η εφαρμογή File Browser εκτελεί τις εντολές καλώντας τα προγράμματα των εντολών απευθείας. Αν θέλετε να τις εκτελέσετε σε ένα κέλυφος (όπως το Bash ή το PowerShell), μπορείτε να το καθορίσετε εδώ με τις απαιτούμενες παραμέτρους. Εάν οριστεί, η εντολή που εκτελείτε θα προστίθεται ως παράμετρος. Αυτό ισχύει τόσο για τις εντολές χρήστη όσο και για τους αγκίστρους συμβάντων (event hooks).",
|
||||
"globalRules": "Πρόκειται για ένα γενικό σύνολο κανόνων που επιτρέπουν και απαγορεύουν διάφορες λειτουργίες και ισχύουν για κάθε χρήστη. Μπορείτε να καθορίσετε συγκεκριμένους κανόνες στις ρυθμίσεις κάθε χρήστη για να παρακάμψετε τους γενικούς κανόνες.",
|
||||
"globalSettings": "Γενικές ρυθμίσεις",
|
||||
"hideDotfiles": "Απόκρυψη κρυφών αρχείων (dotfiles)",
|
||||
"insertPath": "Εισάγετε διαδρομή",
|
||||
"insertRegex": "Εισάγετε έκφραση regex",
|
||||
"instanceName": "Όνομα περιβάλλοντος",
|
||||
"language": "Γλώσσα",
|
||||
"lockPassword": "Αποτρέψτε τον χρήστη από την αλλαγή του κωδικού πρόσβασης",
|
||||
"newPassword": "Νέος κωδικός πρόσβασης",
|
||||
"newPasswordConfirm": "Επιβεβαιώστε τον νέο κωδικό πρόσβασης",
|
||||
"newUser": "Νέος χρήστης",
|
||||
"password": "Κωδικός πρόσβασης",
|
||||
"passwordUpdated": "Ο κωδικός πρόσβασης ενημερώθηκε!",
|
||||
"path": "Διαδρομή",
|
||||
"perm": {
|
||||
"create": "Δημιουργία αρχείων και φακέλων",
|
||||
"delete": "Διαγραφή αρχείων και φακέλων",
|
||||
"download": "Λήψη",
|
||||
"execute": "Εκτέλεση εντολών",
|
||||
"modify": "Επεξεργασία αρχείων",
|
||||
"rename": "Μετονομασία ή μετακίνηση αρχείων και φακέλων",
|
||||
"share": "Κοινοποίηση αρχείων"
|
||||
},
|
||||
"permissions": "Δικαιώματα",
|
||||
"permissionsHelp": "Μπορείτε να ορίσετε τον χρήστη ως διαχειριστή ή να επιλέξετε τα δικαιώματα μεμονωμένα. Αν επιλέξετε \"Διαχειριστής\", όλες οι υπόλοιπες επιλογές θα είναι αυτόματα επιλεγμένες. Η διαχείριση χρηστών παραμένει προνόμιο ενός χρήστη με τον ρόλο του διαχειριστή.\n",
|
||||
"profileSettings": "Ρυθμίσεις προφίλ",
|
||||
"ruleExample1": "αποκλείει την πρόσβαση σε οποιοδήποτε κρυφό αρχείο (όπως .git, .gitignore) σε κάθε φάκελο.\n",
|
||||
"ruleExample2": "αποκλείει την πρόσβαση στο αρχείο με το όνομα Caddyfile στον ριζικό φάκελο της εμβέλειας του κανόνα.",
|
||||
"rules": "Κανόνες",
|
||||
"rulesHelp": "Εδώ μπορείτε να ορίσετε ένα σύνολο κανόνων που επιτρέπουν και απαγορεύουν διάφορες λειτουργίες για τον συγκεκριμένο χρήστη. Τα αποκλεισμένα αρχεία δεν θα εμφανίζονται στα περιεχόμενα των αντίστοιχων φακέλων και δεν θα είναι προσβάσιμα από τον χρήστη. Υποστηρίζονται εκφράσεις regex και διαδρομές σχετικές με την εμβέλεια αρχείων των χρηστών.\n",
|
||||
"scope": "Εμβέλεια",
|
||||
"setDateFormat": "Ορισμός ακριβούς μορφής ημερομηνίας",
|
||||
"settingsUpdated": "Οι ρυθμίσεις ενημερώθηκαν!",
|
||||
"shareDuration": "Διάρκεια κοινοποίησης",
|
||||
"shareManagement": "Διαχείριση κοινοποίησης",
|
||||
"shareDeleted": "Η κοινοποίηση διαγράφηκε!",
|
||||
"singleClick": "Χρήση μονού κλικ για να ανοίξετε αρχεία και φακέλους",
|
||||
"themes": {
|
||||
"dark": "Σκοτεινό",
|
||||
"light": "Φωτεινό",
|
||||
"title": "Μοτίβο"
|
||||
},
|
||||
"user": "Χρήστης",
|
||||
"userCommands": "Εντολές χρήστη",
|
||||
"userCommandsHelp": "Μια λίστα με τις διαθέσιμες εντολές για αυτόν το χρήστη, χωρισμένες μεταξύ τους με κενά. Παράδειγμα:\n",
|
||||
"userCreated": "Ο χρήστης δημιουργήθηκε!",
|
||||
"userDefaults": "Προεπιλεγμένες ρυθμίσεις χρήστη",
|
||||
"userDeleted": "Ο χρήστης διαγράφηκε!",
|
||||
"userManagement": "Διαχείριση χρηστών",
|
||||
"userUpdated": "Ο χρήστης ενημερώθηκε!",
|
||||
"username": "Όνομα χρήστη",
|
||||
"users": "Χρήστες"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Βοήθεια",
|
||||
"hugoNew": "Νέο Hugo",
|
||||
"login": "Σύνδεση",
|
||||
"logout": "Αποσύνδεση",
|
||||
"myFiles": "Τα αρχεία μου",
|
||||
"newFile": "Νέο αρχείο",
|
||||
"newFolder": "Νέος φάκελος",
|
||||
"preview": "Προεπισκόπηση",
|
||||
"settings": "Ρυθμίσεις",
|
||||
"signup": "Εγγραφή",
|
||||
"siteSettings": "Ρυθμίσεις ιστότοπου"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Ο σύνδεσμος αντιγράφηκε!"
|
||||
},
|
||||
"time": {
|
||||
"days": "Ημέρες",
|
||||
"hours": "Ώρες",
|
||||
"minutes": "Λεπτά",
|
||||
"seconds": "Δευτερόλεπτα",
|
||||
"unit": "Μονάδα χρόνου"
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,9 @@
|
||||
"downloadFolder": "Download Folder",
|
||||
"downloadSelected": "Download Selected"
|
||||
},
|
||||
"upload": {
|
||||
"abortUpload": "Are you sure you want to abort?"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "You don't have permissions to access this.",
|
||||
"internal": "Something really went wrong.",
|
||||
@@ -89,6 +92,7 @@
|
||||
"hu": "Magyar",
|
||||
"ar": "العربية",
|
||||
"de": "Deutsch",
|
||||
"el": "Ελληνικά",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
@@ -194,14 +198,14 @@
|
||||
"userScopeGenerationPlaceholder": "The scope will be auto generated",
|
||||
"createUserHomeDirectory": "Create user home directory",
|
||||
"customStylesheet": "Custom Stylesheet",
|
||||
"defaultUserDescription": "This are the default settings for new users.",
|
||||
"defaultUserDescription": "These are the default settings for new users.",
|
||||
"disableExternalLinks": "Disable external links (except documentation)",
|
||||
"disableUsedDiskPercentage": "Disable used disk percentage graph",
|
||||
"documentation": "documentation",
|
||||
"examples": "Examples",
|
||||
"executeOnShell": "Execute on shell",
|
||||
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
|
||||
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
|
||||
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override these ones.",
|
||||
"globalSettings": "Global Settings",
|
||||
"hideDotfiles": "Hide dotfiles",
|
||||
"insertPath": "Insert the path",
|
||||
|
||||
@@ -5,6 +5,7 @@ import he from "./he.json";
|
||||
import hu from "./hu.json";
|
||||
import ar from "./ar.json";
|
||||
import de from "./de.json";
|
||||
import el from "./el.json";
|
||||
import en from "./en.json";
|
||||
import es from "./es.json";
|
||||
import fr from "./fr.json";
|
||||
@@ -38,6 +39,9 @@ export function detectLocale() {
|
||||
case /^ar.*/i.test(locale):
|
||||
locale = "ar";
|
||||
break;
|
||||
case /^el.*/i.test(locale):
|
||||
locale = "el";
|
||||
break;
|
||||
case /^es.*/i.test(locale):
|
||||
locale = "es";
|
||||
break;
|
||||
@@ -114,6 +118,7 @@ const i18n = new VueI18n({
|
||||
hu: removeEmpty(hu),
|
||||
ar: removeEmpty(ar),
|
||||
de: removeEmpty(de),
|
||||
el: removeEmpty(el),
|
||||
en: en,
|
||||
es: removeEmpty(es),
|
||||
fr: removeEmpty(fr),
|
||||
|
||||
@@ -7,7 +7,7 @@ import i18n from "@/i18n";
|
||||
import Vue from "@/utils/vue";
|
||||
import { recaptcha, loginPage } from "@/utils/constants";
|
||||
import { login, validateLogin } from "@/utils/auth";
|
||||
import App from "@/App";
|
||||
import App from "@/App.vue";
|
||||
|
||||
cssVars();
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import Vue from "vue";
|
||||
import Router from "vue-router";
|
||||
import Login from "@/views/Login";
|
||||
import Layout from "@/views/Layout";
|
||||
import Files from "@/views/Files";
|
||||
import Share from "@/views/Share";
|
||||
import Users from "@/views/settings/Users";
|
||||
import User from "@/views/settings/User";
|
||||
import Settings from "@/views/Settings";
|
||||
import GlobalSettings from "@/views/settings/Global";
|
||||
import ProfileSettings from "@/views/settings/Profile";
|
||||
import Shares from "@/views/settings/Shares";
|
||||
import Errors from "@/views/Errors";
|
||||
import Login from "@/views/Login.vue";
|
||||
import Layout from "@/views/Layout.vue";
|
||||
import Files from "@/views/Files.vue";
|
||||
import Share from "@/views/Share.vue";
|
||||
import Users from "@/views/settings/Users.vue";
|
||||
import User from "@/views/settings/User.vue";
|
||||
import Settings from "@/views/Settings.vue";
|
||||
import GlobalSettings from "@/views/settings/Global.vue";
|
||||
import ProfileSettings from "@/views/settings/Profile.vue";
|
||||
import Shares from "@/views/settings/Shares.vue";
|
||||
import Errors from "@/views/Errors.vue";
|
||||
import store from "@/store";
|
||||
import { baseURL, name } from "@/utils/constants";
|
||||
import i18n, { rtlLanguages } from "@/i18n";
|
||||
@@ -33,7 +33,7 @@ const titles = {
|
||||
};
|
||||
|
||||
const router = new Router({
|
||||
base: baseURL,
|
||||
base: import.meta.env.PROD ? baseURL : "",
|
||||
mode: "history",
|
||||
routes: [
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ const getters = {
|
||||
isListing: (state, getters) => getters.isFiles && state.req.isDir,
|
||||
selectedCount: (state) => state.selected.length,
|
||||
progress: (state) => {
|
||||
if (state.upload.progress.length == 0) {
|
||||
if (state.upload.progress.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,7 @@ const getters = {
|
||||
return Math.ceil((sum / totalSize) * 100);
|
||||
},
|
||||
filesInUploadCount: (state) => {
|
||||
let total =
|
||||
Object.keys(state.upload.uploads).length + state.upload.queue.length;
|
||||
return total;
|
||||
return Object.keys(state.upload.uploads).length + state.upload.queue.length;
|
||||
},
|
||||
filesInUpload: (state) => {
|
||||
let files = [];
|
||||
@@ -43,6 +41,16 @@ const getters = {
|
||||
|
||||
return files.sort((a, b) => a.progress - b.progress);
|
||||
},
|
||||
currentPrompt: (state) => {
|
||||
return state.prompts.length > 0
|
||||
? state.prompts[state.prompts.length - 1]
|
||||
: null;
|
||||
},
|
||||
currentPromptName: (_, getters) => {
|
||||
return getters.currentPrompt?.prompt;
|
||||
},
|
||||
uploadSpeed: (state) => state.upload.speedMbyte,
|
||||
eta: (state) => state.upload.eta,
|
||||
};
|
||||
|
||||
export default getters;
|
||||
|
||||
@@ -20,10 +20,8 @@ const state = {
|
||||
reload: false,
|
||||
selected: [],
|
||||
multiple: false,
|
||||
show: null,
|
||||
prompts: [],
|
||||
showShell: false,
|
||||
showConfirm: null,
|
||||
showAction: null,
|
||||
};
|
||||
|
||||
export default new Vuex.Store({
|
||||
|
||||
@@ -11,6 +11,8 @@ const state = {
|
||||
progress: [],
|
||||
queue: [],
|
||||
uploads: {},
|
||||
speedMbyte: 0,
|
||||
eta: 0,
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
|
||||
@@ -3,30 +3,35 @@ import moment from "moment";
|
||||
|
||||
const mutations = {
|
||||
closeHovers: (state) => {
|
||||
state.show = null;
|
||||
state.showConfirm = null;
|
||||
state.showAction = null;
|
||||
state.prompts.pop();
|
||||
},
|
||||
toggleShell: (state) => {
|
||||
state.show = null;
|
||||
state.showShell = !state.showShell;
|
||||
},
|
||||
showHover: (state, value) => {
|
||||
if (typeof value !== "object") {
|
||||
state.show = value;
|
||||
state.prompts.push({
|
||||
prompt: value,
|
||||
confirm: null,
|
||||
action: null,
|
||||
props: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
state.show = value.prompt;
|
||||
state.showConfirm = value.confirm;
|
||||
if (value.action !== undefined) {
|
||||
state.showAction = value.action;
|
||||
}
|
||||
state.prompts.push({
|
||||
prompt: value.prompt, // Should not be null
|
||||
confirm: value?.confirm,
|
||||
action: value?.action,
|
||||
props: value?.props,
|
||||
});
|
||||
},
|
||||
showError: (state) => {
|
||||
state.show = "error";
|
||||
state.prompts.push("error");
|
||||
},
|
||||
showSuccess: (state) => {
|
||||
state.show = "success";
|
||||
state.prompts.push("success");
|
||||
},
|
||||
setLoading: (state, value) => {
|
||||
state.loading = value;
|
||||
@@ -74,8 +79,15 @@ const mutations = {
|
||||
}
|
||||
},
|
||||
updateRequest: (state, value) => {
|
||||
const selectedItems = state.selected.map((i) => state.req.items[i]);
|
||||
state.oldReq = state.req;
|
||||
state.req = value;
|
||||
state.selected = [];
|
||||
|
||||
if (!state.req?.items) return;
|
||||
state.selected = state.req.items
|
||||
.filter((item) => selectedItems.some((rItem) => rItem.url === item.url))
|
||||
.map((item) => item.index);
|
||||
},
|
||||
updateClipboard: (state, value) => {
|
||||
state.clipboard.key = value.key;
|
||||
@@ -86,6 +98,21 @@ const mutations = {
|
||||
state.clipboard.key = "";
|
||||
state.clipboard.items = [];
|
||||
},
|
||||
setUploadSpeed: (state, value) => {
|
||||
state.upload.speedMbyte = value;
|
||||
},
|
||||
setETA(state, value) {
|
||||
state.upload.eta = value;
|
||||
},
|
||||
resetUpload(state) {
|
||||
state.upload.uploads = {};
|
||||
state.upload.queue = [];
|
||||
state.upload.progress = [];
|
||||
state.upload.sizes = [];
|
||||
state.upload.id = 0;
|
||||
state.upload.speedMbyte = 0;
|
||||
state.upload.eta = 0;
|
||||
},
|
||||
};
|
||||
|
||||
export default mutations;
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function validateLogin() {
|
||||
await renew(localStorage.getItem("jwt"));
|
||||
}
|
||||
} catch (_) {
|
||||
console.warn('Invalid JWT token in storage') // eslint-disable-line
|
||||
console.warn("Invalid JWT token in storage"); // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
6
frontend/src/utils/index.js
Normal file
6
frontend/src/utils/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { partial } from "filesize";
|
||||
|
||||
/**
|
||||
* Formats filesize as KiB/MiB/...
|
||||
*/
|
||||
export const filesize = partial({ base: 2 });
|
||||
@@ -10,7 +10,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||
|
||||
const errors = {
|
||||
0: {
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
import { files as api } from "@/api";
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs";
|
||||
import Errors from "@/views/Errors";
|
||||
import Preview from "@/views/files/Preview";
|
||||
import Listing from "@/views/files/Listing";
|
||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import Errors from "@/views/Errors.vue";
|
||||
import Preview from "@/views/files/Preview.vue";
|
||||
import Listing from "@/views/files/Listing.vue";
|
||||
|
||||
function clean(path) {
|
||||
return path.endsWith("/") ? path.slice(0, -1) : path;
|
||||
@@ -41,7 +41,7 @@ export default {
|
||||
Errors,
|
||||
Preview,
|
||||
Listing,
|
||||
Editor: () => import("@/views/files/Editor"),
|
||||
Editor: () => import("@/views/files/Editor.vue"),
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
@@ -50,7 +50,7 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "reload", "loading", "show"]),
|
||||
...mapState(["req", "reload", "loading"]),
|
||||
currentView() {
|
||||
if (this.req.type == undefined) {
|
||||
return null;
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import Prompts from "@/components/prompts/Prompts";
|
||||
import Shell from "@/components/Shell";
|
||||
import UploadFiles from "../components/prompts/UploadFiles";
|
||||
import Sidebar from "@/components/Sidebar.vue";
|
||||
import Prompts from "@/components/prompts/Prompts.vue";
|
||||
import Shell from "@/components/Shell.vue";
|
||||
import UploadFiles from "../components/prompts/UploadFiles.vue";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
@@ -30,7 +30,7 @@ export default {
|
||||
UploadFiles,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["isLogged", "progress"]),
|
||||
...mapGetters(["isLogged", "progress", "currentPrompt"]),
|
||||
...mapState(["user"]),
|
||||
isExecEnabled: () => enableExec,
|
||||
},
|
||||
@@ -38,7 +38,7 @@ export default {
|
||||
$route: function () {
|
||||
this.$store.commit("resetSelected");
|
||||
this.$store.commit("multiple", false);
|
||||
if (this.$store.state.show !== "success")
|
||||
if (this.currentPrompt?.prompt !== "success")
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||
|
||||
export default {
|
||||
name: "settings",
|
||||
|
||||
@@ -182,15 +182,15 @@
|
||||
<script>
|
||||
import { mapState, mapMutations, mapGetters } from "vuex";
|
||||
import { pub as api } from "@/api";
|
||||
import filesize from "filesize";
|
||||
import { filesize } from "@/utils";
|
||||
import moment from "moment";
|
||||
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Action from "@/components/header/Action";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs";
|
||||
import Errors from "@/views/Errors";
|
||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import Errors from "@/views/Errors.vue";
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
import Item from "@/components/files/ListingItem";
|
||||
import Item from "@/components/files/ListingItem.vue";
|
||||
import Clipboard from "clipboard";
|
||||
|
||||
export default {
|
||||
|
||||
@@ -26,13 +26,13 @@ import { theme } from "@/utils/constants";
|
||||
import buttons from "@/utils/buttons";
|
||||
import url from "@/utils/url";
|
||||
|
||||
import { version as ace_version } from "ace-builds";
|
||||
import ace from "ace-builds/src-min-noconflict/ace.js";
|
||||
import modelist from "ace-builds/src-min-noconflict/ext-modelist.js";
|
||||
import "ace-builds/webpack-resolver";
|
||||
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Action from "@/components/header/Action";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs";
|
||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
export default {
|
||||
name: "editor",
|
||||
@@ -86,6 +86,11 @@ export default {
|
||||
mounted: function () {
|
||||
const fileContent = this.req.content || "";
|
||||
|
||||
ace.config.set(
|
||||
"basePath",
|
||||
`https://cdn.jsdelivr.net/npm/ace-builds@${ace_version}/src-min-noconflict/`
|
||||
);
|
||||
|
||||
this.editor = ace.edit("editor", {
|
||||
value: fileContent,
|
||||
showPrintMargin: false,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<header-bar showMenu showLogo>
|
||||
<search /> <title />
|
||||
<search />
|
||||
<title />
|
||||
<action
|
||||
class="search-button"
|
||||
icon="search"
|
||||
@@ -274,10 +275,10 @@ import * as upload from "@/utils/upload";
|
||||
import css from "@/utils/css";
|
||||
import throttle from "lodash.throttle";
|
||||
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Action from "@/components/header/Action";
|
||||
import Search from "@/components/Search";
|
||||
import Item from "@/components/files/ListingItem";
|
||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
import Search from "@/components/Search.vue";
|
||||
import Item from "@/components/files/ListingItem.vue";
|
||||
|
||||
export default {
|
||||
name: "listing",
|
||||
@@ -297,16 +298,8 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
"req",
|
||||
"selected",
|
||||
"user",
|
||||
"show",
|
||||
"multiple",
|
||||
"selected",
|
||||
"loading",
|
||||
]),
|
||||
...mapGetters(["selectedCount"]),
|
||||
...mapState(["req", "selected", "user", "multiple", "selected", "loading"]),
|
||||
...mapGetters(["selectedCount", "currentPrompt"]),
|
||||
nameSorted() {
|
||||
return this.req.sorting.by === "name";
|
||||
},
|
||||
@@ -443,7 +436,7 @@ export default {
|
||||
},
|
||||
keyEvent(event) {
|
||||
// No prompts are shown
|
||||
if (this.show !== null) {
|
||||
if (this.currentPrompt !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -143,14 +143,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { mapGetters, mapState } from "vuex";
|
||||
import { files as api } from "@/api";
|
||||
import { resizePreview } from "@/utils/constants";
|
||||
import url from "@/utils/url";
|
||||
import throttle from "lodash.throttle";
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Action from "@/components/header/Action";
|
||||
import ExtendedImage from "@/components/files/ExtendedImage";
|
||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
import ExtendedImage from "@/components/files/ExtendedImage.vue";
|
||||
|
||||
const mediaTypes = ["image", "video", "audio", "blob"];
|
||||
|
||||
@@ -177,7 +177,8 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "user", "oldReq", "jwt", "loading", "show"]),
|
||||
...mapState(["req", "user", "oldReq", "jwt", "loading"]),
|
||||
...mapGetters(["currentPrompt"]),
|
||||
hasPrevious() {
|
||||
return this.previousLink !== "";
|
||||
},
|
||||
@@ -195,7 +196,7 @@ export default {
|
||||
return api.getDownloadURL(this.req, true);
|
||||
},
|
||||
showMore() {
|
||||
return this.$store.state.show === "more";
|
||||
return this.currentPrompt?.prompt === "more";
|
||||
},
|
||||
isResizeEnabled() {
|
||||
return resizePreview;
|
||||
@@ -247,7 +248,7 @@ export default {
|
||||
this.$router.replace({ path: this.nextLink });
|
||||
},
|
||||
key(event) {
|
||||
if (this.show !== null) {
|
||||
if (this.currentPrompt !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -223,10 +223,10 @@
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
import { settings as api } from "@/api";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
import UserForm from "@/components/settings/UserForm";
|
||||
import Rules from "@/components/settings/Rules";
|
||||
import Themes from "@/components/settings/Themes";
|
||||
import Errors from "@/views/Errors";
|
||||
import UserForm from "@/components/settings/UserForm.vue";
|
||||
import Rules from "@/components/settings/Rules.vue";
|
||||
import Themes from "@/components/settings/Themes.vue";
|
||||
import Errors from "@/views/Errors.vue";
|
||||
|
||||
export default {
|
||||
name: "settings",
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<script>
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
import { users as api } from "@/api";
|
||||
import Languages from "@/components/settings/Languages";
|
||||
import Languages from "@/components/settings/Languages.vue";
|
||||
import i18n, { rtlLanguages } from "@/i18n";
|
||||
|
||||
export default {
|
||||
|
||||
@@ -65,7 +65,7 @@ import { share as api, users } from "@/api";
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
import moment from "moment";
|
||||
import Clipboard from "clipboard";
|
||||
import Errors from "@/views/Errors";
|
||||
import Errors from "@/views/Errors.vue";
|
||||
|
||||
export default {
|
||||
name: "shares",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-if="$store.state.show === 'deleteUser'" class="card floating">
|
||||
<div v-if="this.currentPromptName === 'deleteUser'" class="card floating">
|
||||
<div class="card-content">
|
||||
<p>Are you sure you want to delete this user?</p>
|
||||
</div>
|
||||
@@ -61,10 +61,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
import { mapState, mapMutations, mapGetters } from "vuex";
|
||||
import { users as api, settings } from "@/api";
|
||||
import UserForm from "@/components/settings/UserForm";
|
||||
import Errors from "@/views/Errors";
|
||||
import UserForm from "@/components/settings/UserForm.vue";
|
||||
import Errors from "@/views/Errors.vue";
|
||||
import deepClone from "lodash.clonedeep";
|
||||
|
||||
export default {
|
||||
@@ -89,6 +89,7 @@ export default {
|
||||
return this.$route.path === "/settings/users/new";
|
||||
},
|
||||
...mapState(["loading"]),
|
||||
...mapGetters(["currentPrompt", "currentPromptName"]),
|
||||
},
|
||||
watch: {
|
||||
$route: "fetchData",
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<script>
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
import { users as api } from "@/api";
|
||||
import Errors from "@/views/Errors";
|
||||
import Errors from "@/views/Errors.vue";
|
||||
|
||||
export default {
|
||||
name: "users",
|
||||
|
||||
70
frontend/vite.config.js
Normal file
70
frontend/vite.config.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
import path from "node:path";
|
||||
import { defineConfig } from "vite";
|
||||
import legacy from "@vitejs/plugin-legacy";
|
||||
import vue2 from "@vitejs/plugin-vue2";
|
||||
import { compression } from "vite-plugin-compression2";
|
||||
import pluginRewriteAll from "vite-plugin-rewrite-all";
|
||||
|
||||
const plugins = [
|
||||
vue2(),
|
||||
legacy({
|
||||
targets: ["ie >= 11"],
|
||||
additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
|
||||
}),
|
||||
compression({ include: /\.js$/i, deleteOriginalAssets: true }),
|
||||
pluginRewriteAll(), // fixes 404 error with paths containing dot in dev server
|
||||
];
|
||||
|
||||
const resolve = {
|
||||
alias: {
|
||||
vue: "vue/dist/vue.esm.js",
|
||||
"@/": `${path.resolve(__dirname, "src")}/`,
|
||||
},
|
||||
};
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ command }) => {
|
||||
if (command === "serve") {
|
||||
return {
|
||||
plugins,
|
||||
resolve,
|
||||
server: {
|
||||
proxy: {
|
||||
"/api/command": {
|
||||
target: "ws://127.0.0.1:8080",
|
||||
ws: true,
|
||||
},
|
||||
"/api": "http://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// command === 'build'
|
||||
return {
|
||||
plugins,
|
||||
resolve,
|
||||
base: "",
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: fileURLToPath(
|
||||
new URL(`./public/index.html`, import.meta.url)
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
experimental: {
|
||||
renderBuiltUrl(filename, { hostType }) {
|
||||
if (hostType === "js") {
|
||||
return { runtime: `window.__prependStaticUrl("${filename}")` };
|
||||
} else if (hostType === "html") {
|
||||
return `[{[ .StaticURL ]}]/${filename}`;
|
||||
} else {
|
||||
return { relative: true };
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
runtimeCompiler: true,
|
||||
publicPath: "[{[ .StaticURL ]}]",
|
||||
parallel: 2,
|
||||
configureWebpack: {
|
||||
plugins: [
|
||||
new CompressionPlugin({
|
||||
include: /\.js$/,
|
||||
deleteOriginalAssets: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
10
go.mod
10
go.mod
@@ -23,9 +23,9 @@ require (
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/image v0.5.0
|
||||
golang.org/x/text v0.10.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/image v0.10.0
|
||||
golang.org/x/text v0.13.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
@@ -59,8 +59,8 @@ require (
|
||||
github.com/ulikunitz/xz v0.5.9 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/net v0.11.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
|
||||
27
go.sum
27
go.sum
@@ -292,8 +292,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -307,8 +307,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M=
|
||||
golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -331,6 +331,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -365,8 +366,9 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -387,6 +389,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -428,10 +431,12 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -440,8 +445,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -493,6 +499,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
41
http/auth.go
41
http/auth.go
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TokenExpirationTime = time.Hour * 2
|
||||
DefaultTokenExpirationTime = time.Hour * 2
|
||||
)
|
||||
|
||||
type userInfo struct {
|
||||
@@ -101,19 +101,21 @@ func withAdmin(fn handleFunc) handleFunc {
|
||||
})
|
||||
}
|
||||
|
||||
var loginHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
auther, err := d.store.Auth.Get(d.settings.AuthMethod)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
func loginHandler(tokenExpireTime time.Duration) handleFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
auther, err := d.store.Auth.Get(d.settings.AuthMethod)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
user, err := auther.Auth(r, d.store.Users, d.settings, d.server)
|
||||
if err == os.ErrPermission {
|
||||
return http.StatusForbidden, nil
|
||||
} else if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
} else {
|
||||
return printToken(w, r, d, user)
|
||||
user, err := auther.Auth(r, d.store.Users, d.settings, d.server)
|
||||
if err == os.ErrPermission {
|
||||
return http.StatusForbidden, nil
|
||||
} else if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
} else {
|
||||
return printToken(w, r, d, user, tokenExpireTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,11 +174,14 @@ var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int,
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
var renewHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
return printToken(w, r, d, d.user)
|
||||
})
|
||||
func renewHandler(tokenExpireTime time.Duration) handleFunc {
|
||||
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
w.Header().Set("X-Renew-Token", "false")
|
||||
return printToken(w, r, d, d.user, tokenExpireTime)
|
||||
})
|
||||
}
|
||||
|
||||
func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User) (int, error) {
|
||||
func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User, tokenExpirationTime time.Duration) (int, error) {
|
||||
claims := &authToken{
|
||||
User: userInfo{
|
||||
ID: user.ID,
|
||||
@@ -191,7 +196,7 @@ func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.Use
|
||||
},
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpirationTime)),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(tokenExpirationTime)),
|
||||
Issuer: "File Browser",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -49,7 +49,9 @@ func (d *data) Check(path string) bool {
|
||||
|
||||
func handle(fn handleFunc, prefix string, store *storage.Storage, server *settings.Server) http.Handler {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
for k, v := range globalHeaders {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
|
||||
settings, err := store.Settings.Get()
|
||||
if err != nil {
|
||||
|
||||
9
http/headers.go
Normal file
9
http/headers.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !dev
|
||||
// +build !dev
|
||||
|
||||
package http
|
||||
|
||||
// global headers to append to every response
|
||||
var globalHeaders = map[string]string{
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
}
|
||||
15
http/headers_dev.go
Normal file
15
http/headers_dev.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build dev
|
||||
// +build dev
|
||||
|
||||
package http
|
||||
|
||||
// global headers to append to every response
|
||||
// cross-origin headers are necessary to be able to
|
||||
// access them from a different URL during development
|
||||
var globalHeaders = map[string]string{
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Allow-Methods": "*",
|
||||
"Access-Control-Allow-Credentials": "true",
|
||||
}
|
||||
@@ -48,9 +48,10 @@ func NewHandler(
|
||||
|
||||
api := r.PathPrefix("/api").Subrouter()
|
||||
|
||||
api.Handle("/login", monkey(loginHandler, ""))
|
||||
tokenExpirationTime := server.GetTokenExpirationTime(DefaultTokenExpirationTime)
|
||||
api.Handle("/login", monkey(loginHandler(tokenExpirationTime), ""))
|
||||
api.Handle("/signup", monkey(signupHandler, ""))
|
||||
api.Handle("/renew", monkey(renewHandler, ""))
|
||||
api.Handle("/renew", monkey(renewHandler(tokenExpirationTime), ""))
|
||||
|
||||
users := api.PathPrefix("/users").Subrouter()
|
||||
users.Handle("", monkey(usersGetHandler, "")).Methods("GET")
|
||||
@@ -66,7 +67,7 @@ func NewHandler(
|
||||
api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler(fileCache), "/api/resources")).Methods("PATCH")
|
||||
|
||||
api.PathPrefix("/tus").Handler(monkey(tusPostHandler(), "/api/tus")).Methods("POST")
|
||||
api.PathPrefix("/tus").Handler(monkey(tusHeadHandler(), "/api/tus")).Methods("HEAD")
|
||||
api.PathPrefix("/tus").Handler(monkey(tusHeadHandler(), "/api/tus")).Methods("HEAD", "GET")
|
||||
api.PathPrefix("/tus").Handler(monkey(tusPatchHandler(), "/api/tus")).Methods("PATCH")
|
||||
|
||||
api.PathPrefix("/usage").Handler(monkey(diskUsage, "/api/usage")).Methods("GET")
|
||||
|
||||
@@ -105,7 +105,7 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
|
||||
}
|
||||
|
||||
w.Header().Set("x-xss-protection", "1; mode=block")
|
||||
return handleWithStaticData(w, r, d, assetsFs, "index.html", "text/html; charset=utf-8")
|
||||
return handleWithStaticData(w, r, d, assetsFs, "public/index.html", "text/html; charset=utf-8")
|
||||
}, "", store, server)
|
||||
|
||||
static = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
@@ -113,6 +113,10 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
if strings.HasSuffix(r.URL.Path, "/") {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
const maxAge = 86400 // 1 day
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%v", maxAge))
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ package settings
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/rules"
|
||||
)
|
||||
@@ -47,6 +49,7 @@ type Server struct {
|
||||
EnableExec bool `json:"enableExec"`
|
||||
TypeDetectionByHeader bool `json:"typeDetectionByHeader"`
|
||||
AuthHook string `json:"authHook"`
|
||||
TokenExpirationTime string `json:"tokenExpirationTime"`
|
||||
}
|
||||
|
||||
// Clean cleans any variables that might need cleaning.
|
||||
@@ -54,6 +57,19 @@ func (s *Server) Clean() {
|
||||
s.BaseURL = strings.TrimSuffix(s.BaseURL, "/")
|
||||
}
|
||||
|
||||
func (s *Server) GetTokenExpirationTime(fallback time.Duration) time.Duration {
|
||||
if s.TokenExpirationTime == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
if duration, err := time.ParseDuration(s.TokenExpirationTime); err == nil {
|
||||
return duration
|
||||
} else {
|
||||
log.Printf("[WARN] Failed to parse tokenExpirationTime: %v", err)
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateKey generates a key of 512 bits.
|
||||
func GenerateKey() ([]byte, error) {
|
||||
b := make([]byte, 64) //nolint:gomnd
|
||||
|
||||
Reference in New Issue
Block a user