Compare commits

..

44 Commits

Author SHA1 Message Date
Oleg Lobanov
04e03a83b4 chore(release): 2.27.0 2024-01-02 18:29:29 +04:00
Yogesh
c4e955acf4 fix: typo in build error #2903 (#2904) 2023-12-31 14:32:54 +01:00
People-11
fc04578e28 Update zh-cn.json (#2875) 2023-12-28 10:30:52 +01:00
Shlomo
3264cea830 fix: delete message when delete file from preview 2023-12-28 10:11:53 +01:00
Yarden Shoham
748af7172c feat: allow setting theme via cli (#2881) 2023-12-28 09:52:40 +01:00
Shlomo
da595326ee chore: update he.json (#2877) 2023-12-28 09:44:58 +01:00
dependabot[bot]
821fba41a2 build(deps): bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#2890)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-28 09:44:24 +01:00
Omar Hussein
cfafefa35a chore: update Arabic and English translations (#2823) 2023-12-06 11:21:04 +01:00
Fritz Lin
391a078cd4 feat: make user session timeout configurable by flags (#2845) 2023-12-06 11:19:30 +01:00
dependabot[bot]
fc2ee37353 build(deps-dev): bump vite from 4.4.9 to 4.4.12 in /frontend (#2862)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.9 to 4.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 11:09:33 +01:00
M A E R Y O
a09dfa8d9f feat: display image resolutions in file details (#2830)
---------

Co-authored-by: MAERYO <maeryo@hanwha.com>
2023-11-25 13:35:00 +01:00
ねらひかだ
4dbc802972 fix: fix typo (#2843) 2023-11-25 13:29:43 +01:00
Emmanuel Frecon
d59ad594b8 fix: set correct port in docker healthcheck (#2812)
When the `FB_PORT` environment variable is set, respect its value. Otherwise, pick the port from the configuration.
2023-11-08 17:59:46 +01:00
Oleg Lobanov
a4cb813ddf chore(release): 2.26.0 2023-11-02 22:49:15 +01:00
Oleg Lobanov
4d0a68e787 fix: goreleaser yaml 2023-11-02 22:48:29 +01:00
dependabot[bot]
a744bd224f build(deps): bump golang.org/x/image from 0.5.0 to 0.10.0 (#2800)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.5.0 to 0.10.0.
- [Commits](https://github.com/golang/image/compare/v0.5.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 22:36:32 +01:00
Oleg Lobanov
da1fe7c9d7 fix: disable static resource files listing 2023-11-02 22:23:20 +01:00
Dardan
7fabadc871 feat: make user session timeout configurable (#2753)
Co-authored-by: Oleg Lobanov <oleg@lobanov.me>
2023-11-02 22:01:56 +01:00
Isaak Tsalicoglou
c3079d30e2 feat: add modern greek translation (#2778)
---------

Co-authored-by: Oleg Lobanov <oleg@lobanov.me>
2023-11-02 21:55:22 +01:00
shonge
6a31af6c0a fix: solve docker build failed issue (#2797) 2023-11-02 21:38:00 +01:00
dependabot[bot]
21d361ad30 build(deps-dev): bump postcss from 8.4.27 to 8.4.31 in /frontend (#2749)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 09:09:01 +01:00
dependabot[bot]
d574fb6d1a build(deps): bump golang.org/x/net from 0.11.0 to 0.17.0 (#2758)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.11.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.11.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 09:03:48 +01:00
dependabot[bot]
bb4bb508a9 build(deps): bump @babel/traverse in /frontend (#2775)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.10 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 09:03:28 +01:00
bufubaoni
edd808f124 fix: avoid the front-end calling api/renew loop (#2792) 2023-11-02 09:02:51 +01:00
kloon15
cdcd9a313a fix: display file size as base 2 (KiB instead of KB) (#2779) 2023-11-02 08:47:55 +01:00
Isaak Tsalicoglou
d0c3aeace1 chore: update en translation (#2777) 2023-11-02 08:43:50 +01:00
Isaak Tsalicoglou
9484454584 chore: update en translation (#2776) 2023-11-02 08:43:32 +01:00
kloon15
bd3c1941ff fix: revert fetchURL changes in auth (Fixes #2729) (#2739) 2023-10-01 18:09:13 +02:00
Oleg Lobanov
01f7842a18 docs: add demo url to README 2023-09-15 01:11:47 +02:00
Oleg Lobanov
38f7788255 build: fix deprecated goreleaser config options 2023-09-15 00:59:16 +02:00
Oleg Lobanov
c1fb4004f7 chore(release): 2.25.0 2023-09-15 00:51:58 +02:00
Cameron
584b706b1e feat: added shell resizing (#2648) 2023-09-15 00:50:40 +02:00
M A E R Y O
ecdd684bf1 feat: implement upload speed calculation and ETA estimation (#2677) 2023-09-15 00:41:36 +02:00
Oleg Lobanov
36af01daa6 fix: tus upload with cloudflare proxy
closes #2593
2023-09-15 00:39:37 +02:00
Thomas
d0c3b8033d chore: update German translation (#2616) 2023-09-10 14:35:51 +02:00
kloon15
aa00c1c89c chore: fixes for vite dev server (#2678) 2023-08-29 20:14:45 +02:00
M A E R Y O
a404fb043d feat: implement abort upload functionality (#2673) 2023-08-27 23:59:49 +02:00
M A E R Y O
95fec7f694 fix: refactor path resolution logic for project root (#2674)
---------

Co-authored-by: MAERYO <maeryo@hanwha.com>
2023-08-27 23:52:33 +02:00
ArthurMousatov
5994224468 feat: add new folder button to move/create dialogs (#2667)
---------

Co-authored-by: Oleg Lobanov <oleg@lobanov.me>
2023-08-27 00:28:58 +02:00
古大羊
374bbd3ec1 perf(backend): optimize subtitles detection performance (#2637) 2023-08-26 14:53:18 +02:00
Oleg Lobanov
2c97573301 build: bump go version to 1.21.0 (#2672) 2023-08-26 14:16:10 +02:00
Oleg Lobanov
70eba7ecc9 build: bump node version to 18 (#2671) 2023-08-26 14:10:07 +02:00
Oleg Lobanov
7a4d0c0c08 chore: fix frontend dev proxy settings 2023-08-26 14:01:01 +02:00
kloon15
8838a09cf5 refactor: migrate frontend tooling to vite 4 (#2645)
---------

Co-authored-by: Oleg Lobanov <oleg@lobanov.me>
2023-08-26 13:55:51 +02:00
91 changed files with 5645 additions and 26463 deletions

View File

@@ -1,4 +1,5 @@
*
!docker/*
!healthcheck.sh
!docker_config.json
!filebrowser

View File

@@ -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
View File

@@ -29,3 +29,6 @@ yarn-error.log*
*.sw*
bin/
build/
/frontend/dist/*
!/frontend/dist/.gitkeep

View File

@@ -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

View File

@@ -2,6 +2,88 @@
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.27.0](https://github.com/filebrowser/filebrowser/compare/v2.26.0...v2.27.0) (2024-01-02)
### Features
* allow setting theme via cli ([#2881](https://github.com/filebrowser/filebrowser/issues/2881)) ([748af71](https://github.com/filebrowser/filebrowser/commit/748af7172ce96f0b66c394e88839bd57c194ffc7))
* display image resolutions in file details ([#2830](https://github.com/filebrowser/filebrowser/issues/2830)) ([a09dfa8](https://github.com/filebrowser/filebrowser/commit/a09dfa8d9f190243d811a841de44c4abb4403d87))
* make user session timeout configurable by flags ([#2845](https://github.com/filebrowser/filebrowser/issues/2845)) ([391a078](https://github.com/filebrowser/filebrowser/commit/391a078cd486e618c95a0c5850326076cbc025b6))
### Bug Fixes
* delete message when delete file from preview ([3264cea](https://github.com/filebrowser/filebrowser/commit/3264cea8307dca9ab5463dc81f2a10a817eb3d54))
* fix typo ([#2843](https://github.com/filebrowser/filebrowser/issues/2843)) ([4dbc802](https://github.com/filebrowser/filebrowser/commit/4dbc802972c930f5f42fc27507fac35c28c42afd))
* set correct port in docker healthcheck ([#2812](https://github.com/filebrowser/filebrowser/issues/2812)) ([d59ad59](https://github.com/filebrowser/filebrowser/commit/d59ad594b8649f57f61453b0dfbc350c57b690a2))
* typo in build error [#2903](https://github.com/filebrowser/filebrowser/issues/2903) ([#2904](https://github.com/filebrowser/filebrowser/issues/2904)) ([c4e955a](https://github.com/filebrowser/filebrowser/commit/c4e955acf4a1a8f8e8e94f697ffc838515e69a60))
### Build
* **deps-dev:** bump vite from 4.4.9 to 4.4.12 in /frontend ([#2862](https://github.com/filebrowser/filebrowser/issues/2862)) ([fc2ee37](https://github.com/filebrowser/filebrowser/commit/fc2ee373536584d024f7def62f350bdbb712d927))
* **deps:** bump golang.org/x/crypto from 0.14.0 to 0.17.0 ([#2890](https://github.com/filebrowser/filebrowser/issues/2890)) ([821fba4](https://github.com/filebrowser/filebrowser/commit/821fba41a25ba99d47641f01b10ac51960157888))
## [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)

View File

@@ -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)

View File

@@ -42,6 +42,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
flags.String("recaptcha.secret", "", "ReCaptcha secret")
flags.String("branding.name", "", "replace 'File Browser' by this name")
flags.String("branding.theme", "", "set the theme")
flags.String("branding.color", "", "set the theme color")
flags.String("branding.files", "", "path to directory with images and custom styles")
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
@@ -150,6 +151,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Branding.DisableExternal)
fmt.Fprintf(w, "\tDisable used disk percentage graph:\t%t\n", set.Branding.DisableUsedPercentage)
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color)
fmt.Fprintf(w, "\tTheme:\t%s\n", set.Branding.Theme)
fmt.Fprintln(w, "\nServer:")
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)

View File

@@ -38,6 +38,7 @@ override the options.`,
Name: mustGetString(flags, "branding.name"),
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
DisableUsedPercentage: mustGetBool(flags, "branding.disableUsedPercentage"),
Theme: mustGetString(flags, "branding.theme"),
Files: mustGetString(flags, "branding.files"),
},
}

View File

@@ -53,6 +53,8 @@ you want to change. Other options will remain unchanged.`,
set.Branding.Name = mustGetString(flags, flag.Name)
case "branding.color":
set.Branding.Color = mustGetString(flags, flag.Name)
case "branding.theme":
set.Branding.Theme = mustGetString(flags, flag.Name)
case "branding.disableExternal":
set.Branding.DisableExternal = mustGetBool(flags, flag.Name)
case "branding.disableUsedPercentage":

View File

@@ -64,6 +64,7 @@ func addServerFlags(flags *pflag.FlagSet) {
flags.Uint32("socket-perm", 0666, "unix socket file permissions") //nolint:gomnd
flags.StringP("baseurl", "b", "", "base url")
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
flags.String("token-expiration-time", "2h", "user session timeout")
flags.Int("img-processors", 4, "image processors count") //nolint:gomnd
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
@@ -261,6 +262,10 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
_, disableExec := getParamB(flags, "disable-exec")
server.EnableExec = !disableExec
if val, set := getParamB(flags, "token-expiration-time"); set {
server.TokenExpirationTime = val
}
return server
}

View File

@@ -7,6 +7,7 @@ import (
"crypto/sha512"
"encoding/hex"
"hash"
"image"
"io"
"log"
"mime"
@@ -29,20 +30,22 @@ 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:"-"`
Resolution *ImageResolution `json:"resolution,omitempty"`
}
// FileOptions are the options when getting a file info.
@@ -57,6 +60,11 @@ type FileOptions struct {
Content bool
}
type ImageResolution struct {
Width int `json:"width"`
Height int `json:"height"`
}
// NewFileInfo creates a File object from a path and a given user. This File
// object will be automatically filled depending on if it is a directory
// or a file. If it's a video file, it will also detect any subtitles.
@@ -235,6 +243,12 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
return nil
case strings.HasPrefix(mimetype, "image"):
i.Type = "image"
resolution, err := calculateImageResolution(i.Fs, i.Path)
if err != nil {
log.Printf("Error calculating image resolution: %v", err)
} else {
i.Resolution = resolution
}
return nil
case strings.HasSuffix(mimetype, "pdf"):
i.Type = "pdf"
@@ -263,6 +277,28 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
return nil
}
func calculateImageResolution(fs afero.Fs, filePath string) (*ImageResolution, error) {
file, err := fs.Open(filePath)
if err != nil {
return nil, err
}
defer func() {
if cErr := file.Close(); cErr != nil {
log.Printf("Failed to close file: %v", cErr)
}
}()
config, _, err := image.DecodeConfig(file)
if err != nil {
return nil, err
}
return &ImageResolution{
Width: config.Width,
Height: config.Height,
}, nil
}
func (i *FileInfo) readFirstBytes() []byte {
reader, err := i.Fs.Open(i.Path)
if err != nil {
@@ -294,13 +330,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 +384,25 @@ 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 && strings.HasPrefix(mime.TypeByExtension(file.Extension), "image/") {
resolution, err := calculateImageResolution(file.Fs, file.Path)
if err != nil {
log.Printf("Error calculating resolution for image %s: %v", file.Path, err)
} else {
file.Resolution = resolution
}
}
if file.IsDir {

20
frontend/.eslintrc.json Normal file
View 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
View File

@@ -0,0 +1,2 @@
# Ignore artifacts:
dist

View File

@@ -0,0 +1,3 @@
{
"trailingComma": "es5"
}

View File

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

View File

@@ -1,4 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

0
frontend/dist/.gitkeep vendored Normal file
View File

192
frontend/index.html Normal file
View 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
View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

29113
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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.12",
"vite-plugin-compression2": "^0.10.3",
"vite-plugin-rewrite-all": "^1.0.1"
},
"browserslist": [
"> 1%",

View File

@@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {},
},
};

0
frontend/public/.gitkeep Normal file
View File

View 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>

View File

@@ -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);
}

View File

@@ -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",

View File

@@ -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];
}
}

View File

@@ -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;
},

View File

@@ -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;
},

View File

@@ -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,

View File

@@ -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>

View File

@@ -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";

View File

@@ -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>

View File

@@ -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();

View File

@@ -1,7 +1,7 @@
<template>
<div class="card floating">
<div class="card-content">
<p v-if="selectedCount === 1">
<p v-if="!this.isListing || selectedCount === 1">
{{ $t("prompts.deleteMessageSingle") }}
</p>
<p v-else>
@@ -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;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -12,10 +12,17 @@
<p class="break-word" v-if="selected.length < 2">
<strong>{{ $t("prompts.displayName") }}</strong> {{ name }}
</p>
<p v-if="!dir || selected.length > 1">
<strong>{{ $t("prompts.size") }}:</strong>
<span id="content_length"></span> {{ humanSize }}
</p>
<div v-if="resolution">
<strong>{{ $t("prompts.resolution") }}:</strong>
{{ resolution.width }} x {{ resolution.height }}
</div>
<p v-if="selected.length < 2" :title="modTime">
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
</p>
@@ -81,7 +88,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";
@@ -126,6 +133,18 @@ export default {
: this.req.items[this.selected[0]].isDir)
);
},
resolution: function() {
if (this.selectedCount === 1) {
const selectedItem = this.req.items[this.selected[0]];
if (selectedItem && selectedItem.type === 'image') {
return selectedItem.resolution;
}
}
else if (this.req && this.req.type === 'image') {
return this.req.resolution;
}
return null;
},
},
methods: {
checksum: async function (event, algo) {
@@ -142,7 +161,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);
}

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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"
);
},
},

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
},
},
};

View File

@@ -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>

View File

@@ -17,6 +17,7 @@ export default {
hu: "hu",
ar: "ar",
de: "de",
el: "el",
en: "en",
es: "es",
fr: "fr",

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -120,6 +120,10 @@
right: 0;
}
.shell__divider {
height: 12px;
}
header .search-button,
header .menu-button {
display: inherit;

View File

@@ -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 *
* * * * * * * * * * * * * * * */

View File

@@ -5,10 +5,13 @@
"copy": "نسخ",
"copyFile": "نسخ الملف",
"copyToClipboard": "نسخ الى الحافظة",
"copyDownloadLinkToClipboard": "نسخ رابط التحميل الى الحافظة",
"create": "إنشاء",
"delete": "حذف",
"download": "تحميل",
"hideDotfiles": "",
"file": "ملف",
"folder": "مجلد",
"hideDotfiles": "إخفاء ملفات النقطة",
"info": "معلومات",
"more": "المزيد",
"move": "نقل",
@@ -16,7 +19,7 @@
"new": "جديد",
"next": "التالي",
"ok": "موافق",
"permalink": "الحصول على لنك دائم",
"permalink": "الحصول على رابط دائم",
"previous": "السابق",
"publish": "نشر",
"rename": "إعادة تسمية",
@@ -28,21 +31,28 @@
"select": "تحديد",
"selectMultiple": "تحديد متعدد",
"share": "مشاركة",
"shell": "Toggle shell",
"shell": "تفعيل/إغلاق واجهة اﻷوامر (shell)",
"submit": "تسليم",
"switchView": "تغيير العرض",
"toggleSidebar": "تبديل الشريط الجانبي",
"update": "تحديث",
"upload": "رفع"
"upload": "رفع",
"openFile": "فتح الملف",
"continue": "متابعة"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": ""
"downloadFile": "تحميل الملف",
"downloadFolder": "تحميل المجلد",
"downloadSelected": "تحميل الملفات المحددة"
},
"upload": {
"abortUpload": "هل تريد بالتاكيد إلغاء الرفع؟"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
"forbidden": "ليست لديك الصلاحيات للوصول لهذا المحتوى.",
"internal": "لقد حدث خطأ ما.",
"notFound": "لا يمكن الوصول لهذا المحتوى."
"notFound": "لا يمكن الوصول لهذا المحتوى.",
"connection": "لا يمكن اﻹتصال بالخادم."
},
"files": {
"body": "الصفحة",
@@ -50,17 +60,18 @@
"closePreview": "إغلاق العرض",
"files": "الملفات",
"folders": "المجلدات",
"home": "الصفحة الاولى",
"home": "الصفحة الرئيسية",
"lastModified": "آخر تعديل",
"loading": "جاري التحميل...",
"lonely": "تبدو وحيدا هنا...",
"metadata": "بيانات تعريفية",
"metadata": "بيانات وصفية",
"multipleSelectionEnabled": "التحديد المتعدد مفعل",
"name": "الإسم",
"name": "اسم",
"size": "الحجم",
"sortByLastModified": "الترتيب بآخر تعديل",
"sortByName": "الترتيب بالإسم",
"sortBySize": "الترتيب بالحجم"
"sortByName": "الترتيب باسم",
"sortBySize": "الترتيب بالحجم",
"noPreview": "لا يوجد عرض مسبق لهذا الملف."
},
"help": {
"click": "حدد الملف أو المجلد",
@@ -71,7 +82,7 @@
},
"del": "حذف البيانات المحددة",
"doubleClick": "فتح المجلد او الملف",
"esc": "مسح التحديد وإغلاق النافذة المنبثقة",
"esc": "مسح التحديد و إغلاق النافذة المنبثقة",
"f1": "هذه المعلومات",
"f2": "إعادة تسمية الملف",
"help": "مساعدة"
@@ -81,47 +92,49 @@
"hu": "Magyar",
"ar": "العربية",
"de": "Deutsch",
"el": "Ελληνικά",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "",
"is": "Icelandic",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "",
"nlBE": "Dutch (Belgium)",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "",
"ro": "Romanian",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"svSE": "Swedish (Sweden)",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": {
"createAnAccount": "Create an account",
"loginInstead": "Already have an account",
"createAnAccount": "إنشاء حساب جديد",
"loginInstead": "هل لديك حساب",
"password": "كلمة المرور",
"passwordConfirm": "Password Confirmation",
"passwordsDontMatch": "Passwords don't match",
"signup": "Signup",
"passwordConfirm": "تأكيد كلمة المرور",
"passwordsDontMatch": "كلمة المرور غير متطابقة",
"signup": "إشترك",
"submit": "تسجيل دخول",
"username": "إسم المستخدم",
"usernameTaken": "Username already taken",
"usernameTaken": "إسم المستخدم غير متاح",
"wrongCredentials": "بيانات دخول خاطئة"
},
"permanent": "دائم",
"prompts": {
"copy": "نسخ",
"copyMessage": "رجاء حدد المكان لنسخ ملفاتك فيه:",
"currentlyNavigating": "يتم الإنتقال حاليا إلى:",
"copyMessage": "حدد المكان لنسخ ملفاتك فيه:",
"currentlyNavigating": "يتم انتقال حاليا إلى:",
"deleteMessageMultiple": "هل تريد بالتأكيد حذف {count} ملف؟",
"deleteMessageSingle": "هل تريد بالتأكيد حذف هذا الملف/المجلد؟",
"deleteMessageShare": "هل تريد بالتأكيد إلغاء مشاركة هذا الملف/المجلد ({path})؟",
"deleteTitle": "حذف الملفات",
"displayName": "الإسم:",
"displayName": "عرض اﻹسم:",
"download": "تحميل الملفات",
"downloadMessage": "حدد إمتداد الملف المراد تحميله.",
"error": "لقد حدث خطأ ما",
@@ -130,81 +143,90 @@
"lastModified": "آخر تعديل",
"move": "نقل",
"moveMessage": "إختر مكان جديد للملفات أو المجلدات المراد نقلها:",
"newArchetype": "إنشاء منشور من المنشور الأصلي. الملف سيتم انشاءه في مجلد المحتويات.",
"newArchetype": "إنشاء منشور من المنشور اصلي. الملف سيتم انشاءه في مجلد المحتويات.",
"newDir": "مجلد جديد",
"newDirMessage": "رجاء أدخل اسم المجلد الجديد.",
"newDirMessage": "أدخل اسم المجلد الجديد.",
"newFile": "ملف جديد",
"newFileMessage": "رجاء ادخل اسم الملف الجديد.",
"newFileMessage": "ادخل اسم الملف الجديد.",
"numberDirs": "عدد المجلدات",
"numberFiles": "عدد الملفات",
"rename": "إعادة تسمية",
"renameMessage": "إدراج اسم جديد لـ",
"replace": "إستبدال",
"replaceMessage": "أحد الملفات التي تحاول رفعها يتعارض مع ملف موجود بنفس الإسم. هل تريد إستبدال الملف الموجود؟\n",
"replaceMessage": "أحد الملفات التي تحاول رفعها يتعارض مع ملف موجود بنفس اسم. هل المتابعة مع تخطي هذا الملف ام تريد إستبدال الملف الموجود؟\n",
"schedule": "جدولة",
"scheduleMessage": "أختر الوقت والتاريخ لجدولة نشر هذا المقال.",
"scheduleMessage": "أختر الوقت و التاريخ لجدولة نشر هذا المقال.",
"show": "عرض",
"size": "الحجم",
"upload": "",
"uploadMessage": ""
"upload": "رفع",
"uploadFiles": "يتم رفع {files} ملفات.",
"uploadMessage": "إختر الملفات التي تريد رفعها.",
"optionalPassword": "كلمة مرور إختيارية"
},
"search": {
"images": "الصور",
"music": "الموسيقى",
"pdf": "PDF",
"pressToSearch": "Press enter to search...",
"pressToSearch": "أضغط زر اﻹدخال للبحث...",
"search": "البحث...",
"typeToSearch": "Type to search...",
"types": "الأنواع",
"typeToSearch": "اكتب للبحث...",
"types": "انواع",
"video": "فيديوهات"
},
"settings": {
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "تنفيذ الأوامر",
"allowEdit": "تعديل، إعادة تسمية وحذف الملفات والمجلدات",
"allowNew": "إنشاء ملفات ومجلدات جديدة",
"allowPublish": "نشر مقالات وصفحات جديدة",
"allowSignup": "Allow users to signup",
"admin": "إدارة",
"administrator": "مدير",
"allowCommands": "تنفيذ اوامر",
"allowEdit": "تعديل، إعادة تسمية و حذف الملفات و المجلدات",
"allowNew": "إنشاء ملفات و مجلدات جديدة",
"allowPublish": "نشر مقالات و صفحات جديدة",
"allowSignup": "اسمح للمستخدمين بالاشتراك",
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
"branding": "Branding",
"brandingDirectoryPath": "Branding directory path",
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"branding": "الشعار",
"brandingDirectoryPath": "مسار مجلد الشعار",
"brandingHelp": "بإمكانك ان تخصص شكل و مظهر متصفح الملفات الخاص بك عن طريق تغيير اسمه، او تغيير الشعار، او اضافة ستايل مخصص، او حتى تعطيل الروابط الخارجية لـ GitHub.\nلمزيد من المعلومات حول التخصيص، يرجى الاطلاع على {0}.",
"changePassword": "تغيير كلمة المرور",
"commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "تم تحديث الأوامر",
"createUserDir": "Auto create user home dir while adding new user",
"commandRunner": "منفذ اﻷوامر",
"commandRunnerHelp": "هنا بإمكانك تعيين اﻷوامر التي سيتم تنفيذها في اﻷحداث المسماة. يجب كتابة أمر واحد في كل سطر. ستكون المتغيرات البيئية (env) {0} و {1} متاحة، حيث {0} نسبي لـ {1}. لمزيد من المعلومات حول هذه الميزة و المتغيرات البيئية المتاحة، يرجى قراءة {2}.",
"commandsUpdated": "تم تحديث اوامر",
"createUserDir": "إنشاء مجلد المستخدم (home) تلقائياً عند إنشاء مستخدم جديد",
"tusUploads": "التحميلات المتقطعة",
"tusUploadsHelp": "يدعم متصفح الملفات تحميل الملفات المتقطعة، مما يسمح بتحميلات الملفات بشكل فعال و موثوق و قابلة للمتابغة و متقطعة حتى على الشبكات غير الموثوقة.",
"tusUploadsChunkSize": "يشير إلى الحد اﻷقصى لحجم الطلب (سيتم استخدام التحميل المباشر للتحميلات صغيرة الخحم). يمكنك إدخال عدد صحيح عادي يدل على الحجم بوحدة البايت أو نمظ مثل10MB, 1GB, إلخ.",
"tusUploadsRetryCount": "عدد مرات إعادة المحاولة إذا فشلت عملية تحميل القطعة.",
"userHomeBasePath": "المسار الرئيسي لمجلد المستخدم (home)",
"userScopeGenerationPlaceholder": "سيتم تعيين نطاق المستخدم تلقائياً",
"createUserHomeDirectory": "إنشاء مجلد المستخدم (home)",
"customStylesheet": "ستايل مخصص",
"defaultUserDescription": "This are the default settings for new users.",
"disableExternalLinks": "Disable external links (except documentation)",
"disableUsedDiskPercentage": "Disable used disk percentage graph",
"documentation": "documentation",
"defaultUserDescription": "هذه اﻹعدادات اﻹفتراضية للمستخدمين الجدد.",
"disableExternalLinks": "تعطيل الروابط الخارجية (بإسثناء الوثائق)",
"disableUsedDiskPercentage": "تعطيل الرسم البياني لنسبة القرص المستخدم",
"documentation": "التوثيق",
"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.",
"executeOnShell": "نفيذ اﻷمر على الواجهة (shell)",
"executeOnShellDescription": "يقوم متصفح الملفات بتنفيذ اﻷوامر عن طريق استدعاء البرامج المنفذة مباشرة. إذا كنت تريد تشغيلها عن ظريق واجهة اﻷوامر (shell) مثل Bash أو PowerShell، يمكنك تعريفها هنا مع الوسائظ (arguments) المطلوبة. إذا تم تعيينها، سيتم إضافة اﻷمر الذي تقوم بتنفيذه كوسيط. ينطبق هذا على كل من أوامر المستخدم روابظ الحدث (hooks).",
"globalRules": "هذه مجموعة من القواعد العامة للسماح و المنع. تطبق على كل المستخدمين. يمكنك تحديد قواعد محددة لكل مستخدم لتجاوز القواعد الغامة.",
"globalSettings": "إعدادات عامة",
"hideDotfiles": "",
"insertPath": "Insert the path",
"insertRegex": "Insert regex expression",
"instanceName": "Instance name",
"hideDotfiles": "إخفاء ملفات النقطة",
"insertPath": "ادخل المسار",
"insertRegex": "ادخل تعبيراً منطقياً (regex)",
"instanceName": "اسم النسخة",
"language": "اللغة",
"lockPassword": "منع المستخدم من تغيير كلمة المرور",
"newPassword": "كلمة المرور الجديدة",
"newPasswordConfirm": "تأكيد كلمة المرور",
"newUser": "مستخدم جديد",
"password": "كلمة المرور",
"passwordUpdated": "تم تغيير كلمة المرور",
"path": "",
"passwordUpdated": "تم تغيير كلمة المرور!",
"path": "المسار",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"execute": "Execute commands",
"modify": "Edit files",
"rename": "Rename or move files and directories",
"share": "Share files"
"create": "إنشاء ملفات و مجلدات جديدة",
"delete": "حذف ملفات و مجلدات",
"download": "تحميل",
"execute": "تنفيذ اﻷوامر",
"modify": "تعديل محتويات الملفات",
"rename": "إعادة تسمية او نقل ملفات و مجلدات",
"share": "مشاركة ملفات"
},
"permissions": "الصلاحيات",
"permissionsHelp": "يمكنك تعيين المستخدم كـ \"مدير\" أو تحديد الصلاحيات بشكل منفرد.\n إذا قمت بتحديد المستخدم كـ \"مدير\"، باقي الخيارات سيتم تحديدها تلقائياً.\n إدارة المستخدمين تبقى صلاحية فريدة للـ \"مدير\" فقط.\n",
@@ -212,22 +234,24 @@
"ruleExample1": "منع الوصول إلى الملفات التي تبدأ بنقطة مثل (.git، و .gitignore) في كل مجلد.\n",
"ruleExample2": "منع الوصول إلى الملف المسمى Caddyfile في نطاق الجذر.",
"rules": "المجموعات",
"rulesHelp": "يمكنك هنا تحديد مجموعة من شروط السماح والمنع لهذا المستخدم. الملفات الممنوعة لن تظهر ضمن القائمة لهذا المستخدم ولن يستطيع الوصول لها. هنا ندعم الـ regex والـ relative path لنطاق المستخدمين.\n",
"rulesHelp": "يمكنك هنا تحديد مجموعة من شروط السماح و المنع لهذا المستخدم. الملفات الممنوعة لن تظهر ضمن القائمة لهذا المستخدم و لن يستطيع الوصول لها. هنا ندعم الـ regex و الـ relative path لنطاق المستخدمين.\n",
"scope": "نطاق",
"settingsUpdated": "تم تعديل الإعدادات",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"setDateFormat": "حدد تنسيق التاريخ",
"settingsUpdated": "تم تعديل اﻹعدادات",
"shareDuration": "مدة المشاركة",
"shareManagement": "إدارة المشاركات",
"shareDeleted": "تم حذف المشاركة!",
"singleClick": "استخدم النقرة الواحدة لفتح الملفات",
"themes": {
"dark": "",
"light": "",
"title": ""
"dark": "غامق",
"light": "فاتح",
"title": "موضوع"
},
"user": "المستخدم",
"userCommands": "الأوامر",
"userCommandsHelp": "الأوامر المتاحة لهذا المستخدم مفصولة فيما بينها بمسافة. مثال:\n",
"userCommands": "اوامر",
"userCommandsHelp": "اوامر المتاحة لهذا المستخدم مفصولة فيما بينها بمسافة. مثال:\n",
"userCreated": "تم إنشاء المستخدم",
"userDefaults": "User default settings",
"userDefaults": "إعدادات المستخدم اﻹفتراضية",
"userDeleted": "تم حذف المستخدم",
"userManagement": "إدارة المستخدمين",
"userUpdated": "تم تعديل المستخدم",
@@ -237,14 +261,14 @@
"sidebar": {
"help": "مساعدة",
"hugoNew": "هيوجو جديد",
"login": "Login",
"login": "تسجيل دخول",
"logout": "تسجيل خروج",
"myFiles": "ملفاتي",
"newFile": "ملف جديد",
"newFolder": "مجلد جديد",
"preview": "معاينة",
"settings": "الإعدادات",
"signup": "Signup",
"preview": "عرض مسبق",
"settings": "اعدادات",
"signup": "إشتراك",
"siteSettings": "إعدادات الموقع"
},
"success": {
@@ -258,3 +282,4 @@
"unit": "وحدة الوقت"
}
}

View File

@@ -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
View 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": "Μονάδα χρόνου"
}
}

View File

@@ -45,6 +45,9 @@
"downloadFolder": "Download Folder",
"downloadSelected": "Download Selected"
},
"upload": {
"abortUpload": "Are you sure you wish 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",
@@ -124,32 +128,32 @@
"permanent": "Permanent",
"prompts": {
"copy": "Copy",
"copyMessage": "Choose the place to copy your files:",
"copyMessage": "Choose the location to copy your files to:",
"currentlyNavigating": "Currently navigating on:",
"deleteMessageMultiple": "Are you sure you want to delete {count} file(s)?",
"deleteMessageSingle": "Are you sure you want to delete this file/folder?",
"deleteMessageShare": "Are you sure you want to delete this share({path})?",
"deleteMessageMultiple": "Are you sure you wish to delete {count} file(s)?",
"deleteMessageSingle": "Are you sure you wish to delete this file/folder?",
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
"deleteTitle": "Delete files",
"displayName": "Display Name:",
"download": "Download files",
"downloadMessage": "Choose the format you want to download.",
"downloadMessage": "Choose the format you wish to download.",
"error": "Something went wrong",
"fileInfo": "File information",
"filesSelected": "{count} files selected.",
"lastModified": "Last Modified",
"move": "Move",
"moveMessage": "Choose new house for your file(s)/folder(s):",
"moveMessage": "Choose new home for your file(s)/folder(s):",
"newArchetype": "Create a new post based on an archetype. Your file will be created on content folder.",
"newDir": "New directory",
"newDirMessage": "Write the name of the new directory.",
"newDirMessage": "Name your new directory.",
"newFile": "New file",
"newFileMessage": "Write the name of the new file.",
"newFileMessage": "Name your new file.",
"numberDirs": "Number of directories",
"numberFiles": "Number of files",
"rename": "Rename",
"renameMessage": "Insert a new name for",
"replace": "Replace",
"replaceMessage": "One of the files you're trying to upload is conflicting because of its name. Do you wish to continue to upload or replace the existing one?\n",
"replaceMessage": "One of the files you're trying to upload has a conflicting name. Do you wish to skip this file and continue to upload or replace the existing one?\n",
"schedule": "Schedule",
"scheduleMessage": "Pick a date and time to schedule the publication of this post.",
"show": "Show",
@@ -157,7 +161,8 @@
"upload": "Upload",
"uploadFiles": "Uploading {files} files...",
"uploadMessage": "Select an option to upload.",
"optionalPassword": "Optional password"
"optionalPassword": "Optional password",
"resolution": "Resolution"
},
"search": {
"images": "Images",
@@ -188,20 +193,20 @@
"createUserDir": "Auto create user home dir while adding new user",
"tusUploads": "Chunked Uploads",
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting a bytes input or a string like 10MB, 1GB etc.",
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.",
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.",
"userHomeBasePath": "Base path for user home directories",
"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.",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you wish 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 applies 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 these ones.",
"globalSettings": "Global Settings",
"hideDotfiles": "Hide dotfiles",
"insertPath": "Insert the path",
@@ -227,7 +232,7 @@
"permissions": "Permissions",
"permissionsHelp": "You can set the user to be an administrator or choose the permissions individually. If you select \"Administrator\", all of the other options will be automatically checked. The management of users remains a privilege of an administrator.\n",
"profileSettings": "Profile Settings",
"ruleExample1": "prevents the access to any dot file (such as .git, .gitignore) in every folder.\n",
"ruleExample1": "prevents the access to any dotfile (such as .git, .gitignore) in every folder.\n",
"ruleExample2": "blocks the access to the file named Caddyfile on the root of the scope.",
"rules": "Rules",
"rulesHelp": "Here you can define a set of allow and disallow rules for this specific user. The blocked files won't show up in the listings and they wont be accessible to the user. We support regex and paths relative to the users scope.\n",

View File

@@ -2,45 +2,45 @@
"buttons": {
"cancel": "ביטול",
"close": "סגירה",
"copy": "העתק",
"copy": "העתקה",
"copyFile": "העתק קובץ",
"copyToClipboard": "העתק ללוח",
"create": "צור",
"delete": "מחק",
"download": "הורד",
"create": "יצירה",
"delete": "מחיקה",
"download": "הורדה",
"file": "קובץ",
"folder": "תקייה",
"hideDotfiles": "הסתר קבצים נסתרים",
"folder": יקייה",
"hideDotfiles": "הסתר קבצים/תיקיות ששמם מתחיל בנקודה",
"info": "מידע",
"more": "עוד",
"move": "העבר",
"move": "העברה",
"moveFile": "העבר קובץ",
"new": "חדש",
"next": "הבא",
"ok": "אישור",
"permalink": "צור קישור קבוע",
"previous": "הקודם",
"publish": "יצירה",
"rename": נה שם",
"replace": "החלף",
"publish": "פרסום",
"rename": ינוי שם",
"replace": "החלפה",
"reportIssue": "דווח על תקלה",
"save": "שמור",
"save": "שמירה",
"schedule": "תזמון",
"search": "חיפוש",
"select": "בחר",
"select": "בחירה",
"selectMultiple": "בחירה מרובה",
"share": תף",
"share": יתוף",
"shell": "פתיחת מסוף",
"submit": "אישור",
"switchView": נה תצוגה",
"toggleSidebar": "פתיחת / סגירת סרגל צד",
"switchView": ינוי תצוגה",
"toggleSidebar": "פתיחת/סגירת סרגל צד",
"update": "עדכון",
"upload": "העלאה",
"openFile": "פתח קובץ"
},
"download": {
"downloadFile": "הורד קובץ",
"downloadFolder": "הורד תקייה",
"downloadFolder": "הורד תיקייה",
"downloadSelected": "הורד קבצים שנבחרו"
},
"errors": {
@@ -51,10 +51,10 @@
},
"files": {
"body": "גוף",
"clear": קה",
"clear": יקוי",
"closePreview": "סגירת תצוגה מקדימה",
"files": "קבצים",
"folders": "תקיות",
"folders": יקיות",
"home": "ראשי",
"lastModified": "שונה לאחרונה",
"loading": "טוען...",
@@ -69,17 +69,17 @@
"noPreview": "תצוגה מקדימה לא זמינה לקובץ זה"
},
"help": {
"click": "בחר קובץ או תקייה",
"click": "בחר קובץ או תיקייה",
"ctrl": {
"click": "בחר מספר קבצים או תקיות",
"click": "בחר מספר קבצים או תיקיות",
"f": "פותח את החיפוש",
"s": "לשמור קובץ או להוריד את התקייה שבה אתה נמצא"
"s": "לשמור קובץ או להוריד את התיקייה שבה אתה נמצא"
},
"del": "מחק את מה שנבחר",
"doubleClick": "פתח קובץ או תקייה",
"del": "מחק את הבחירה",
"doubleClick": "פתח קובץ או תיקייה",
"esc": "נקה את הבחירה ו/או סגור את השדה",
"f1": "המידע הזה",
"f2": נה שם קובץ",
"f2": ינוי שם קובץ",
"help": "עזרה"
},
"languages": {
@@ -109,44 +109,44 @@
},
"login": {
"createAnAccount": "צור חשבון",
"loginInstead": "חשבון קיים",
"loginInstead": "כבר יש לי חשבון",
"password": "סיסמא",
"passwordConfirm": "אימות סיסמא",
"passwordsDontMatch": "סיסמאות אינן תואמות",
"passwordsDontMatch": "הסיסמאות אינן תואמות",
"signup": "הרשמה",
"submit": "התחברות",
"username": "שם משתמש",
"usernameTaken": "שם משתמש כבר קיים",
"usernameTaken": "שם המשתמש כבר קיים",
"wrongCredentials": "פרטי התחברות שגויים"
},
"permanent": "קבוע",
"prompts": {
"copy": "העתק",
"copy": "העתקה",
"copyMessage": "בחר לאן להעתיק את הקבצים:",
"currentlyNavigating": "כרגע מנווט ב:",
"deleteMessageMultiple": "האם אתה בטוח שברצונך למחוק {count} קבצים?",
"deleteMessageSingle": "האם אתה בטוח שברצונך למחוק את הקובץ או התקייה?",
"deleteMessageShare": "האם אתה בטוח שברצונך למחוק את השיתוף הזה?({path})?",
"deleteTitle": "מחק קבצים",
"deleteMessageSingle": "האם אתה בטוח שברצונך למחוק את הקובץ/התיקייה?",
"deleteMessageShare": "האם אתה בטוח שברצונך למחוק את השיתוף הזה ({path})?",
"deleteTitle": "מחיקת קבצים",
"displayName": "שם:",
"download": "הורד קבצים",
"download": "הורדת קבצים",
"downloadMessage": "בחר את הפורמט שברצונך להוריד",
"error": "משהו השתבש",
"fileInfo": "מידע על הקובץ",
"filesSelected": "{count} קבצים נבחרו.",
"lastModified": "שונה לאחרונה",
"move": "העבר",
"moveMessage": "בחר מיקום חדש לקובץ / תקייה:",
"newArchetype": "צור פוסט חדש. הקובץ יווצר בתקיית התוכן",
"newDir": "תקייה חדשה",
"newDirMessage": "כתוב את שם התקייה החדשה",
"move": "העברה",
"moveMessage": "בחר מיקום חדש לקובץ/תיקייה:",
"newArchetype": "Create a new post based on an archetype. Your file will be created on content folder",
"newDir": יקייה חדשה",
"newDirMessage": "כתוב את שם התיקייה החדשה",
"newFile": "קובץ חדש",
"newFileMessage": "כתוב את שם הקובץ החדש",
"numberDirs": "מספר התקיות",
"numberFiles": "מספר הקבצים",
"rename": נה שם",
"numberDirs": "כמות התיקיות",
"numberFiles": "כמות הקבצים",
"rename": ינוי שם",
"renameMessage": "הכנס שם חדש עבור",
"replace": "החלף",
"replace": "החלפה",
"replaceMessage": "אחד הקבצים בעל שם זהה לקובץ קיים, האם ברצונך להחליף את הקובץ הקיים בחדש? זהירות - הקובץ הישן ימחק\n",
"schedule": "תזמון",
"scheduleMessage": "בחר תאריך ושעה לתזמון הפרסום של פוסט זה.",
@@ -161,34 +161,34 @@
"images": "תמונות",
"music": "מוזיקה",
"pdf": "PDF",
"pressToSearch": "הקש אנטר לחיפוש...",
"search": פש...",
"typeToSearch": "הקלד לחיפוש...",
"types": "סוג",
"pressToSearch": "הקש אנטר כדי לחפש...",
"search": יפוש...",
"typeToSearch": "הקלד כדי לחפש...",
"types": "סוגים",
"video": "וידאו"
},
"settings": {
"admin": "מנהל",
"administrator": "מנהל ראשי",
"allowCommands": "הפעל פקודות",
"allowEdit": "ערוך, שנה שם ומחק קבצים או תקיות",
"allowNew": "צור קבצים ותקיות חדשות",
"allowPublish": "פרסם פוסטים ודפים חדשים",
"allowSignup": "אפשר למשתמשים להירשם",
"allowCommands": "הפעלת פקודות",
"allowEdit": "עריכת, שינוי שם ומחיקת קבצים/תיקיות",
"allowNew": "יצירת קבצים ותיקיות חדשות",
"allowPublish": "פרסום פוסטים ודפים חדשים",
"allowSignup": "אפשר למשתמשים חדשים להירשם",
"avoidChanges": "(השאר ריק כדי למנוע שינויים)",
"branding": "מיתוג",
"brandingDirectoryPath": "נתיב תקיית מיתוג",
"brandingDirectoryPath": "נתיב תיקיית מיתוג",
"brandingHelp": "אתה יכול להגדיר את האופן שבו האפליקציה תראה על ידי שינוי שם האפליקציה, החלפת הלוגו, הוספת עיצוב מותאם אישית ואפילו השבתת קישורים חיצוניים לGithub.\nלמידע נוסף עיין ב-{0}.",
"changePassword": נה סיסמא",
"changePassword": ינוי סיסמא",
"commandRunner": "הרצת פקודות",
"commandRunnerHelp": "אתה יכול להגדיר פקודות שיבוצעו באירועים שונים. עליך לכתוב אחד בכל שורה. משתני הסביבה {0} ו-{1} יהיו זמינים, בהיותם {0} ביחס ל-{1}. למידע נוסף על תכונה זו ועל משתני הסביבה הזמינים, עיין ב {2}.",
"commandsUpdated": "הפקודות עודכנו.",
"createUserDir": "צור תקיית בית במהלך הוספת משתמש חדש",
"userHomeBasePath": "נתיב ראשי לתקיות הבית של משתמשים",
"userScopeGenerationPlaceholder": תחום יווצר אוטומטית",
"createUserHomeDirectory": "צור תקיית בית למשתמש",
"commandsUpdated": "הפקודות עודכנו!",
"createUserDir": "צור אוטומטית תיקיית בית בעת הוספת משתמש חדש",
"userHomeBasePath": "נתיב ראשי לתיקיות הבית של משתמשים",
"userScopeGenerationPlaceholder": היקף יווצר אוטומטית",
"createUserHomeDirectory": "צור תיקיית בית למשתמש",
"customStylesheet": "עיצוב מותאם אישית (Stylesheet)",
"defaultUserDescription": "אלה הגדרות ברירת המחדל למשתמשים חדשים",
"defaultUserDescription": "הגדרות ברירת המחדל למשתמשים חדשים",
"disableExternalLinks": "השבת קישורים חיצוניים (למעט תיעוד)",
"documentation": "תיעוד",
"examples": "דוגמאות",
@@ -196,12 +196,12 @@
"executeOnShellDescription": "כברירת מחדל, האפליקציה מבצעת את הפקודות על ידי הפעלה ישירה לקבצים (הבינארים). אם אתה רוצה להפעיל אותם מתוך מעטפת כלשהי, (לדוגמא מתוך Bash או PowerShell) אתה יכול להגדיר אותם כאן עם הפרמטרים הנדרשים. שים לב שזה יבוצע גם על פקודות משתמש וגם על הוקים (Hooks) לאירועים.",
"globalRules": "זוהי קבוצה גלובלית של חוקים והרשאות (מה מותר ומה אסור), הם חלים על כל משתמש. אתה יכול להגדיר כללים ספציפיים בהגדרות של כל משתמש, כדי לעקוף את החוקים הגלובלים.",
"globalSettings": "הגדרות גלובליות",
"hideDotfiles": "הסתר קבצים נסתרים",
"hideDotfiles": "הסתר קבצים/תיקיות ששמם מתחיל בנקודה",
"insertPath": "הכנס את הנתיב",
"insertRegex": "הוסף ביטוי רגולרי",
"instanceName": "שם",
"instanceName": "שם מופע",
"language": "שפה",
"lockPassword": "מנע ממשתמש להחליף סיסמא",
"lockPassword": "מנע מהמשתמש להחליף סיסמא",
"newPassword": "הסיסמא החדשה שלך",
"newPasswordConfirm": "אשר את הסיסמה החדשה שלך",
"newUser": "משתמש חדש",
@@ -209,28 +209,28 @@
"passwordUpdated": "הסיסמא עודכנה!",
"path": "נתיב",
"perm": {
"create": "יצירת קבצים ותקיות",
"delete": "מחיקת קבצים ותקיות",
"download": "הורדת קבצים ותקיות",
"create": "יצירת קבצים ותיקיות",
"delete": "מחיקת קבצים ותיקיות",
"download": "הורדת קבצים ותיקיות",
"execute": "ביצוע פקודות",
"modify": "עריכת קבצים קבצים",
"rename": "שינוי שם או העברת קבצים ותקיות",
"rename": "שינוי שם/העברת קבצים ותיקיות",
"share": "שיתוף קבצים"
},
"permissions": "הרשאות",
"permissionsHelp": "אתה יכול להגדיר את המשתמש להיות מנהל מערכת או לבחור את ההרשאות בנפרד. אם תבחר \"מנהל מערכת\", כל ההרשאות ייבחרו אוטומטית. ניהול המשתמשים נשאר הרשאה של מנהל מערכת.\n",
"profileSettings": "הגדרות פרופיל",
"ruleExample1": "מנע גישה לקבצים נסתרים (כל קובץ שמתחיל בנקודה, לדוגמא .git)",
"ruleExample2": "חסימת גישה לקובץ בשם Caddyfile בתחום הראשי.",
"ruleExample1": "מנע גישה לקבצים נסתרים (כל קובץ/תיקייה שמתחיל בנקודה, לדוגמא .git)",
"ruleExample2": "חסימת גישה לקובץ בשם Caddyfile בהיקף הראשי.",
"rules": "חוקים",
"rulesHelp": "כאן אתה יכול להגדיר רשימה של כללים למשתמש ספציפי, רשימה שחורה ולבנה. הקבצים החסומים לא יופיעו ברשימת הקבצים ולא יהיו נגישים למשתמש. יש תמיכה בנתיבים (ביחס לתקייה הראשית של המשתמש), וגם בביטוי רגולרי.\n",
"scope": "תחום",
"rulesHelp": "כאן אתה יכול להגדיר רשימה של כללים למשתמש ספציפי, רשימה שחורה ולבנה. הקבצים החסומים לא יופיעו ברשימת הקבצים ולא יהיו נגישים למשתמש. יש תמיכה בנתיבים (ביחס לתיקייה הראשית של המשתמש), וגם בביטוי רגולרי.\n",
"scope": "היקף",
"setDateFormat": "הגדר פורמט תאריך",
"settingsUpdated": "ההגדרות עודכנו!",
"shareDuration": "משך השיתוף",
"shareManagement": "ניהול שיתוף",
"shareDeleted": "שיתוף נמחק!",
"singleClick": "השתמש בלחיצות בודדות כדי לפתוח קבצים ותקיות",
"shareDeleted": "השיתוף נמחק!",
"singleClick": "השתמש בלחיצה בודדת כדי לפתוח קובץ/תיקייה",
"themes": {
"dark": "כהה",
"light": "בהיר",
@@ -238,10 +238,10 @@
},
"user": "משתמש",
"userCommands": "פקודות",
"userCommandsHelp": "רשימה מופרדת עם רווחים עם הפקודות הזמינות עבור משתמש זה. דוגמא:\n",
"userCreated": "משתמש נוצר!",
"userCommandsHelp": "רשימה מופרדת ברווחים של הפקודות הזמינות עבור משתמש זה. דוגמא:\n",
"userCreated": "המשתמש נוצר!",
"userDefaults": "הגדרות ברירת מחדל למשתמש",
"userDeleted": "משתמש נמחק!",
"userDeleted": "המשתמש נמחק!",
"userManagement": "ניהול משתמש",
"userUpdated": "המשתמש עודכן!",
"username": "שם משתמש",
@@ -254,11 +254,11 @@
"logout": "התנתק",
"myFiles": "הקבצים שלי",
"newFile": "קובץ חדש",
"newFolder": "תקייה חדשה",
"newFolder": יקייה חדשה",
"preview": "תצוגה מקדימה",
"settings": "הגדרות",
"signup": "הרשמה",
"siteSettings": "הגדרות"
"siteSettings": "הגדרות אתר"
},
"success": {
"linkCopied": "הקישור הועתק!"

View File

@@ -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),

View File

@@ -5,6 +5,7 @@
"copy": "复制",
"copyFile": "复制文件",
"copyToClipboard": "复制到剪贴板",
"copyDownloadLinkToClipboard": "复制下载链接到剪贴板",
"create": "创建",
"delete": "删除",
"download": "下载",
@@ -36,13 +37,17 @@
"toggleSidebar": "切换侧边栏",
"update": "更新",
"upload": "上传",
"openFile": "打开文件"
"openFile": "打开文件",
"continue": "继续"
},
"download": {
"downloadFile": "下载文件",
"downloadFolder": "下载文件夹",
"downloadSelected": "下载已选"
},
"upload": {
"abortUpload": "你确定要中止吗?"
},
"errors": {
"forbidden": "你无权限访问",
"internal": "服务器出了点问题。",
@@ -69,9 +74,9 @@
"noPreview": "此文件无法预览。"
},
"help": {
"click": "选择文件或目录",
"click": "选择文件或文件夹",
"ctrl": {
"click": "选择多个文件或目录",
"click": "选择多个文件或文件夹",
"f": "打开搜索框",
"s": "保存文件或下载当前文件夹"
},
@@ -87,6 +92,7 @@
"hu": "Magyar",
"ar": "العربية",
"de": "Deutsch",
"el": "Ελληνικά",
"en": "English",
"es": "Español",
"fr": "Français",
@@ -94,7 +100,7 @@
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "DutchBelgium",
"nlBE": "Dutch (Belgium)",
"pl": "Polski",
"pt": "Português",
"ptBR": "PortuguêsBrasil",
@@ -122,7 +128,7 @@
"permanent": "永久",
"prompts": {
"copy": "复制",
"copyMessage": "请选择欲复制至的目录:",
"copyMessage": "请选择目标目录:",
"currentlyNavigating": "当前目录:",
"deleteMessageMultiple": "你确定要删除这 {count} 个文件吗?",
"deleteMessageSingle": "你确定要删除这个文件/文件夹吗?",
@@ -136,31 +142,33 @@
"filesSelected": "已选择 {count} 个文件。",
"lastModified": "最后修改",
"move": "移动",
"moveMessage": "请选择欲移动至的目录:",
"newArchetype": "创建一个基于原型的新帖子。的文件将会创建在内容文件夹中。",
"newDir": "新建目录",
"newDirMessage": "请输入新目录的名称。",
"moveMessage": "请选择目标目录:",
"newArchetype": "创建一个基于原型的新帖子。的文件将会创建在内容文件夹中。",
"newDir": "新建文件夹",
"newDirMessage": "请输入新文件夹的名称。",
"newFile": "新建文件",
"newFileMessage": "请输入新文件的名称。",
"numberDirs": "目录数",
"numberDirs": "文件夹数",
"numberFiles": "文件数",
"rename": "重命名",
"renameMessage": "请输入新名称,旧名称为:",
"replace": "替换",
"replaceMessage": "尝试上传的文件中有一个与现有文件的名称存在冲突。是否替换现有的同名文件?\n",
"replaceMessage": "尝试上传的文件中有一个与现有文件的名称存在冲突。是否替换现有的同名文件?\n",
"schedule": "计划",
"scheduleMessage": "请选择发布这篇帖子的日期与时间。",
"show": "点击以显示",
"size": "大小",
"upload": "上传",
"uploadFiles": "正在上传 {files} ...",
"uploadMessage": "选择上传选项。",
"optionalPassword": "密码(选填,不填即无密码)"
"optionalPassword": "密码(选填,不填即无密码)",
"resolution": "分辨率"
},
"search": {
"images": "图像",
"music": "音乐",
"pdf": "PDF",
"pressToSearch": "输入回车以搜索...",
"pressToSearch": "回车以搜索...",
"search": "搜索...",
"typeToSearch": "输入以搜索...",
"types": "类型",
@@ -170,28 +178,35 @@
"admin": "管理员",
"administrator": "管理员",
"allowCommands": "执行命令Shell 命令)",
"allowEdit": "编辑、重命名或删除文件/目录",
"allowNew": "创建新文件和目录",
"allowEdit": "编辑、重命名或删除文件/文件夹",
"allowNew": "创建新文件和文件夹",
"allowPublish": "发布新的帖子与页面",
"allowSignup": "允许用户注册",
"avoidChanges": "(留空以避免更改)",
"branding": "品牌",
"brandingDirectoryPath": "品牌信息文件夹路径",
"brandingHelp": "可以通过改变实例名称,更换 Logo加入自定义样式甚至禁用到 Github 的外部链接来自定义 File Browser 的外观和感觉。\n想获得更多信息请查看 {0}。",
"brandingHelp": "可以通过改变实例名称,更换 Logo加入自定义样式甚至禁用到 Github 的外部链接来自定义 File Browser 的外观和感觉。\n想获得更多信息请查看 {0}。",
"changePassword": "更改密码",
"commandRunner": "命令执行器",
"commandRunnerHelp": "你可以在此设置在下列事件中执行的命令。每行必须写一条命令。可以在命令中使用环境变量 {0} 和 {1},使 {0} 与 {1} 相关联。关于此功能和可用环境变量的更多信息,请阅读 {2}。",
"commandsUpdated": "命令已更新!",
"createUserDir": "在添加新用户的同时自动创建用户的个人目录",
"createUserDir": "在添加新用户的同时自动创建用户的目录",
"tusUploads": "分块上传",
"tusUploadsHelp": "File Browser 支持分块上传,在不佳的网络下也可进行高效、可靠、可续的文件上传",
"tusUploadsChunkSize": "分块上传大小,例如 10MB 或 1GB",
"tusUploadsRetryCount": "分块上传失败时的重试次数",
"userHomeBasePath": "用户主目录的路径",
"userScopeGenerationPlaceholder": "自动生成目录范围",
"createUserHomeDirectory": "创建用户主目录",
"customStylesheet": "自定义样式表CSS",
"defaultUserDescription": "这些是新用户的默认设置。",
"disableExternalLinks": "禁止外部链接(帮助文档除外)",
"disableUsedDiskPercentage": "Disable used disk percentage graph",
"disableUsedDiskPercentage": "禁用磁盘已用空间展示",
"documentation": "帮助文档",
"examples": "例",
"examples": "例",
"executeOnShell": "在 Shell 中执行",
"executeOnShellDescription": "默认情况下File Browser 通过直接调用命令的二进制包来执行命令,如果想在 Shell中 执行(如 Bash 或 PowerShell你可以在这里定义所使用的 Shell 和参数。设置后,所执行的命令会作为参数追加。本设置对用户命令和事件钩子都生效。",
"globalRules": "这是全局允许与禁止规则。它们作用于所有用户。可以给每个用户定义单独的特殊规则来覆盖全局规则。",
"executeOnShellDescription": "默认情况下File Browser 通过直接调用命令的二进制包来执行命令,如果想在 Shell中 执行(如 Bash 或 PowerShell你可以在这里定义所使用的 Shell 和参数。设置后,所执行的命令会作为参数追加。本设置对用户命令和事件钩子都生效。",
"globalRules": "这是全局允许与禁止规则。它们作用于所有用户。可以给每个用户定义单独的特殊规则来覆盖全局规则。",
"globalSettings": "全局设置",
"hideDotfiles": "不显示隐藏文件",
"insertPath": "插入路径",
@@ -199,8 +214,8 @@
"instanceName": "实例名称",
"language": "语言",
"lockPassword": "禁止用户修改密码",
"newPassword": "的新密码",
"newPasswordConfirm": "再次输入以确认的新密码",
"newPassword": "的新密码",
"newPasswordConfirm": "再次输入以确认的新密码",
"newUser": "新建用户",
"password": "密码",
"passwordUpdated": "密码已更新!",
@@ -215,19 +230,19 @@
"share": "分享文件"
},
"permissions": "权限",
"permissionsHelp": "可以将该用户设置为管理员或单独选择各项权限。如果选择了“管理员”,则其他的选项会被自动选中,同时该用户可以管理其他用户。\n",
"permissionsHelp": "可以将该用户设置为管理员或单独选择各项权限。如果选择了“管理员”,则其他的选项会被自动选中,同时该用户可以管理其他用户。\n",
"profileSettings": "个人设置",
"ruleExample1": "阻止用户访问所有文件夹下任何以 . 开头的文件(隐藏文件, 例如: .git, .gitignore。\n",
"ruleExample2": "阻止用户访问其目录范围的根目录下名为 Caddyfile 的文件。",
"rules": "规则",
"rulesHelp": "可以为该用户制定一组黑名单或白名单式的规则,被屏蔽的文件将不会显示在列表中,用户也无权限访问,支持正则表达式和相对于用户范围的路径。\n",
"rulesHelp": "可以为该用户制定一组黑名单或白名单式的规则,被屏蔽的文件将不会显示在列表中,用户也无权限访问,支持正则表达式和相对于用户范围的路径。\n",
"scope": "目录范围",
"setDateFormat": "显示精确的日期格式",
"settingsUpdated": "设置已更新!",
"shareDuration": "分享期限",
"shareManagement": "分享管理",
"shareDeleted": "分享已删除!",
"singleClick": "使用单击来打开文件和目录",
"singleClick": "使用单击来打开文件和文件夹",
"themes": {
"dark": "深色",
"light": "浅色",

View File

@@ -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();

View File

@@ -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: [
{

View File

@@ -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;

View File

@@ -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({

View File

@@ -11,6 +11,8 @@ const state = {
progress: [],
queue: [],
uploads: {},
speedMbyte: 0,
eta: 0,
};
const mutations = {

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -0,0 +1,6 @@
import { partial } from "filesize";
/**
* Formats filesize as KiB/MiB/...
*/
export const filesize = partial({ base: 2 });

View File

@@ -10,7 +10,7 @@
</template>
<script>
import HeaderBar from "@/components/header/HeaderBar";
import HeaderBar from "@/components/header/HeaderBar.vue";
const errors = {
0: {

View File

@@ -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;

View File

@@ -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");
},
},

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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",

View File

@@ -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",
@@ -109,7 +110,7 @@ export default {
this.user = {
...defaults,
username: "",
passsword: "",
password: "",
rules: [],
lockPassword: false,
id: 0,

View File

@@ -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
View 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 };
}
},
},
};
}
});

View File

@@ -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
View File

@@ -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.17.0
golang.org/x/image v0.10.0
golang.org/x/text v0.14.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.15.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
View File

@@ -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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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=

View File

@@ -1,3 +1,3 @@
#!/bin/sh
PORT=$(jq .port /.filebrowser.json)
PORT=${FB_PORT:-$(jq .port /.filebrowser.json)}
curl -f http://localhost:$PORT/health || exit 1

View File

@@ -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",
},
}

View File

@@ -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
View 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
View 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",
}

View File

@@ -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")

View File

@@ -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))

View File

@@ -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