Compare commits

...

69 Commits

Author SHA1 Message Date
Henrique Dias
83492a4dfb feat(frontend): migrate Vue to Composition API
Signed-off-by: Henrique Dias <mail@hacdias.com>
2025-11-13 14:23:20 +01:00
Henrique Dias
bf3ba65782 chore: bump @vue/tsconfig
Signed-off-by: Henrique Dias <mail@hacdias.com>
2025-11-13 14:21:30 +01:00
Henrique Dias
f35b7c9d9d chore: remove unused tests
Signed-off-by: Henrique Dias <mail@hacdias.com>
2025-11-13 14:21:11 +01:00
Henrique Dias
e9506c3eae chore: remove unused dependencies
Signed-off-by: Henrique Dias <mail@hacdias.com>
2025-11-13 14:19:08 +01:00
Henrique Dias
bb4465548b chore: use 'chore' instead of 'fix' in renovate 2025-11-13 14:10:06 +01:00
Henrique Dias
cf8b5ca768 docs: fix duplicated changelog entry 2025-11-13 14:05:50 +01:00
Henrique Dias
f93d760b1b chore(release): 2.45.2 2025-11-13 14:03:52 +01:00
Henrique Dias
1495ee8dd8 chore: replace release-please with commit-and-tag-version 2025-11-13 14:03:41 +01:00
FileBrowser Robot
8a7279e3ee chore(master): release 2.45.2 (#5538) 2025-11-13 13:51:22 +01:00
renovate[bot]
fdff7a38f4 fix(deps): update module github.com/shirou/gopsutil/v3 to v4 (#5536)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-13 13:48:34 +01:00
renovate[bot]
f26a68587d fix(deps): update module gopkg.in/yaml.v2 to v3 (#5537)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-13 13:47:39 +01:00
Henrique Dias
1c62038344 chore: delete commitlint 2025-11-13 09:41:33 +01:00
Henrique Dias
6eb7b4b8ec ci: replace standard-version with release please (#5533) 2025-11-13 09:34:59 +01:00
Henrique Dias
f11fc37409 chore: bump to Node 24, pnpm 10, multiple GH actions (#5532) 2025-11-13 07:50:00 +01:00
renovate[bot]
d12a3dc8a8 chore(deps): update amannn/action-semantic-pull-request action to v6 (#5523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 22:28:24 +01:00
Henrique Dias
0cfab8770a chore: some dependency updates 2025-11-12 22:25:18 +01:00
renovate[bot]
3876ae8fe8 chore(deps): update actions/setup-go action to v6 2025-11-12 15:15:46 +01:00
renovate[bot]
77644e4425 chore(deps): update actions/setup-node action to v6 2025-11-12 15:15:36 +01:00
renovate[bot]
6592782dc0 chore: update minor and patch dependencies, go 1.25 2025-11-12 15:12:28 +01:00
renovate[bot]
d6dc250ed4 chore(deps): update actions/checkout action to v5 2025-11-12 14:54:45 +01:00
Henrique Dias
9579f14c34 chore: add renovate.json 2025-11-12 14:49:22 +01:00
Henrique Dias
c5acbffe3f docs: import logo and banner (#5514) 2025-11-11 18:32:41 +01:00
Henrique Dias
63142042bc docs: remove unmaintained badges 2025-11-11 18:10:55 +01:00
Henrique Dias
1ac0305ed0 docs: add notice about releases page 2025-11-11 17:40:33 +01:00
Henrique Dias
7860013aa9 chore: update CODEOWNERS to use team (#5512) 2025-11-11 17:39:24 +01:00
Henrique Dias
7a5b964611 chore(release): 2.45.1 2025-11-11 08:10:41 +01:00
Jagadam Dinesh Reddy
6950c2e4d2 fix: share page preview items to contain baseUrl (#5510)
Co-authored-by: jagadam97 <dineshjagadam@hmail.com>
2025-11-11 08:09:19 +01:00
Henrique Dias
291223b3ce Merge commit from fork 2025-11-11 08:06:16 +01:00
Henrique Dias
99aeb766c3 chore(release): 2.45.0 2025-11-01 09:57:04 +01:00
Henrique Dias
93fe31cc55 fix: support croatian (#5502) 2025-11-01 08:53:50 +01:00
transifex-integration[bot]
b9a03fabd9 feat: update translations (#5458) 2025-11-01 08:38:55 +01:00
jagadam97
d00b3ea8f8 fix(img):Prevent thumbnail generation for large images 2025-11-01 08:37:50 +01:00
Henrique Dias
c18afcddc4 chore(release): 2.44.2 2025-10-22 10:39:43 +02:00
Henrique Dias
57db25d08a fix(http): remove auth query parameter 2025-10-22 10:38:30 +02:00
dependabot[bot]
b8f64a1c1b build(deps-dev): bump vite from 6.3.6 to 6.4.1 in /frontend
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.3.6 to 6.4.1.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@6.4.1/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-22 08:54:36 +02:00
Henrique Dias
de35dee1c5 chore(release): 2.44.1 2025-10-17 17:41:27 +02:00
MSomnium Studios
dd883985bb fix(auth): prevent integer overflow in logout timer using safeTimeout (#5470) 2025-10-17 17:38:57 +02:00
rocksload
97b8911ba8 refactor: use slices.Contains to simplify code (#5483) 2025-10-17 16:45:47 +02:00
Ryan
a397e7305d fix: editor discard prompt doesn't save nor discard
Co-authored-by: Ryan Miller <ryan.miller@infinitetactics.com>
2025-10-17 16:45:36 +02:00
Ryan
d0039afbb7 fix: wrong url on settings branding link 2025-10-03 10:42:24 +02:00
Henrique Dias
878cdfbc52 chore(release): 2.44.0 2025-09-25 17:20:12 +02:00
transifex-integration[bot]
1165f00bd4 feat: Updates for project File Browser (#5457)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-09-25 17:19:21 +02:00
Henrique Dias
949ddffef2 fix: some formatting issues with i18n files 2025-09-25 17:13:30 +02:00
Henrique Dias
c4725428e0 fix: computation of file path 2025-09-25 17:09:16 +02:00
MSomnium Studios
d29ad356d1 feat: Improved path display in the new file and directory modal (#5451) 2025-09-25 16:57:30 +02:00
MSomnium Studios
692ca5eaf0 fix(upload): throttle upload speed calculation to 100ms to avoid Infinity MB/s (#5456)
Co-authored-by: Henrique Dias <mail@hacdias.com>
2025-09-25 16:54:28 +02:00
Adam
b9787c78f3 feat: allow setting ace editor theme (#3826)
Co-authored-by: Henrique Dias <mail@hacdias.com>
2025-09-25 16:47:00 +02:00
transifex-integration[bot]
dec7a02737 feat: Translate frontend/src/i18n/en.json in no
100% translated source file: 'frontend/src/i18n/en.json'
on 'no'.
2025-09-25 16:46:45 +02:00
transifex-integration[bot]
0eade717ce feat: Updates for project File Browser (#5450)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-09-19 21:32:15 +02:00
MSomnium Studios
e6c674b3c6 fix: show login when session token expires 2025-09-19 15:09:26 +02:00
transifex-integration[bot]
4ff247e134 feat: Updates for project File Browser (#5446)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-09-18 13:10:27 +02:00
Henrique Dias
2f0c1f5fa2 chore(release): 2.43.0 2025-09-13 09:04:12 +02:00
Henrique Dias
07692653ff revert: build(deps): bump github.com/ulikunitz/xz from 0.5.12 to 0.5.14 2025-09-13 09:03:12 +02:00
Henrique Dias
82dc57ad43 chore(release): 2.43.0 2025-09-13 08:44:52 +02:00
Jorge
84e8632b98 feat: "save changes" button to discard changes dialog 2025-09-13 08:07:05 +02:00
transifex-integration[bot]
571ce6cb0d feat: Translate frontend/src/i18n/en.json in es
94% of minimum 50% translated source file: 'frontend/src/i18n/en.json'
on 'es'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-09-13 07:59:33 +02:00
wuwenbin
783503aece fix: optimize markdown preview height 2025-09-13 07:58:43 +02:00
cui
b482a9bf0d refactor: to use strings.Lines 2025-09-13 07:57:18 +02:00
dependabot[bot]
36c6cc203e build(deps-dev): bump vite from 6.1.6 to 6.3.6 in /frontend
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.1.6 to 6.3.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.6/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-10 14:27:17 +02:00
transifex-integration[bot]
8950585141 feat: Updates for project File Browser (#5427) 2025-09-08 07:48:31 +02:00
dependabot[bot]
950028abeb build(deps): bump github.com/ulikunitz/xz from 0.5.12 to 0.5.14
Bumps [github.com/ulikunitz/xz](https://github.com/ulikunitz/xz) from 0.5.12 to 0.5.14.
- [Commits](https://github.com/ulikunitz/xz/compare/v0.5.12...v0.5.14)

---
updated-dependencies:
- dependency-name: github.com/ulikunitz/xz
  dependency-version: 0.5.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-30 08:00:45 +02:00
dependabot[bot]
280fa562a6 build(deps): bump github.com/go-viper/mapstructure/v2 in /tools
Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-22 09:02:02 +02:00
transifex-integration[bot]
6b1fa87ad3 feat: Translate frontend/src/i18n/en.json in fr
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-08-20 14:33:09 +02:00
Henrique Dias
cacfb2bc08 chore(release): 2.42.5 2025-08-16 09:42:55 +02:00
Jagadam Dinesh Reddy
3107ae4147 fix: "new folder" button not working in the move and copy popup (#5368) 2025-08-16 09:42:05 +02:00
Henrique Dias
c182114883 chore(release): 2.42.4 2025-08-16 08:05:47 +02:00
Henrique Dias
342b239ac6 fix: add libcap to Dockerfile.s6 2025-08-16 08:01:18 +02:00
Henrique Dias
0f41aac20b chore(release): 2.42.3 2025-08-09 08:46:10 +02:00
wx-11
cd51a59e72 fix: add missing CLI flags for user management (#5351) 2025-08-09 07:42:42 +02:00
137 changed files with 4602 additions and 7030 deletions

6
.github/CODEOWNERS vendored
View File

@@ -1,5 +1 @@
# These owners will be the default owners for everything in the repo.
# Unless a later match takes precedence, @o1egl will be requested for
# review when someone opens a pull request.
* @o1egl @hacdias
* @filebrowser/maintainers

View File

@@ -13,23 +13,23 @@ jobs:
lint-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
with:
package_json_file: "frontend/package.json"
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: "22.x"
node-version: "24.x"
cache: "pnpm"
cache-dependency-path: "frontend/pnpm-lock.yaml"
- run: make lint-frontend
lint-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: '1.24'
go-version: '1.25'
- run: make lint-backend
lint:
runs-on: ubuntu-latest
@@ -41,23 +41,23 @@ jobs:
test-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
with:
package_json_file: "frontend/package.json"
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: "22.x"
node-version: "24.x"
cache: "pnpm"
cache-dependency-path: "frontend/pnpm-lock.yaml"
- run: make test-frontend
test-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: '1.24'
go-version: '1.25'
- run: make test-backend
test:
runs-on: ubuntu-latest
@@ -71,33 +71,33 @@ jobs:
needs: [lint, test]
if: startsWith(github.event.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version: '1.23'
go-version: '1.25'
- uses: pnpm/action-setup@v4
with:
package_json_file: "frontend/package.json"
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: "22.x"
node-version: "24.x"
cache: "pnpm"
cache-dependency-path: "frontend/pnpm-lock.yaml"
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Build frontend
run: make build-frontend
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --clean

View File

@@ -16,7 +16,7 @@ jobs:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
- uses: amannn/action-semantic-pull-request@v6
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Build site
run: make site

View File

@@ -15,10 +15,10 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Build site
run: make site

5
.gitignore vendored
View File

@@ -35,10 +35,5 @@ build/
/frontend/dist/*
!/frontend/dist/.gitkeep
# Playwright files
/frontend/test-results/
/frontend/playwright-report/
/frontend/playwright/.cache/
default.nix
Dockerfile.dev

View File

@@ -1,6 +1,159 @@
# Changelog
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.
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
## [2.45.2](https://github.com/filebrowser/filebrowser/compare/v2.45.1...v2.45.2) (2025-11-13)
### Bug Fixes
* **deps:** update module github.com/shirou/gopsutil/v3 to v4 ([#5536](https://github.com/filebrowser/filebrowser/issues/5536)) ([fdff7a3](https://github.com/filebrowser/filebrowser/commit/fdff7a38f4711f2b58dfdd60bebbb057bd3a478d))
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#5537](https://github.com/filebrowser/filebrowser/issues/5537)) ([f26a685](https://github.com/filebrowser/filebrowser/commit/f26a68587d8432b536453093f42dc255d19d10fa))
### [2.45.1](https://github.com/filebrowser/filebrowser/compare/v2.45.0...v2.45.1) (2025-11-11)
### Bug Fixes
* share page preview items to contain baseUrl ([#5510](https://github.com/filebrowser/filebrowser/issues/5510)) ([6950c2e](https://github.com/filebrowser/filebrowser/commit/6950c2e4d2868f06235f93c0a18b303b4095ca0a))
## [2.45.0](https://github.com/filebrowser/filebrowser/compare/v2.44.2...v2.45.0) (2025-11-01)
### Features
* update translations ([#5458](https://github.com/filebrowser/filebrowser/issues/5458)) ([b9a03fa](https://github.com/filebrowser/filebrowser/commit/b9a03fabd98119d6588882f5ba2a7d29b012d729))
### Bug Fixes
* support croatian ([#5502](https://github.com/filebrowser/filebrowser/issues/5502)) ([93fe31c](https://github.com/filebrowser/filebrowser/commit/93fe31cc55c9d9d27c634993619a768fa700da1d))
### [2.44.2](https://github.com/filebrowser/filebrowser/compare/v2.44.1...v2.44.2) (2025-10-22)
### Bug Fixes
* **http:** remove auth query parameter ([57db25d](https://github.com/filebrowser/filebrowser/commit/57db25d08a1ef2cd0b41f34e312b7b7c35c7ed38))
### Build
* **deps-dev:** bump vite from 6.3.6 to 6.4.1 in /frontend ([b8f64a1](https://github.com/filebrowser/filebrowser/commit/b8f64a1c1bc235df784d7f52abd3a9e84c6db6ce))
### [2.44.1](https://github.com/filebrowser/filebrowser/compare/v2.44.0...v2.44.1) (2025-10-17)
### Bug Fixes
* **auth:** prevent integer overflow in logout timer using safeTimeout ([#5470](https://github.com/filebrowser/filebrowser/issues/5470)) ([dd88398](https://github.com/filebrowser/filebrowser/commit/dd883985bb484af9dfea2677a40d56999fdc72f3))
* editor discard prompt doesn't save nor discard ([a397e73](https://github.com/filebrowser/filebrowser/commit/a397e7305d1572baf67823413f97a29eea38f0cc))
* wrong url on settings branding link ([d0039af](https://github.com/filebrowser/filebrowser/commit/d0039afbb76a9364c1e6ac9715ccc3c239dc8cb6))
### Refactorings
* use slices.Contains to simplify code ([#5483](https://github.com/filebrowser/filebrowser/issues/5483)) ([97b8911](https://github.com/filebrowser/filebrowser/commit/97b8911ba8a65456091cbec0202f6b5209fcf363))
## [2.44.0](https://github.com/filebrowser/filebrowser/compare/v2.43.0...v2.44.0) (2025-09-25)
### Features
* allow setting ace editor theme ([#3826](https://github.com/filebrowser/filebrowser/issues/3826)) ([b9787c7](https://github.com/filebrowser/filebrowser/commit/b9787c78f3889171f94db19e7655dce68c64b6fb))
* Improved path display in the new file and directory modal ([#5451](https://github.com/filebrowser/filebrowser/issues/5451)) ([d29ad35](https://github.com/filebrowser/filebrowser/commit/d29ad356d1067c87b2821debab91286549f512a0))
* Translate frontend/src/i18n/en.json in no ([dec7a02](https://github.com/filebrowser/filebrowser/commit/dec7a027378fbc6948d203199c44a640a141bcad))
* Updates for project File Browser ([#5446](https://github.com/filebrowser/filebrowser/issues/5446)) ([4ff247e](https://github.com/filebrowser/filebrowser/commit/4ff247e134e4d61668ee656a258ed67f71414e18))
* Updates for project File Browser ([#5450](https://github.com/filebrowser/filebrowser/issues/5450)) ([0eade71](https://github.com/filebrowser/filebrowser/commit/0eade717ce9d04bf48051922f11d983edbc7c2d0))
* Updates for project File Browser ([#5457](https://github.com/filebrowser/filebrowser/issues/5457)) ([1165f00](https://github.com/filebrowser/filebrowser/commit/1165f00bd4dcb0dcfbc084f54f51902ba4b4a714))
### Bug Fixes
* computation of file path ([c472542](https://github.com/filebrowser/filebrowser/commit/c4725428e07da72b855009e2c13c6ed91d32e0b7))
* show login when session token expires ([e6c674b](https://github.com/filebrowser/filebrowser/commit/e6c674b3c616831942c4d4aacab0907d58003e23))
* some formatting issues with i18n files ([949ddff](https://github.com/filebrowser/filebrowser/commit/949ddffef20e38169902c5fd74dca4815dcecf11))
* **upload:** throttle upload speed calculation to 100ms to avoid Infinity MB/s ([#5456](https://github.com/filebrowser/filebrowser/issues/5456)) ([692ca5e](https://github.com/filebrowser/filebrowser/commit/692ca5eaf01e4dcf346ba03f82c5dbd50cce246b))
## [2.43.0](https://github.com/filebrowser/filebrowser/compare/v2.42.5...v2.43.0) (2025-09-13)
### Features
* "save changes" button to discard changes dialog ([84e8632](https://github.com/filebrowser/filebrowser/commit/84e8632b98e315bfef2da77dd7d1049daec99241))
* Translate frontend/src/i18n/en.json in es ([571ce6c](https://github.com/filebrowser/filebrowser/commit/571ce6cb0d7c8725d1cc1a3238ea506ddc72b060))
* Translate frontend/src/i18n/en.json in fr ([6b1fa87](https://github.com/filebrowser/filebrowser/commit/6b1fa87ad38ebbb1a9c5d0e5fc88ba796c148bcf))
* Updates for project File Browser ([#5427](https://github.com/filebrowser/filebrowser/issues/5427)) ([8950585](https://github.com/filebrowser/filebrowser/commit/89505851414bfcee6b9ff02087eb4cec51c330f6))
### Bug Fixes
* optimize markdown preview height ([783503a](https://github.com/filebrowser/filebrowser/commit/783503aece7fca9e26f7e849b0e7478aba976acb))
### Reverts
* build(deps): bump github.com/ulikunitz/xz from 0.5.12 to 0.5.14 ([0769265](https://github.com/filebrowser/filebrowser/commit/07692653ffe0ea5e517e6dc1fd3961172e931843))
### Build
* **deps-dev:** bump vite from 6.1.6 to 6.3.6 in /frontend ([36c6cc2](https://github.com/filebrowser/filebrowser/commit/36c6cc203e10947439519a0413d5817921a1690d))
* **deps:** bump github.com/go-viper/mapstructure/v2 in /tools ([280fa56](https://github.com/filebrowser/filebrowser/commit/280fa562a67824887ae6e2530a3b73739d6e1bb4))
* **deps:** bump github.com/ulikunitz/xz from 0.5.12 to 0.5.14 ([950028a](https://github.com/filebrowser/filebrowser/commit/950028abebe2898bac4ecfd8715c0967246310cb))
### Refactorings
* to use strings.Lines ([b482a9b](https://github.com/filebrowser/filebrowser/commit/b482a9bf0d292ec6542d2145a4408971e4c985f1))
## [2.43.0](https://github.com/filebrowser/filebrowser/compare/v2.42.5...v2.43.0) (2025-09-13)
### Features
* "save changes" button to discard changes dialog ([84e8632](https://github.com/filebrowser/filebrowser/commit/84e8632b98e315bfef2da77dd7d1049daec99241))
* Translate frontend/src/i18n/en.json in es ([571ce6c](https://github.com/filebrowser/filebrowser/commit/571ce6cb0d7c8725d1cc1a3238ea506ddc72b060))
* Translate frontend/src/i18n/en.json in fr ([6b1fa87](https://github.com/filebrowser/filebrowser/commit/6b1fa87ad38ebbb1a9c5d0e5fc88ba796c148bcf))
* Updates for project File Browser ([#5427](https://github.com/filebrowser/filebrowser/issues/5427)) ([8950585](https://github.com/filebrowser/filebrowser/commit/89505851414bfcee6b9ff02087eb4cec51c330f6))
### Bug Fixes
* optimize markdown preview height ([783503a](https://github.com/filebrowser/filebrowser/commit/783503aece7fca9e26f7e849b0e7478aba976acb))
### Build
* **deps-dev:** bump vite from 6.1.6 to 6.3.6 in /frontend ([36c6cc2](https://github.com/filebrowser/filebrowser/commit/36c6cc203e10947439519a0413d5817921a1690d))
* **deps:** bump github.com/go-viper/mapstructure/v2 in /tools ([280fa56](https://github.com/filebrowser/filebrowser/commit/280fa562a67824887ae6e2530a3b73739d6e1bb4))
* **deps:** bump github.com/ulikunitz/xz from 0.5.12 to 0.5.14 ([950028a](https://github.com/filebrowser/filebrowser/commit/950028abebe2898bac4ecfd8715c0967246310cb))
### Refactorings
* to use strings.Lines ([b482a9b](https://github.com/filebrowser/filebrowser/commit/b482a9bf0d292ec6542d2145a4408971e4c985f1))
### [2.42.5](https://github.com/filebrowser/filebrowser/compare/v2.42.4...v2.42.5) (2025-08-16)
### Bug Fixes
* "new folder" button not working in the move and copy popup ([#5368](https://github.com/filebrowser/filebrowser/issues/5368)) ([3107ae4](https://github.com/filebrowser/filebrowser/commit/3107ae41475ae9383c3af414d25a133e549f8087))
### [2.42.4](https://github.com/filebrowser/filebrowser/compare/v2.42.3...v2.42.4) (2025-08-16)
### Bug Fixes
* add libcap to Dockerfile.s6 ([342b239](https://github.com/filebrowser/filebrowser/commit/342b239ac6f4af2453d5f7aa27f7f0093024dd72))
### [2.42.3](https://github.com/filebrowser/filebrowser/compare/v2.42.2...v2.42.3) (2025-08-09)
### Bug Fixes
* add missing CLI flags for user management ([#5351](https://github.com/filebrowser/filebrowser/issues/5351)) ([cd51a59](https://github.com/filebrowser/filebrowser/commit/cd51a59e72c72560fce7bcc9b12aaf02646b699c))
### [2.42.2](https://github.com/filebrowser/filebrowser/compare/v2.42.1...v2.42.2) (2025-08-06)

View File

@@ -1,7 +1,7 @@
FROM ghcr.io/linuxserver/baseimage-alpine:3.22
RUN apk update && \
apk --no-cache add ca-certificates mailcap jq
apk --no-cache add ca-certificates mailcap jq libcap
# Make user and create necessary directories
RUN mkdir -p /config /database /srv && \
@@ -12,7 +12,8 @@ COPY filebrowser /bin/filebrowser
COPY docker/common/ /
COPY docker/s6/ /
RUN chown -R abc:abc /bin/filebrowser /defaults healthcheck.sh
RUN chown -R abc:abc /bin/filebrowser /defaults healthcheck.sh && \
setcap 'cap_net_bind_service=+ep' /bin/filebrowser
# Define healthcheck script
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s CMD /healthcheck.sh

View File

@@ -46,10 +46,6 @@ lint-frontend: ## Run frontend linters
lint-backend: | $(golangci-lint) ## Run backend linters
$Q $(golangci-lint) run -v
.PHONY: lint-commits
lint-commits: $(commitlint) ## Run commit linters
$Q ./scripts/commitlint.sh
fmt: $(goimports) ## Format source files
$Q $(goimports) -local $(MODULE) -w $$(find . -type f -name '*.go' -not -path "./vendor/*")
@@ -57,9 +53,13 @@ clean: clean-tools ## Clean
## Release:
.PHONY: bump-version
bump-version: $(standard-version) ## Bump app version
$Q ./scripts/bump_version.sh
.PHONY: release-dry-run
release-dry-run:
pnpm dlx commit-and-tag-version --dry-run --skip
.PHONY: release
release:
pnpm dlx commit-and-tag-version -s
.PHONY: site
site: ## Build site

View File

@@ -1,12 +1,10 @@
<p align="center">
<img src="https://raw.githubusercontent.com/filebrowser/logo/master/banner.png" width="550"/>
<img src="https://raw.githubusercontent.com/filebrowser/filebrowser/master/branding/banner.png" width="550"/>
</p>
[![Build](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml/badge.svg)](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/filebrowser/filebrowser)](https://goreportcard.com/report/github.com/filebrowser/filebrowser)
[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/filebrowser/filebrowser)
[![Go Report Card](https://goreportcard.com/badge/github.com/filebrowser/filebrowser/v2)](https://goreportcard.com/report/github.com/filebrowser/filebrowser/v2)
[![Version](https://img.shields.io/github/release/filebrowser/filebrowser.svg)](https://github.com/filebrowser/filebrowser/releases/latest)
[![Chat IRC](https://img.shields.io/badge/freenode-%23filebrowser-blue.svg)](http://webchat.freenode.net/?channels=%23filebrowser)
File Browser provides a file managing interface within a specified directory and it can be used to upload, delete, preview and edit your files. It is a **create-your-own-cloud**-kind of software where you can just install it on your server, direct it to a path and access your files through a nice web interface.

View File

@@ -8,6 +8,7 @@ import (
"net/http"
"os"
"os/exec"
"slices"
"strings"
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
@@ -123,7 +124,7 @@ func (a *HookAuth) GetValues(s string) {
s = strings.ReplaceAll(s, "\r\n", "\n")
// iterate input lines
for _, val := range strings.Split(s, "\n") {
for val := range strings.Lines(s) {
v := strings.SplitN(val, "=", 2)
// skips non key and value format
@@ -266,13 +267,7 @@ var validHookFields = []string{
// IsValid checks if the provided field is on the valid fields list
func (hf *hookFields) IsValid(field string) bool {
for _, val := range validHookFields {
if field == val {
return true
}
}
return false
return slices.Contains(validHookFields, field)
}
// GetString returns the string value or provided default

BIN
branding/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

1
branding/banner.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

BIN
branding/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

1
branding/icon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="700" height="700" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd"><defs><style>.prefix__fil1{fill:#fefefe}.prefix__fil6{fill:#006498}.prefix__fil5{fill:#bdeaff}</style></defs><g id="prefix__Layer_x0020_1"><path d="M80 0h540c44 0 80 36 80 80v540c0 44-36 80-80 80H80c-44 0-80-36-80-80V80C0 36 36 0 80 0z" fill="#455a64"/><path class="prefix__fil1" d="M350 71c154 0 279 125 279 279S504 629 350 629 71 504 71 350 196 71 350 71z"/><path d="M475 236l118 151c3 116-149 252-292 198l-76-99 114-156s138-95 136-94z" fill="#332c2b" fill-opacity=".149"/><path d="M231 211h208l38 24v246c0 5-3 8-8 8H231c-5 0-8-3-8-8V219c0-5 3-8 8-8z" fill="#2bbcff"/><path d="M231 211h208l38 24v2l-37-23H231c-4 0-7 3-7 7v263c-1-1-1-2-1-3V219c0-5 3-8 8-8z" fill="#53c6fc"/><path class="prefix__fil5" d="M305 212h113v98H305zM255 363h189c3 0 5 2 5 4v116H250V367c0-2 2-4 5-4z"/><path class="prefix__fil6" d="M250 470h199v13H250zM380 226h10c3 0 6 2 6 5v40c0 3-3 6-6 6h-10c-3 0-6-3-6-6v-40c0-3 3-5 6-5z"/><path class="prefix__fil1" d="M254 226c10 0 17 7 17 17 0 9-7 16-17 16-9 0-17-7-17-16 0-10 8-17 17-17z"/><path class="prefix__fil6" d="M267 448h165c2 0 3 1 3 3 0 1-1 3-3 3H267c-2 0-3-2-3-3 0-2 1-3 3-3zM267 415h165c2 0 3 1 3 3 0 1-1 2-3 2H267c-2 0-3-1-3-2 0-2 1-3 3-3zM267 381h165c2 0 3 2 3 3 0 2-1 3-3 3H267c-2 0-3-1-3-3 0-1 1-3 3-3z"/><path class="prefix__fil1" d="M236 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5zM463 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5z"/><path class="prefix__fil6" d="M305 212h-21v98h21z"/><path d="M477 479v2c0 5-3 8-8 8H231c-5 0-8-3-8-8v-2c0 4 3 8 8 8h238c5 0 8-4 8-8z" fill="#0ea5eb"/><path d="M350 70c155 0 280 125 280 280S505 630 350 630 70 505 70 350 195 70 350 70zm0 46c129 0 234 105 234 234S479 584 350 584 116 479 116 350s105-234 234-234z" fill="#2979ff"/></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
branding/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

1
branding/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="560" height="560" version="1.1" id="prefix__svg44" clip-rule="evenodd" fill-rule="evenodd" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision"><defs id="prefix__defs4"><style type="text/css" id="style2">.prefix__fil1{fill:#fefefe}.prefix__fil6{fill:#006498}.prefix__fil5{fill:#bdeaff}</style></defs><g id="prefix__g85" transform="translate(-70 -70)"><path class="prefix__fil1" d="M350 71c154 0 279 125 279 279S504 629 350 629 71 504 71 350 196 71 350 71z" id="prefix__path9" fill="#fefefe"/><path d="M475 236l118 151c3 116-149 252-292 198l-76-99 114-156s138-95 136-94z" id="prefix__path11" fill="#332c2b" fill-opacity=".149"/><path d="M231 211h208l38 24v246c0 5-3 8-8 8H231c-5 0-8-3-8-8V219c0-5 3-8 8-8z" id="prefix__path13" fill="#2bbcff"/><path d="M231 211h208l38 24v2l-37-23H231c-4 0-7 3-7 7v263c-1-1-1-2-1-3V219c0-5 3-8 8-8z" id="prefix__path15" fill="#53c6fc"/><path class="prefix__fil5" id="prefix__polygon17" fill="#bdeaff" d="M305 212h113v98H305z"/><path class="prefix__fil5" d="M255 363h189c3 0 5 2 5 4v116H250V367c0-2 2-4 5-4z" id="prefix__path19" fill="#bdeaff"/><path class="prefix__fil6" id="prefix__polygon21" fill="#006498" d="M250 470h199v13H250z"/><path class="prefix__fil6" d="M380 226h10c3 0 6 2 6 5v40c0 3-3 6-6 6h-10c-3 0-6-3-6-6v-40c0-3 3-5 6-5z" id="prefix__path23" fill="#006498"/><path class="prefix__fil1" d="M254 226c10 0 17 7 17 17 0 9-7 16-17 16-9 0-17-7-17-16 0-10 8-17 17-17z" id="prefix__path25" fill="#fefefe"/><path class="prefix__fil6" d="M267 448h165c2 0 3 1 3 3 0 1-1 3-3 3H267c-2 0-3-2-3-3 0-2 1-3 3-3z" id="prefix__path27" fill="#006498"/><path class="prefix__fil6" d="M267 415h165c2 0 3 1 3 3 0 1-1 2-3 2H267c-2 0-3-1-3-2 0-2 1-3 3-3z" id="prefix__path29" fill="#006498"/><path class="prefix__fil6" d="M267 381h165c2 0 3 2 3 3 0 2-1 3-3 3H267c-2 0-3-1-3-3 0-1 1-3 3-3z" id="prefix__path31" fill="#006498"/><path class="prefix__fil1" d="M236 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5z" id="prefix__path33" fill="#fefefe"/><path class="prefix__fil1" d="M463 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5z" id="prefix__path35" fill="#fefefe"/><path class="prefix__fil6" id="prefix__polygon37" fill="#006498" d="M305 212h-21v98h21z"/><path d="M477 479v2c0 5-3 8-8 8H231c-5 0-8-3-8-8v-2c0 4 3 8 8 8h238c5 0 8-4 8-8z" id="prefix__path39" fill="#0ea5eb"/><path d="M350 70c155 0 280 125 280 280S505 630 350 630 70 505 70 350 195 70 350 70zm0 46c129 0 234 105 234 234S479 584 350 584 116 479 116 350s105-234 234-234z" id="prefix__path41" fill="#2979ff"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -221,6 +221,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
fmt.Fprintf(w, "\tFile Creation Mode:\t%O\n", set.FileMode)
fmt.Fprintf(w, "\tDirectory Creation Mode:\t%O\n", set.DirMode)
fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " "))
fmt.Fprintf(w, "\tAce editor syntax highlighting theme:\t%s\n", set.Defaults.AceEditorTheme)
fmt.Fprintf(w, "\tSorting:\n")
fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)
fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc)

View File

@@ -424,9 +424,10 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
Defaults: settings.UserDefaults{
Scope: ".",
Locale: "en",
SingleClick: false,
Scope: ".",
Locale: "en",
SingleClick: false,
AceEditorTheme: getStringParam(flags, "defaults.aceEditorTheme"),
Perm: users.Permissions{
Admin: false,
Execute: true,

View File

@@ -77,6 +77,9 @@ func addUserFlags(flags *pflag.FlagSet) {
flags.String("locale", "en", "locale for users")
flags.String("viewMode", string(users.ListViewMode), "view mode for users")
flags.Bool("singleClick", false, "use single clicks only")
flags.Bool("dateFormat", false, "use date format (true for absolute time, false for relative)")
flags.Bool("hideDotfiles", false, "hide dotfiles")
flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users")
}
func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
@@ -108,6 +111,8 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
defaults.ViewMode, err = getViewMode(flags)
case "singleClick":
defaults.SingleClick, err = getBool(flags, flag.Name)
case "aceEditorTheme":
defaults.AceEditorTheme, err = getString(flags, flag.Name)
case "perm.admin":
defaults.Perm.Admin, err = getBool(flags, flag.Name)
case "perm.execute":

View File

@@ -36,10 +36,22 @@ var usersAddCmd = &cobra.Command{
return err
}
dateFormat, err := getBool(cmd.Flags(), "dateFormat")
if err != nil {
return err
}
hideDotfiles, err := getBool(cmd.Flags(), "hideDotfiles")
if err != nil {
return err
}
user := &users.User{
Username: args[0],
Password: password,
LockPassword: lockPassword,
DateFormat: dateFormat,
HideDotfiles: hideDotfiles,
}
s.Defaults.Apply(user)

View File

@@ -76,6 +76,14 @@ options you want to change.`,
if err != nil {
return err
}
user.DateFormat, err = getBool(flags, "dateFormat")
if err != nil {
return err
}
user.HideDotfiles, err = getBool(flags, "hideDotfiles")
if err != nil {
return err
}
if newUsername != "" {
user.Username = newUsername

View File

@@ -14,7 +14,7 @@ import (
"github.com/asdine/storm/v3"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
yaml "gopkg.in/yaml.v2"
yaml "gopkg.in/yaml.v3"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"

View File

@@ -1,5 +1,4 @@
//go:build !dev
// +build !dev
package frontend

View File

@@ -1,5 +1,4 @@
//go:build dev
// +build dev
package frontend

View File

@@ -12,7 +12,11 @@
<link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg" />
<link rel="shortcut icon" href="/img/icons/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/img/icons/apple-touch-icon.png" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/img/icons/apple-touch-icon.png"
/>
<meta name="apple-mobile-web-app-title" content="File Browser" />
<!-- Add to home screen for Android and modern mobile browsers -->

View File

@@ -4,37 +4,35 @@
"private": true,
"type": "module",
"engines": {
"node": ">=22.0.0",
"pnpm": ">=9.0.0"
"node": ">=24.0.0",
"pnpm": ">=10.0.0"
},
"scripts": {
"dev": "vite dev",
"build": "pnpm run typecheck && vite build",
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
"typecheck": "vue-tsc -p ./tsconfig.tsc.json --noEmit",
"typecheck": "vue-tsc -p ./tsconfig.app.json --noEmit",
"lint": "eslint src/",
"lint:fix": "eslint --fix src/",
"format": "prettier --write .",
"test": "playwright test"
"format": "prettier --write ."
},
"dependencies": {
"@chenfengyuan/vue-number-input": "^2.0.1",
"@vueuse/core": "^12.5.0",
"@vueuse/integrations": "^12.5.0",
"@vueuse/core": "^14.0.0",
"@vueuse/integrations": "^14.0.0",
"ace-builds": "^1.43.2",
"core-js": "^3.44.0",
"dayjs": "^1.11.13",
"dompurify": "^3.2.6",
"epubjs": "^0.3.93",
"filesize": "^10.1.1",
"filesize": "^11.0.13",
"js-base64": "^3.7.7",
"jwt-decode": "^4.0.0",
"lodash-es": "^4.17.21",
"marked": "^15.0.6",
"marked": "^17.0.0",
"material-icons": "^1.13.14",
"normalize.css": "^8.0.1",
"pinia": "^2.3.1",
"pretty-bytes": "^6.1.1",
"pinia": "^3.0.4",
"pretty-bytes": "^7.1.0",
"qrcode.vue": "^3.6.0",
"tus-js-client": "^4.3.1",
"utif": "^3.1.0",
@@ -50,30 +48,28 @@
"vue-toastification": "^2.0.0-rc.5"
},
"devDependencies": {
"@intlify/unplugin-vue-i18n": "^6.0.8",
"@playwright/test": "^1.54.1",
"@tsconfig/node22": "^22.0.2",
"@intlify/unplugin-vue-i18n": "^11.0.1",
"@tsconfig/node24": "^24.0.2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.10.10",
"@types/node": "^24.10.1",
"@typescript-eslint/eslint-plugin": "^8.37.0",
"@vitejs/plugin-legacy": "^6.0.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-legacy": "^7.2.1",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/tsconfig": "^0.7.0",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.21",
"concurrently": "^9.2.0",
"eslint": "^9.31.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-vue": "^9.24.0",
"jsdom": "^26.1.0",
"eslint-plugin-vue": "^10.5.1",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"terser": "^5.43.1",
"vite": "^6.1.6",
"vite-plugin-compression2": "^1.0.0",
"vue-tsc": "^2.2.0"
"typescript": "^5.9.3",
"vite": "^7.2.2",
"vite-plugin-compression2": "^2.3.1",
"vue-tsc": "^3.1.3"
},
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0"
"packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c"
}

View File

@@ -1,80 +0,0 @@
import { defineConfig, devices } from "@playwright/test";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://127.0.0.1:5173",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
/* Set default locale to English (US) */
locale: "en-US",
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
// {
// name: "webkit",
// use: { ...devices["Desktop Safari"] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: {
command: "npm run dev",
url: "http://127.0.0.1:5173",
reuseExistingServer: !process.env.CI,
},
});

4237
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,147 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xml:space="preserve"
width="560"
height="560"
version="1.1"
style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
viewBox="0 0 560 560"
id="svg44"
sodipodi:docname="icon_raw.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
inkscape:export-filename="/home/umarcor/filebrowser/logo/icon_raw.svg.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"><metadata
id="metadata48"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="711"
id="namedview46"
showgrid="false"
inkscape:zoom="0.33714286"
inkscape:cx="-172.33051"
inkscape:cy="280"
inkscape:window-x="0"
inkscape:window-y="20"
inkscape:window-maximized="1"
inkscape:current-layer="svg44" />
<defs
id="defs4">
<style
type="text/css"
id="style2">
<![CDATA[
.fil1 {fill:#FEFEFE}
.fil6 {fill:#006498}
.fil7 {fill:#0EA5EB}
.fil8 {fill:#2979FF}
.fil3 {fill:#2BBCFF}
.fil0 {fill:#455A64}
.fil4 {fill:#53C6FC}
.fil5 {fill:#BDEAFF}
.fil2 {fill:#332C2B;fill-opacity:0.149020}
]]>
</style>
</defs>
<g
id="g85"
transform="translate(-70,-70)"><path
class="fil1"
d="M 350,71 C 504,71 629,196 629,350 629,504 504,629 350,629 196,629 71,504 71,350 71,196 196,71 350,71 Z"
id="path9"
inkscape:connector-curvature="0"
style="fill:#fefefe" /><path
class="fil2"
d="M 475,236 593,387 C 596,503 444,639 301,585 L 225,486 339,330 c 0,0 138,-95 136,-94 z"
id="path11"
inkscape:connector-curvature="0"
style="fill:#332c2b;fill-opacity:0.14902003" /><path
class="fil3"
d="m 231,211 h 208 l 38,24 v 246 c 0,5 -3,8 -8,8 H 231 c -5,0 -8,-3 -8,-8 V 219 c 0,-5 3,-8 8,-8 z"
id="path13"
inkscape:connector-curvature="0"
style="fill:#2bbcff" /><path
class="fil4"
d="m 231,211 h 208 l 38,24 v 2 L 440,214 H 231 c -4,0 -7,3 -7,7 v 263 c -1,-1 -1,-2 -1,-3 V 219 c 0,-5 3,-8 8,-8 z"
id="path15"
inkscape:connector-curvature="0"
style="fill:#53c6fc" /><polygon
class="fil5"
points="305,212 418,212 418,310 305,310 "
id="polygon17"
style="fill:#bdeaff" /><path
class="fil5"
d="m 255,363 h 189 c 3,0 5,2 5,4 V 483 H 250 V 367 c 0,-2 2,-4 5,-4 z"
id="path19"
inkscape:connector-curvature="0"
style="fill:#bdeaff" /><polygon
class="fil6"
points="250,470 449,470 449,483 250,483 "
id="polygon21"
style="fill:#006498" /><path
class="fil6"
d="m 380,226 h 10 c 3,0 6,2 6,5 v 40 c 0,3 -3,6 -6,6 h -10 c -3,0 -6,-3 -6,-6 v -40 c 0,-3 3,-5 6,-5 z"
id="path23"
inkscape:connector-curvature="0"
style="fill:#006498" /><path
class="fil1"
d="m 254,226 c 10,0 17,7 17,17 0,9 -7,16 -17,16 -9,0 -17,-7 -17,-16 0,-10 8,-17 17,-17 z"
id="path25"
inkscape:connector-curvature="0"
style="fill:#fefefe" /><path
class="fil6"
d="m 267,448 h 165 c 2,0 3,1 3,3 v 0 c 0,1 -1,3 -3,3 H 267 c -2,0 -3,-2 -3,-3 v 0 c 0,-2 1,-3 3,-3 z"
id="path27"
inkscape:connector-curvature="0"
style="fill:#006498" /><path
class="fil6"
d="m 267,415 h 165 c 2,0 3,1 3,3 v 0 c 0,1 -1,2 -3,2 H 267 c -2,0 -3,-1 -3,-2 v 0 c 0,-2 1,-3 3,-3 z"
id="path29"
inkscape:connector-curvature="0"
style="fill:#006498" /><path
class="fil6"
d="m 267,381 h 165 c 2,0 3,2 3,3 v 0 c 0,2 -1,3 -3,3 H 267 c -2,0 -3,-1 -3,-3 v 0 c 0,-1 1,-3 3,-3 z"
id="path31"
inkscape:connector-curvature="0"
style="fill:#006498" /><path
class="fil1"
d="m 236,472 c 3,0 5,2 5,5 0,2 -2,4 -5,4 -3,0 -5,-2 -5,-4 0,-3 2,-5 5,-5 z"
id="path33"
inkscape:connector-curvature="0"
style="fill:#fefefe" /><path
class="fil1"
d="m 463,472 c 3,0 5,2 5,5 0,2 -2,4 -5,4 -3,0 -5,-2 -5,-4 0,-3 2,-5 5,-5 z"
id="path35"
inkscape:connector-curvature="0"
style="fill:#fefefe" /><polygon
class="fil6"
points="305,212 284,212 284,310 305,310 "
id="polygon37"
style="fill:#006498" /><path
class="fil7"
d="m 477,479 v 2 c 0,5 -3,8 -8,8 H 231 c -5,0 -8,-3 -8,-8 v -2 c 0,4 3,8 8,8 h 238 c 5,0 8,-4 8,-8 z"
id="path39"
inkscape:connector-curvature="0"
style="fill:#0ea5eb" /><path
class="fil8"
d="M 350,70 C 505,70 630,195 630,350 630,505 505,630 350,630 195,630 70,505 70,350 70,195 195,70 350,70 Z m 0,46 C 479,116 584,221 584,350 584,479 479,584 350,584 221,584 116,479 116,350 116,221 221,116 350,116 Z"
id="path41"
inkscape:connector-curvature="0"
style="fill:#2979ff" /></g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="560" height="560" version="1.1" id="prefix__svg44" clip-rule="evenodd" fill-rule="evenodd" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision"><defs id="prefix__defs4"><style type="text/css" id="style2">.prefix__fil1{fill:#fefefe}.prefix__fil6{fill:#006498}.prefix__fil5{fill:#bdeaff}</style></defs><g id="prefix__g85" transform="translate(-70 -70)"><path class="prefix__fil1" d="M350 71c154 0 279 125 279 279S504 629 350 629 71 504 71 350 196 71 350 71z" id="prefix__path9" fill="#fefefe"/><path d="M475 236l118 151c3 116-149 252-292 198l-76-99 114-156s138-95 136-94z" id="prefix__path11" fill="#332c2b" fill-opacity=".149"/><path d="M231 211h208l38 24v246c0 5-3 8-8 8H231c-5 0-8-3-8-8V219c0-5 3-8 8-8z" id="prefix__path13" fill="#2bbcff"/><path d="M231 211h208l38 24v2l-37-23H231c-4 0-7 3-7 7v263c-1-1-1-2-1-3V219c0-5 3-8 8-8z" id="prefix__path15" fill="#53c6fc"/><path class="prefix__fil5" id="prefix__polygon17" fill="#bdeaff" d="M305 212h113v98H305z"/><path class="prefix__fil5" d="M255 363h189c3 0 5 2 5 4v116H250V367c0-2 2-4 5-4z" id="prefix__path19" fill="#bdeaff"/><path class="prefix__fil6" id="prefix__polygon21" fill="#006498" d="M250 470h199v13H250z"/><path class="prefix__fil6" d="M380 226h10c3 0 6 2 6 5v40c0 3-3 6-6 6h-10c-3 0-6-3-6-6v-40c0-3 3-5 6-5z" id="prefix__path23" fill="#006498"/><path class="prefix__fil1" d="M254 226c10 0 17 7 17 17 0 9-7 16-17 16-9 0-17-7-17-16 0-10 8-17 17-17z" id="prefix__path25" fill="#fefefe"/><path class="prefix__fil6" d="M267 448h165c2 0 3 1 3 3 0 1-1 3-3 3H267c-2 0-3-2-3-3 0-2 1-3 3-3z" id="prefix__path27" fill="#006498"/><path class="prefix__fil6" d="M267 415h165c2 0 3 1 3 3 0 1-1 2-3 2H267c-2 0-3-1-3-2 0-2 1-3 3-3z" id="prefix__path29" fill="#006498"/><path class="prefix__fil6" d="M267 381h165c2 0 3 2 3 3 0 2-1 3-3 3H267c-2 0-3-1-3-3 0-1 1-3 3-3z" id="prefix__path31" fill="#006498"/><path class="prefix__fil1" d="M236 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5z" id="prefix__path33" fill="#fefefe"/><path class="prefix__fil1" d="M463 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5z" id="prefix__path35" fill="#fefefe"/><path class="prefix__fil6" id="prefix__polygon37" fill="#006498" d="M305 212h-21v98h21z"/><path d="M477 479v2c0 5-3 8-8 8H231c-5 0-8-3-8-8v-2c0 4 3 8 8 8h238c5 0 8-4 8-8z" id="prefix__path39" fill="#0ea5eb"/><path d="M350 70c155 0 280 125 280 280S505 630 350 630 70 505 70 350 195 70 350 70zm0 46c129 0 234 105 234 234S479 584 350 584 116 479 116 350s105-234 234-234z" id="prefix__path41" fill="#2979ff"/></g></svg>

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -18,9 +18,17 @@
<meta name="robots" content="noindex,nofollow" />
<link rel="icon" type="image/svg+xml" href="[{[ .StaticURL ]}]/img/icons/favicon.svg" />
<link
rel="icon"
type="image/svg+xml"
href="[{[ .StaticURL ]}]/img/icons/favicon.svg"
/>
<link rel="shortcut icon" href="[{[ .StaticURL ]}]/img/icons/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
/>
<meta name="apple-mobile-web-app-title" content="File Browser" />
<!-- Add to home screen for Android and modern mobile browsers -->

View File

@@ -91,3 +91,21 @@ export function createURL(endpoint: string, searchParams = {}): string {
return url.toString();
}
export function setSafeTimeout(callback: () => void, delay: number): number {
const MAX_DELAY = 86_400_000;
let remaining = delay;
function scheduleNext(): number {
if (remaining <= MAX_DELAY) {
return window.setTimeout(callback, remaining);
} else {
return window.setTimeout(() => {
remaining -= MAX_DELAY;
scheduleNext();
}, MAX_DELAY);
}
}
return scheduleNext();
}

View File

@@ -1,6 +1,3 @@
<!-- This component taken directly from vue-simple-progress
since it didnt support Vue 3 but the component itself does
https://raw.githubusercontent.com/dzwillia/vue-simple-progress/master/src/components/Progress.vue -->
<template>
<div>
<div
@@ -44,182 +41,164 @@ https://raw.githubusercontent.com/dzwillia/vue-simple-progress/master/src/compon
</div>
</template>
<script>
// We're leaving this untouched as you can read in the beginning
const isNumber = function (n) {
return !isNaN(parseFloat(n)) && isFinite(n);
<script setup lang="ts">
import { computed } from "vue";
const isNumber = (n: number | string): boolean => {
return !isNaN(parseFloat(n as string)) && isFinite(n as number);
};
export default {
name: "progress-bar",
props: {
val: {
default: 0,
},
max: {
default: 100,
},
size: {
// either a number (pixel width/height) or 'tiny', 'small',
// 'medium', 'large', 'huge', 'massive' for common sizes
default: 3,
},
"bg-color": {
type: String,
default: "#eee",
},
"bar-color": {
type: String,
default: "#2196f3", // match .blue color to Material Design's 'Blue 500' color
},
"bar-transition": {
type: String,
default: "all 0.5s ease",
},
"bar-border-radius": {
type: Number,
default: 0,
},
spacing: {
type: Number,
default: 4,
},
text: {
type: String,
default: "",
},
"text-align": {
type: String,
default: "center", // 'left', 'right'
},
"text-position": {
type: String,
default: "bottom", // 'bottom', 'top', 'middle', 'inside'
},
"font-size": {
type: Number,
default: 13,
},
"text-fg-color": {
type: String,
default: "#222",
},
},
computed: {
pct() {
let pct = (this.val / this.max) * 100;
pct = pct.toFixed(2);
return Math.min(pct, this.max);
},
size_px() {
switch (this.size) {
case "tiny":
return 2;
case "small":
return 4;
case "medium":
return 8;
case "large":
return 12;
case "big":
return 16;
case "huge":
return 32;
case "massive":
return 64;
}
const props = withDefaults(
defineProps<{
val?: number;
max?: number;
size?: number | string;
bgColor?: string;
barColor?: string;
barTransition?: string;
barBorderRadius?: number;
spacing?: number;
text?: string;
textAlign?: string;
textPosition?: string;
fontSize?: number;
textFgColor?: string;
}>(),
{
val: 0,
max: 100,
size: 3,
bgColor: "#eee",
barColor: "#2196f3",
barTransition: "all 0.5s ease",
barBorderRadius: 0,
spacing: 4,
text: "",
textAlign: "center",
textPosition: "bottom",
fontSize: 13,
textFgColor: "#222",
}
);
return isNumber(this.size) ? this.size : 32;
},
text_padding() {
switch (this.size) {
case "tiny":
case "small":
case "medium":
case "large":
case "big":
case "huge":
case "massive":
return Math.min(Math.max(Math.ceil(this.size_px / 8), 3), 12);
}
const pct = computed(() => {
const pct = (props.val / props.max) * 100;
const pctFixed = pct.toFixed(2);
return Math.min(parseFloat(pctFixed), props.max);
});
return isNumber(this.spacing) ? this.spacing : 4;
},
text_font_size() {
switch (this.size) {
case "tiny":
case "small":
case "medium":
case "large":
case "big":
case "huge":
case "massive":
return Math.min(Math.max(Math.ceil(this.size_px * 1.4), 11), 32);
}
const size_px = computed(() => {
switch (props.size) {
case "tiny":
return 2;
case "small":
return 4;
case "medium":
return 8;
case "large":
return 12;
case "big":
return 16;
case "huge":
return 32;
case "massive":
return 64;
}
return isNumber(this.fontSize) ? this.fontSize : 13;
},
progress_style() {
const style = {
background: this.bgColor,
};
return isNumber(props.size) ? (props.size as number) : 32;
});
if (this.textPosition == "middle" || this.textPosition == "inside") {
style["position"] = "relative";
style["min-height"] = this.size_px + "px";
style["z-index"] = "-2";
}
const text_padding = computed(() => {
switch (props.size) {
case "tiny":
case "small":
case "medium":
case "large":
case "big":
case "huge":
case "massive":
return Math.min(Math.max(Math.ceil(size_px.value / 8), 3), 12);
}
if (this.barBorderRadius > 0) {
style["border-radius"] = this.barBorderRadius + "px";
}
return isNumber(props.spacing) ? props.spacing : 4;
});
return style;
},
bar_style() {
const style = {
background: this.barColor,
width: this.pct + "%",
height: this.size_px + "px",
transition: this.barTransition,
};
const text_font_size = computed(() => {
switch (props.size) {
case "tiny":
case "small":
case "medium":
case "large":
case "big":
case "huge":
case "massive":
return Math.min(Math.max(Math.ceil(size_px.value * 1.4), 11), 32);
}
if (this.barBorderRadius > 0) {
style["border-radius"] = this.barBorderRadius + "px";
}
return isNumber(props.fontSize) ? props.fontSize : 13;
});
if (this.textPosition == "middle" || this.textPosition == "inside") {
style["position"] = "absolute";
style["top"] = "0";
style["height"] = "100%";
((style["min-height"] = this.size_px + "px"),
(style["z-index"] = "-1"));
}
const progress_style = computed(() => {
const style: Record<string, string> = {
background: props.bgColor,
};
return style;
},
text_style() {
const style = {
color: this.textFgColor,
"font-size": this.text_font_size + "px",
"text-align": this.textAlign,
};
if (props.textPosition == "middle" || props.textPosition == "inside") {
style["position"] = "relative";
style["min-height"] = size_px.value + "px";
style["z-index"] = "-2";
}
if (
this.textPosition == "top" ||
this.textPosition == "middle" ||
this.textPosition == "inside"
)
style["padding-bottom"] = this.text_padding + "px";
if (
this.textPosition == "bottom" ||
this.textPosition == "middle" ||
this.textPosition == "inside"
)
style["padding-top"] = this.text_padding + "px";
if (props.barBorderRadius > 0) {
style["border-radius"] = props.barBorderRadius + "px";
}
return style;
},
},
};
return style;
});
const bar_style = computed(() => {
const style: Record<string, string> = {
background: props.barColor,
width: pct.value + "%",
height: size_px.value + "px",
transition: props.barTransition,
};
if (props.barBorderRadius > 0) {
style["border-radius"] = props.barBorderRadius + "px";
}
if (props.textPosition == "middle" || props.textPosition == "inside") {
style["position"] = "absolute";
style["top"] = "0";
style["height"] = "100%";
style["min-height"] = size_px.value + "px";
style["z-index"] = "-1";
}
return style;
});
const text_style = computed(() => {
const style: Record<string, string> = {
color: props.textFgColor,
"font-size": text_font_size.value + "px",
"text-align": props.textAlign,
};
if (
props.textPosition == "top" ||
props.textPosition == "middle" ||
props.textPosition == "inside"
)
style["padding-bottom"] = text_padding.value + "px";
if (
props.textPosition == "bottom" ||
props.textPosition == "middle" ||
props.textPosition == "inside"
)
style["padding-top"] = text_padding.value + "px";
return style;
});
</script>

View File

@@ -2,13 +2,13 @@
<div
class="shell"
:class="{ ['shell--hidden']: !showShell }"
:style="{ height: `${this.shellHeight}em`, direction: 'ltr' }"
:style="{ height: `${shellHeight}em`, direction: 'ltr' }"
>
<div
@pointerdown="startDrag()"
@pointerup="stopDrag()"
class="shell__divider"
:style="this.shellDrag ? { background: `${checkTheme()}` } : ''"
:style="shellDrag ? { background: `${checkTheme()}` } : ''"
></div>
<div @click="focus" class="shell__content" ref="scrollable">
<div v-for="(c, index) in content" :key="index" class="shell__result">
@@ -39,13 +39,15 @@
<div
@pointerup="stopDrag()"
class="shell__overlay"
v-show="this.shellDrag"
v-show="shellDrag"
></div>
</div>
</template>
<script>
import { mapState, mapActions } from "pinia";
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
import { storeToRefs } from "pinia";
import { useRoute } from "vue-router";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
@@ -53,142 +55,164 @@ import { commands } from "@/api";
import { throttle } from "lodash-es";
import { theme } from "@/utils/constants";
export default {
name: "shell",
computed: {
...mapState(useLayoutStore, ["showShell"]),
...mapState(useFileStore, ["isFiles"]),
path: function () {
if (this.isFiles) {
return this.$route.path;
}
const route = useRoute();
return "";
},
},
data: () => ({
content: [],
history: [],
historyPos: 0,
canInput: true,
shellDrag: false,
shellHeight: 25,
fontsize: parseFloat(getComputedStyle(document.documentElement).fontSize),
}),
mounted() {
window.addEventListener("resize", this.resize);
},
beforeUnmount() {
window.removeEventListener("resize", this.resize);
},
methods: {
...mapActions(useLayoutStore, ["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;
const fileStore = useFileStore();
const layoutStore = useLayoutStore();
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;
const { showShell } = storeToRefs(layoutStore);
const { isFiles } = storeToRefs(fileStore);
const { toggleShell } = layoutStore;
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;
},
focus: function () {
this.$refs.input.focus();
},
historyUp() {
if (this.historyPos > 0) {
this.$refs.input.innerText = this.history[--this.historyPos];
this.focus();
}
},
historyDown() {
if (this.historyPos >= 0 && this.historyPos < this.history.length - 1) {
this.$refs.input.innerText = this.history[++this.historyPos];
this.focus();
} else {
this.historyPos = this.history.length;
this.$refs.input.innerText = "";
}
},
submit: function (event) {
const cmd = event.target.innerText.trim();
const scrollable = ref<HTMLElement | null>(null);
const input = ref<HTMLElement | null>(null);
if (cmd === "") {
return;
}
const content = ref<Array<{ text: string }>>([]);
const history = ref<string[]>([]);
const historyPos = ref(0);
const canInput = ref(true);
const shellDrag = ref(false);
const shellHeight = ref(25);
const fontsize = ref(
parseFloat(getComputedStyle(document.documentElement).fontSize)
);
if (cmd === "clear") {
this.content = [];
event.target.innerHTML = "";
return;
}
const path = computed(() => {
if (isFiles.value) {
return route.path;
}
return "";
});
if (cmd === "exit") {
event.target.innerHTML = "";
this.toggleShell();
return;
}
this.canInput = false;
event.target.innerHTML = "";
const results = {
text: `${cmd}\n\n`,
};
this.history.push(cmd);
this.historyPos = this.history.length;
this.content.push(results);
commands(
this.path,
cmd,
(event) => {
results.text += `${event.data}\n`;
this.scroll();
},
() => {
results.text = results.text
.replace(/\u001b\[[0-9;]+m/g, "") // Filter ANSI color for now
.trimEnd();
this.canInput = true;
this.$refs.input.focus();
this.scroll();
}
);
},
},
const checkTheme = () => {
if (theme == "dark") {
return "rgba(255, 255, 255, 0.4)";
}
return "rgba(127, 127, 127, 0.4)";
};
const scroll = () => {
if (scrollable.value) {
scrollable.value.scrollTop = scrollable.value.scrollHeight;
}
};
const focus = () => {
input.value?.focus();
};
const handleDrag = throttle((event: PointerEvent) => {
const top = window.innerHeight / fontsize.value - 4;
const userPos = (window.innerHeight - event.clientY) / fontsize.value;
const divider = document.querySelector(".shell__divider") as HTMLElement;
const bottom = 2.25 + (divider?.offsetHeight ?? 0) / fontsize.value;
if (userPos <= top && userPos >= bottom) {
shellHeight.value = parseFloat(userPos.toFixed(2));
}
}, 32);
const resize = throttle(() => {
const top = window.innerHeight / fontsize.value - 4;
const divider = document.querySelector(".shell__divider") as HTMLElement;
const bottom = 2.25 + (divider?.offsetHeight ?? 0) / fontsize.value;
if (shellHeight.value > top) {
shellHeight.value = top;
} else if (shellHeight.value < bottom) {
shellHeight.value = bottom;
}
}, 32);
const startDrag = () => {
document.addEventListener("pointermove", handleDrag as any);
shellDrag.value = true;
};
const stopDrag = () => {
document.removeEventListener("pointermove", handleDrag as any);
shellDrag.value = false;
};
const historyUp = () => {
if (historyPos.value > 0 && input.value) {
historyPos.value--;
input.value.innerText = history.value[historyPos.value];
focus();
}
};
const historyDown = () => {
if (
historyPos.value >= 0 &&
historyPos.value < history.value.length - 1 &&
input.value
) {
historyPos.value++;
input.value.innerText = history.value[historyPos.value];
focus();
} else {
historyPos.value = history.value.length;
if (input.value) {
input.value.innerText = "";
}
}
};
const submit = (event: Event) => {
const target = event.target as HTMLElement;
const cmd = target.innerText.trim();
if (cmd === "") {
return;
}
if (cmd === "clear") {
content.value = [];
target.innerHTML = "";
return;
}
if (cmd === "exit") {
target.innerHTML = "";
toggleShell();
return;
}
canInput.value = false;
target.innerHTML = "";
const results = {
text: `${cmd}\n\n`,
};
history.value.push(cmd);
historyPos.value = history.value.length;
content.value.push(results);
commands(
path.value,
cmd,
(event: MessageEvent) => {
results.text += `${event.data}\n`;
scroll();
},
() => {
results.text = results.text
.replace(/\u001b\[[0-9;]+m/g, "") // Filter ANSI color for now
.trimEnd();
canInput.value = true;
input.value?.focus();
scroll();
}
);
};
onMounted(() => {
window.addEventListener("resize", resize as any);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", resize as any);
});
</script>

View File

@@ -4,7 +4,7 @@
<template v-if="isLoggedIn">
<button @click="toAccountSettings" class="action">
<i class="material-icons">person</i>
<span>{{ user.username }}</span>
<span>{{ user?.username }}</span>
</button>
<button
class="action"
@@ -16,7 +16,7 @@
<span>{{ $t("sidebar.myFiles") }}</span>
</button>
<div v-if="user.perm.create">
<div v-if="user?.perm.create">
<button
@click="showHover('newDir')"
class="action"
@@ -38,7 +38,7 @@
</button>
</div>
<div v-if="user.perm.admin">
<div v-if="user?.perm.admin">
<button
class="action"
@click="toGlobalSettings"
@@ -113,9 +113,10 @@
</nav>
</template>
<script>
import { reactive } from "vue";
import { mapActions, mapState } from "pinia";
<script setup lang="ts">
import { reactive, ref, computed, watch, onUnmounted } from "vue";
import { storeToRefs } from "pinia";
import { useRoute, useRouter } from "vue-router";
import { useAuthStore } from "@/stores/auth";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
@@ -135,84 +136,85 @@ import prettyBytes from "pretty-bytes";
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
export default {
name: "sidebar",
setup() {
const usage = reactive(USAGE_DEFAULT);
return { usage, usageAbortController: new AbortController() };
},
components: {
ProgressBar,
},
inject: ["$showError"],
computed: {
...mapState(useAuthStore, ["user", "isLoggedIn"]),
...mapState(useFileStore, ["isFiles", "reload"]),
...mapState(useLayoutStore, ["currentPromptName"]),
active() {
return this.currentPromptName === "sidebar";
},
signup: () => signup,
version: () => version,
disableExternal: () => disableExternal,
disableUsedPercentage: () => disableUsedPercentage,
canLogout: () => !noAuth && loginPage,
},
methods: {
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
abortOngoingFetchUsage() {
this.usageAbortController.abort();
},
async fetchUsage() {
const path = this.$route.path.endsWith("/")
? this.$route.path
: this.$route.path + "/";
let usageStats = USAGE_DEFAULT;
if (this.disableUsedPercentage) {
return Object.assign(this.usage, usageStats);
}
try {
this.abortOngoingFetchUsage();
this.usageAbortController = new AbortController();
const usage = await api.usage(path, this.usageAbortController.signal);
usageStats = {
used: prettyBytes(usage.used, { binary: true }),
total: prettyBytes(usage.total, { binary: true }),
usedPercentage: Math.round((usage.used / usage.total) * 100),
};
} finally {
return Object.assign(this.usage, usageStats);
}
},
toRoot() {
this.$router.push({ path: "/files" });
this.closeHovers();
},
toAccountSettings() {
this.$router.push({ path: "/settings/profile" });
this.closeHovers();
},
toGlobalSettings() {
this.$router.push({ path: "/settings/global" });
this.closeHovers();
},
help() {
this.showHover("help");
},
logout: auth.logout,
},
watch: {
$route: {
handler(to) {
if (to.path.includes("/files")) {
this.fetchUsage();
}
},
immediate: true,
},
},
unmounted() {
this.abortOngoingFetchUsage();
},
const route = useRoute();
const router = useRouter();
const authStore = useAuthStore();
const fileStore = useFileStore();
const layoutStore = useLayoutStore();
const { user, isLoggedIn } = storeToRefs(authStore);
const { isFiles } = storeToRefs(fileStore);
const { currentPromptName } = storeToRefs(layoutStore);
const { closeHovers, showHover } = layoutStore;
const usage = reactive(USAGE_DEFAULT);
const usageAbortController = ref(new AbortController());
const active = computed(() => {
return currentPromptName.value === "sidebar";
});
const canLogout = !noAuth && loginPage;
const abortOngoingFetchUsage = () => {
usageAbortController.value.abort();
};
const fetchUsage = async () => {
const path = route.path.endsWith("/") ? route.path : route.path + "/";
let usageStats = USAGE_DEFAULT;
if (disableUsedPercentage) {
return Object.assign(usage, usageStats);
}
try {
abortOngoingFetchUsage();
usageAbortController.value = new AbortController();
const usageData = await api.usage(path, usageAbortController.value.signal);
usageStats = {
used: prettyBytes(usageData.used, { binary: true }),
total: prettyBytes(usageData.total, { binary: true }),
usedPercentage: Math.round((usageData.used / usageData.total) * 100),
};
} finally {
return Object.assign(usage, usageStats);
}
};
const toRoot = () => {
router.push({ path: "/files" });
closeHovers();
};
const toAccountSettings = () => {
router.push({ path: "/settings/profile" });
closeHovers();
};
const toGlobalSettings = () => {
router.push({ path: "/settings/global" });
closeHovers();
};
const help = () => {
showHover("help");
};
const logout = () => {
auth.logout();
};
watch(
() => route.path,
(newPath) => {
if (newPath.includes("/files")) {
fetchUsage();
}
},
{ immediate: true }
);
onUnmounted(() => {
abortOngoingFetchUsage();
});
</script>

View File

@@ -8,7 +8,7 @@
<p>{{ $t("prompts.copyMessage") }}</p>
<file-list
ref="fileList"
@update:selected="(val) => (dest = val)"
@update:selected="(val: string) => (dest = val)"
tabindex="1"
/>
</div>
@@ -17,10 +17,10 @@
class="card-action"
style="display: flex; align-items: center; justify-content: space-between"
>
<template v-if="user.perm.create">
<template v-if="user?.perm.create">
<button
class="button button--flat"
@click="$refs.fileList.createDir()"
@click="fileList?.createDir()"
:aria-label="$t('sidebar.newFolder')"
:title="$t('sidebar.newFolder')"
style="justify-self: left"
@@ -53,8 +53,10 @@
</div>
</template>
<script>
import { mapActions, mapState, mapWritableState } from "pinia";
<script setup lang="ts">
import { ref, inject } from "vue";
import { storeToRefs } from "pinia";
import { useRoute, useRouter } from "vue-router";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { useAuthStore } from "@/stores/auth";
@@ -64,90 +66,84 @@ import buttons from "@/utils/buttons";
import * as upload from "@/utils/upload";
import { removePrefix } from "@/api/utils";
export default {
name: "copy",
components: { FileList },
data: function () {
return {
current: window.location.pathname,
dest: null,
};
},
inject: ["$showError"],
computed: {
...mapState(useFileStore, ["req", "selected"]),
...mapState(useAuthStore, ["user"]),
...mapWritableState(useFileStore, ["reload", "preselect"]),
},
methods: {
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
copy: async function (event) {
event.preventDefault();
const items = [];
const route = useRoute();
const router = useRouter();
const $showError = inject<(error: unknown) => void>("$showError");
// Create a new promise for each file.
for (const item of this.selected) {
items.push({
from: this.req.items[item].url,
to: this.dest + encodeURIComponent(this.req.items[item].name),
name: this.req.items[item].name,
});
}
const fileStore = useFileStore();
const layoutStore = useLayoutStore();
const authStore = useAuthStore();
const action = async (overwrite, rename) => {
buttons.loading("copy");
const { req, selected } = storeToRefs(fileStore);
const { user } = storeToRefs(authStore);
const { showHover, closeHovers } = layoutStore;
await api
.copy(items, overwrite, rename)
.then(() => {
buttons.success("copy");
this.preselect = removePrefix(items[0].to);
const fileList = ref<InstanceType<typeof FileList> | null>(null);
const dest = ref<string | null>(null);
if (this.$route.path === this.dest) {
this.reload = true;
const copy = async (event: Event) => {
event.preventDefault();
const items: Array<{ from: string; to: string; name: string }> = [];
return;
}
// Create a new promise for each file.
for (const item of selected.value) {
items.push({
from: req.value!.items[item].url,
to: dest.value! + encodeURIComponent(req.value!.items[item].name),
name: req.value!.items[item].name,
});
}
this.$router.push({ path: this.dest });
})
.catch((e) => {
buttons.done("copy");
this.$showError(e);
});
};
const action = async (overwrite: boolean, rename: boolean) => {
buttons.loading("copy");
if (this.$route.path === this.dest) {
this.closeHovers();
action(false, true);
await api
.copy(items, overwrite, rename)
.then(() => {
buttons.success("copy");
fileStore.preselect = removePrefix(items[0].to);
return;
}
if (route.path === dest.value) {
fileStore.reload = true;
return;
}
const dstItems = (await api.fetch(this.dest)).items;
const conflict = upload.checkConflict(items, dstItems);
router.push({ path: dest.value! });
})
.catch((e) => {
buttons.done("copy");
$showError?.(e);
});
};
let overwrite = false;
let rename = false;
if (route.path === dest.value) {
closeHovers();
action(false, true);
return;
}
if (conflict) {
this.showHover({
prompt: "replace-rename",
confirm: (event, option) => {
overwrite = option == "overwrite";
rename = option == "rename";
const dstItems = (await api.fetch(dest.value!)).items;
const conflict = upload.checkConflict(items as any, dstItems);
event.preventDefault();
this.closeHovers();
action(overwrite, rename);
},
});
let overwrite = false;
let rename = false;
return;
}
if (conflict) {
showHover({
prompt: "replace-rename",
confirm: (event: Event, option: string) => {
overwrite = option == "overwrite";
rename = option == "rename";
action(overwrite, rename);
},
},
event.preventDefault();
closeHovers();
action(overwrite, rename);
},
});
return;
}
action(overwrite, rename);
};
</script>

View File

@@ -0,0 +1,86 @@
<template>
<div>
<div class="path-container" ref="container">
<template v-for="(item, index) in path" :key="index">
/
<span class="path-item">
<span
v-if="isDir === true || index < path.length - 1"
class="material-icons"
>folder
</span>
<span v-else class="material-icons">insert_drive_file</span>
{{ item }}
</span>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, nextTick, defineProps } from "vue";
import { useRoute } from "vue-router";
import { useFileStore } from "@/stores/file";
import url from "@/utils/url";
const fileStore = useFileStore();
const route = useRoute();
const props = defineProps({
name: {
type: String,
required: true,
},
isDir: {
type: Boolean,
default: false,
},
});
const container = ref<HTMLElement | null>(null);
const path = computed(() => {
let basePath = fileStore.isFiles ? route.path : url.removeLastDir(route.path);
if (!basePath.endsWith("/")) {
basePath += "/";
}
basePath += props.name;
return basePath.split("/").filter(Boolean).splice(1);
});
watch(path, () => {
nextTick(() => {
const lastItem = container.value?.lastElementChild;
lastItem?.scrollIntoView({ behavior: "auto", inline: "end" });
});
});
</script>
<style scoped>
.path-container {
display: flex;
align-items: center;
margin: 0.2em 0;
gap: 0.25em;
overflow-x: auto;
max-width: 100%;
scrollbar-width: none;
opacity: 0.5;
}
.path-container::-webkit-scrollbar {
display: none;
}
.path-item {
display: flex;
align-items: center;
margin: 0.2em 0;
gap: 0.25em;
white-space: nowrap;
}
.path-item > span {
font-size: 0.9em;
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="card floating">
<div class="card-content">
<p v-if="!this.isListing || selectedCount === 1">
<p v-if="!isListing || selectedCount === 1">
{{ $t("prompts.deleteMessageSingle") }}
</p>
<p v-else>
@@ -32,67 +32,62 @@
</div>
</template>
<script>
import { mapActions, mapState, mapWritableState } from "pinia";
<script setup lang="ts">
import { inject } from "vue";
import { storeToRefs } from "pinia";
import { useRoute } from "vue-router";
import { files as api } from "@/api";
import buttons from "@/utils/buttons";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "delete",
inject: ["$showError"],
computed: {
...mapState(useFileStore, [
"isListing",
"selectedCount",
"req",
"selected",
]),
...mapState(useLayoutStore, ["currentPrompt"]),
...mapWritableState(useFileStore, ["reload", "preselect"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
submit: async function () {
buttons.loading("delete");
const route = useRoute();
const $showError = inject<(error: unknown) => void>("$showError");
try {
if (!this.isListing) {
await api.remove(this.$route.path);
buttons.success("delete");
const fileStore = useFileStore();
const layoutStore = useLayoutStore();
this.currentPrompt?.confirm();
this.closeHovers();
return;
}
const { isListing, selectedCount, req, selected } = storeToRefs(fileStore);
const { currentPrompt } = storeToRefs(layoutStore);
const { closeHovers } = layoutStore;
this.closeHovers();
const submit = async () => {
buttons.loading("delete");
if (this.selectedCount === 0) {
return;
}
try {
if (!isListing.value) {
await api.remove(route.path);
buttons.success("delete");
const promises = [];
for (const index of this.selected) {
promises.push(api.remove(this.req.items[index].url));
}
currentPrompt.value?.confirm();
closeHovers();
return;
}
await Promise.all(promises);
buttons.success("delete");
closeHovers();
const nearbyItem =
this.req.items[Math.max(0, Math.min(this.selected) - 1)];
if (selectedCount.value === 0) {
return;
}
this.preselect = nearbyItem?.path;
const promises = [];
for (const index of selected.value) {
promises.push(api.remove(req.value!.items[index].url));
}
this.reload = true;
} catch (e) {
buttons.done("delete");
this.$showError(e);
if (this.isListing) this.reload = true;
}
},
},
await Promise.all(promises);
buttons.success("delete");
const nearbyItem =
req.value!.items[Math.max(0, Math.min(...selected.value) - 1)];
fileStore.preselect = nearbyItem?.path;
fileStore.reload = true;
} catch (e) {
buttons.done("delete");
$showError?.(e);
if (isListing.value) fileStore.reload = true;
}
};
</script>

View File

@@ -33,8 +33,4 @@ import { useI18n } from "vue-i18n";
const layoutStore = useLayoutStore();
const { t } = useI18n();
// const emit = defineEmits<{
// (e: "confirm"): void;
// }>();
</script>

View File

@@ -11,17 +11,26 @@
@click="closeHovers"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
tabindex="2"
tabindex="3"
>
{{ $t("buttons.cancel") }}
</button>
<button
class="button button--flat button--blue"
@click="currentPrompt?.saveAction"
:aria-label="$t('buttons.saveChanges')"
:title="$t('buttons.saveChanges')"
tabindex="1"
>
{{ $t("buttons.saveChanges") }}
</button>
<button
id="focus-prompt"
@click="currentPrompt.confirm"
@click="currentPrompt?.confirm"
class="button button--flat button--red"
:aria-label="$t('buttons.discardChanges')"
:title="$t('buttons.discardChanges')"
tabindex="1"
tabindex="2"
>
{{ $t("buttons.discardChanges") }}
</button>
@@ -29,17 +38,11 @@
</div>
</template>
<script>
import { mapState, mapActions } from "pinia";
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "discardEditorChanges",
computed: {
...mapState(useLayoutStore, ["currentPrompt"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
},
};
const layoutStore = useLayoutStore();
const { currentPrompt } = storeToRefs(layoutStore);
const { closeHovers } = layoutStore;
</script>

View File

@@ -24,155 +24,174 @@
</div>
</template>
<script>
import { mapState } from "pinia";
<script setup lang="ts">
import { ref, computed, inject, onMounted, onUnmounted } from "vue";
import { storeToRefs } from "pinia";
import { useRoute } from "vue-router";
import { useAuthStore } from "@/stores/auth";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import url from "@/utils/url";
import { files } from "@/api";
import { StatusError } from "@/api/utils.js";
export default {
name: "file-list",
props: {
exclude: {
type: Array,
default: () => [],
},
},
data: function () {
return {
items: [],
touches: {
id: "",
count: 0,
},
selected: null,
current: window.location.pathname,
nextAbortController: new AbortController(),
};
},
inject: ["$showError"],
computed: {
...mapState(useAuthStore, ["user"]),
...mapState(useFileStore, ["req"]),
nav() {
return decodeURIComponent(this.current);
},
},
mounted() {
this.fillOptions(this.req);
},
unmounted() {
this.abortOngoingNext();
},
methods: {
abortOngoingNext() {
this.nextAbortController.abort();
},
fillOptions(req) {
// Sets the current path and resets
// the current items.
this.current = req.url;
this.items = [];
const props = defineProps<{
exclude?: string[];
}>();
this.$emit("update:selected", this.current);
const emit = defineEmits<{
"update:selected": [value: string];
}>();
// If the path isn't the root path,
// show a button to navigate to the previous
// directory.
if (req.url !== "/files/") {
this.items.push({
name: "..",
url: url.removeLastDir(req.url) + "/",
});
}
const route = useRoute();
const $showError = inject<(error: unknown) => void>("$showError");
// If this folder is empty, finish here.
if (req.items === null) return;
const authStore = useAuthStore();
const fileStore = useFileStore();
const layoutStore = useLayoutStore();
// Otherwise we add every directory to the
// move options.
for (const item of req.items) {
if (!item.isDir) continue;
if (this.exclude?.includes(item.url)) continue;
const { user } = storeToRefs(authStore);
const { req } = storeToRefs(fileStore);
const { showHover } = layoutStore;
this.items.push({
name: item.name,
url: item.url,
});
}
},
next: function (event) {
// Retrieves the URL of the directory the user
// just clicked in and fill the options with its
// content.
const uri = event.currentTarget.dataset.url;
this.abortOngoingNext();
this.nextAbortController = new AbortController();
files
.fetch(uri, this.nextAbortController.signal)
.then(this.fillOptions)
.catch((e) => {
if (e instanceof StatusError && e.is_canceled) {
return;
}
this.$showError(e);
});
},
touchstart(event) {
const url = event.currentTarget.dataset.url;
const items = ref<Array<{ name: string; url: string }>>([]);
const touches = ref({
id: "",
count: 0,
});
const selected = ref<string | null>(null);
const current = ref(window.location.pathname);
const nextAbortController = ref(new AbortController());
// In 300 milliseconds, we shall reset the count.
setTimeout(() => {
this.touches.count = 0;
}, 300);
const nav = computed(() => {
return decodeURIComponent(current.value);
});
// If the element the user is touching
// is different from the last one he touched,
// reset the count.
if (this.touches.id !== url) {
this.touches.id = url;
this.touches.count = 1;
return;
}
this.touches.count++;
// If there is more than one touch already,
// open the next screen.
if (this.touches.count > 1) {
this.next(event);
}
},
itemClick: function (event) {
if (this.user.singleClick) this.next(event);
else this.select(event);
},
select: function (event) {
// If the element is already selected, unselect it.
if (this.selected === event.currentTarget.dataset.url) {
this.selected = null;
this.$emit("update:selected", this.current);
return;
}
// Otherwise select the element.
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,
},
});
},
},
const abortOngoingNext = () => {
nextAbortController.value.abort();
};
const fillOptions = (reqData: any) => {
// Sets the current path and resets
// the current items.
current.value = reqData.url;
items.value = [];
emit("update:selected", current.value);
// If the path isn't the root path,
// show a button to navigate to the previous
// directory.
if (reqData.url !== "/files/") {
items.value.push({
name: "..",
url: url.removeLastDir(reqData.url) + "/",
});
}
// If this folder is empty, finish here.
if (reqData.items === null) return;
// Otherwise we add every directory to the
// move options.
for (const item of reqData.items) {
if (!item.isDir) continue;
if (props.exclude?.includes(item.url)) continue;
items.value.push({
name: item.name,
url: item.url,
});
}
};
const next = (event: Event) => {
// Retrieves the URL of the directory the user
// just clicked in and fill the options with its
// content.
const uri = (event.currentTarget as HTMLElement).dataset.url!;
abortOngoingNext();
nextAbortController.value = new AbortController();
files
.fetch(uri, nextAbortController.value.signal)
.then(fillOptions)
.catch((e) => {
if (e instanceof StatusError && e.is_canceled) {
return;
}
$showError?.(e);
});
};
const touchstart = (event: Event) => {
const urlValue = (event.currentTarget as HTMLElement).dataset.url!;
// In 300 milliseconds, we shall reset the count.
setTimeout(() => {
touches.value.count = 0;
}, 300);
// If the element the user is touching
// is different from the last one he touched,
// reset the count.
if (touches.value.id !== urlValue) {
touches.value.id = urlValue;
touches.value.count = 1;
return;
}
touches.value.count++;
// If there is more than one touch already,
// open the next screen.
if (touches.value.count > 1) {
next(event);
}
};
const itemClick = (event: Event) => {
if (user.value?.singleClick) next(event);
else select(event);
};
const select = (event: Event) => {
const urlValue = (event.currentTarget as HTMLElement).dataset.url!;
// If the element is already selected, unselect it.
if (selected.value === urlValue) {
selected.value = null;
emit("update:selected", current.value);
return;
}
// Otherwise select the element.
selected.value = urlValue;
emit("update:selected", selected.value);
};
const createDir = async () => {
showHover({
prompt: "newDir",
action: undefined,
confirm: undefined,
props: {
redirect: false,
base: current.value === route.path ? null : current.value,
},
});
};
onMounted(() => {
if (req.value) {
fillOptions(req.value);
}
});
onUnmounted(() => {
abortOngoingNext();
});
defineExpose({
createDir,
});
</script>

View File

@@ -34,14 +34,8 @@
</div>
</template>
<script>
import { mapActions } from "pinia";
<script setup lang="ts">
import { useLayoutStore } from "@/stores/layout";
export default {
name: "help",
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
},
};
const { closeHovers } = useLayoutStore();
</script>

View File

@@ -29,10 +29,10 @@
<template v-if="dir && selected.length === 0">
<p>
<strong>{{ $t("prompts.numberFiles") }}:</strong> {{ req.numFiles }}
<strong>{{ $t("prompts.numberFiles") }}:</strong> {{ req?.numFiles }}
</p>
<p>
<strong>{{ $t("prompts.numberDirs") }}:</strong> {{ req.numDirs }}
<strong>{{ $t("prompts.numberDirs") }}:</strong> {{ req?.numDirs }}
</p>
</template>
@@ -99,98 +99,100 @@
</div>
</template>
<script>
import { mapActions, mapState } from "pinia";
<script setup lang="ts">
import { computed, inject } from "vue";
import { storeToRefs } from "pinia";
import { useRoute } from "vue-router";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { filesize } from "@/utils";
import dayjs from "dayjs";
import { files as api } from "@/api";
export default {
name: "info",
inject: ["$showError"],
computed: {
...mapState(useFileStore, [
"req",
"selected",
"selectedCount",
"isListing",
]),
humanSize: function () {
if (this.selectedCount === 0 || !this.isListing) {
return filesize(this.req.size);
}
const route = useRoute();
const $showError = inject<(error: unknown) => void>("$showError");
let sum = 0;
const fileStore = useFileStore();
const layoutStore = useLayoutStore();
for (const selected of this.selected) {
sum += this.req.items[selected].size;
}
const { req, selected, selectedCount, isListing } = storeToRefs(fileStore);
const { closeHovers } = layoutStore;
return filesize(sum);
},
humanTime: function () {
if (this.selectedCount === 0) {
return dayjs(this.req.modified).fromNow();
}
const humanSize = computed(() => {
if (selectedCount.value === 0 || !isListing.value) {
return filesize(req.value?.size ?? 0);
}
return dayjs(this.req.items[this.selected[0]].modified).fromNow();
},
modTime: function () {
if (this.selectedCount === 0) {
return new Date(Date.parse(this.req.modified)).toLocaleString();
}
let sum = 0;
return new Date(
Date.parse(this.req.items[this.selected[0]].modified)
).toLocaleString();
},
name: function () {
return this.selectedCount === 0
? this.req.name
: this.req.items[this.selected[0]].name;
},
dir: function () {
return (
this.selectedCount > 1 ||
(this.selectedCount === 0
? this.req.isDir
: 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: {
...mapActions(useLayoutStore, ["closeHovers"]),
checksum: async function (event, algo) {
event.preventDefault();
for (const selectedIdx of selected.value) {
sum += req.value?.items[selectedIdx]?.size ?? 0;
}
let link;
return filesize(sum);
});
if (this.selectedCount) {
link = this.req.items[this.selected[0]].url;
} else {
link = this.$route.path;
}
const humanTime = computed(() => {
if (selectedCount.value === 0) {
return dayjs(req.value?.modified).fromNow();
}
try {
const hash = await api.checksum(link, algo);
event.target.textContent = hash;
} catch (e) {
this.$showError(e);
}
},
},
return dayjs(req.value?.items[selected.value[0]]?.modified).fromNow();
});
const modTime = computed(() => {
if (selectedCount.value === 0) {
return new Date(Date.parse(req.value?.modified ?? "")).toLocaleString();
}
return new Date(
Date.parse(req.value?.items[selected.value[0]]?.modified ?? "")
).toLocaleString();
});
const name = computed(() => {
return selectedCount.value === 0
? (req.value?.name ?? "")
: (req.value?.items[selected.value[0]]?.name ?? "");
});
const dir = computed(() => {
return (
selectedCount.value > 1 ||
(selectedCount.value === 0
? (req.value?.isDir ?? false)
: (req.value?.items[selected.value[0]]?.isDir ?? false))
);
});
const resolution = computed(() => {
if (selectedCount.value === 1) {
const selectedItem = req.value?.items[selected.value[0]];
if (selectedItem && selectedItem.type === "image") {
return (selectedItem as any).resolution;
}
} else if (req.value && req.value.type === "image") {
return (req.value as any).resolution;
}
return null;
});
const checksum = async (event: Event, algo: string) => {
event.preventDefault();
let link;
if (selectedCount.value) {
link = req.value?.items[selected.value[0]]?.url ?? "";
} else {
link = route.path;
}
try {
const hash = await api.checksum(link, algo as any);
(event.target as HTMLElement).textContent = hash;
} catch (e) {
$showError?.(e);
}
};
</script>

View File

@@ -7,7 +7,7 @@
<div class="card-content">
<file-list
ref="fileList"
@update:selected="(val) => (dest = val)"
@update:selected="(val: string) => (dest = val)"
:exclude="excludedFolders"
tabindex="1"
/>
@@ -17,10 +17,10 @@
class="card-action"
style="display: flex; align-items: center; justify-content: space-between"
>
<template v-if="user.perm.create">
<template v-if="user?.perm.create">
<button
class="button button--flat"
@click="$refs.fileList.createDir()"
@click="fileList?.createDir()"
:aria-label="$t('sidebar.newFolder')"
:title="$t('sidebar.newFolder')"
style="justify-self: left"
@@ -54,8 +54,10 @@
</div>
</template>
<script>
import { mapActions, mapState, mapWritableState } from "pinia";
<script setup lang="ts">
import { ref, computed, inject } from "vue";
import { storeToRefs } from "pinia";
import { useRouter } from "vue-router";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { useAuthStore } from "@/stores/auth";
@@ -65,80 +67,76 @@ import buttons from "@/utils/buttons";
import * as upload from "@/utils/upload";
import { removePrefix } from "@/api/utils";
export default {
name: "move",
components: { FileList },
data: function () {
return {
current: window.location.pathname,
dest: null,
};
},
inject: ["$showError"],
computed: {
...mapState(useFileStore, ["req", "selected"]),
...mapState(useAuthStore, ["user"]),
...mapWritableState(useFileStore, ["preselect"]),
excludedFolders() {
return this.selected
.filter((idx) => this.req.items[idx].isDir)
.map((idx) => this.req.items[idx].url);
},
},
methods: {
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
move: async function (event) {
event.preventDefault();
const items = [];
const router = useRouter();
const $showError = inject<(error: unknown) => void>("$showError");
for (const item of this.selected) {
items.push({
from: this.req.items[item].url,
to: this.dest + encodeURIComponent(this.req.items[item].name),
name: this.req.items[item].name,
});
}
const fileStore = useFileStore();
const layoutStore = useLayoutStore();
const authStore = useAuthStore();
const action = async (overwrite, rename) => {
buttons.loading("move");
const { req, selected } = storeToRefs(fileStore);
const { user } = storeToRefs(authStore);
const { showHover, closeHovers } = layoutStore;
await api
.move(items, overwrite, rename)
.then(() => {
buttons.success("move");
this.preselect = removePrefix(items[0].to);
this.$router.push({ path: this.dest });
})
.catch((e) => {
buttons.done("move");
this.$showError(e);
});
};
const fileList = ref<InstanceType<typeof FileList> | null>(null);
const dest = ref<string | null>(null);
const dstItems = (await api.fetch(this.dest)).items;
const conflict = upload.checkConflict(items, dstItems);
const excludedFolders = computed(() => {
return selected.value
.filter((idx) => req.value!.items[idx].isDir)
.map((idx) => req.value!.items[idx].url);
});
let overwrite = false;
let rename = false;
const move = async (event: Event) => {
event.preventDefault();
const items: Array<{ from: string; to: string; name: string }> = [];
if (conflict) {
this.showHover({
prompt: "replace-rename",
confirm: (event, option) => {
overwrite = option == "overwrite";
rename = option == "rename";
for (const item of selected.value) {
items.push({
from: req.value!.items[item].url,
to: dest.value! + encodeURIComponent(req.value!.items[item].name),
name: req.value!.items[item].name,
});
}
event.preventDefault();
this.closeHovers();
action(overwrite, rename);
},
});
const action = async (overwrite: boolean, rename: boolean) => {
buttons.loading("move");
return;
}
await api
.move(items, overwrite, rename)
.then(() => {
buttons.success("move");
fileStore.preselect = removePrefix(items[0].to);
router.push({ path: dest.value! });
})
.catch((e) => {
buttons.done("move");
$showError?.(e);
});
};
action(overwrite, rename);
},
},
const dstItems = (await api.fetch(dest.value!)).items;
const conflict = upload.checkConflict(items as any, dstItems);
let overwrite = false;
let rename = false;
if (conflict) {
showHover({
prompt: "replace-rename",
confirm: (event: Event, option: string) => {
overwrite = option == "overwrite";
rename = option == "rename";
event.preventDefault();
closeHovers();
action(overwrite, rename);
},
});
return;
}
action(overwrite, rename);
};
</script>

View File

@@ -14,6 +14,7 @@
v-model.trim="name"
tabindex="1"
/>
<CreateFilePath :name="name" :is-dir="true" />
</div>
<div class="card-action">
@@ -48,6 +49,7 @@ import { files as api } from "@/api";
import url from "@/utils/url";
import { useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import CreateFilePath from "@/components/prompts/CreateFilePath.vue";
const $showError = inject<IToastError>("$showError")!;

View File

@@ -13,6 +13,7 @@
@keyup.enter="submit"
v-model.trim="name"
/>
<CreateFilePath :name="name" />
</div>
<div class="card-action">
@@ -42,6 +43,7 @@ import { useI18n } from "vue-i18n";
import { useRoute, useRouter } from "vue-router";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import CreateFilePath from "@/components/prompts/CreateFilePath.vue";
import { files as api } from "@/api";
import url from "@/utils/url";

View File

@@ -40,80 +40,74 @@
</div>
</template>
<script>
import { mapActions, mapState, mapWritableState } from "pinia";
<script setup lang="ts">
import { ref, onMounted, inject } from "vue";
import { storeToRefs } from "pinia";
import { useRouter } from "vue-router";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import url from "@/utils/url";
import { files as api } from "@/api";
import { removePrefix } from "@/api/utils";
export default {
name: "rename",
data: function () {
return {
name: "",
};
},
created() {
this.name = this.oldName();
},
inject: ["$showError"],
computed: {
...mapState(useFileStore, [
"req",
"selected",
"selectedCount",
"isListing",
]),
...mapWritableState(useFileStore, ["reload", "preselect"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
cancel: function () {
this.closeHovers();
},
oldName: function () {
if (!this.isListing) {
return this.req.name;
}
const router = useRouter();
const $showError = inject<(error: unknown) => void>("$showError");
if (this.selectedCount === 0 || this.selectedCount > 1) {
// This shouldn't happen.
return;
}
const fileStore = useFileStore();
const layoutStore = useLayoutStore();
return this.req.items[this.selected[0]].name;
},
submit: async function () {
let oldLink = "";
let newLink = "";
const { req, selected, selectedCount, isListing } = storeToRefs(fileStore);
const { closeHovers } = layoutStore;
if (!this.isListing) {
oldLink = this.req.url;
} else {
oldLink = this.req.items[this.selected[0]].url;
}
const name = ref("");
newLink =
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
const oldName = (): string => {
if (!isListing.value) {
return req.value?.name ?? "";
}
try {
await api.move([{ from: oldLink, to: newLink }]);
if (!this.isListing) {
this.$router.push({ path: newLink });
return;
}
if (selectedCount.value === 0 || selectedCount.value > 1) {
// This shouldn't happen.
return "";
}
this.preselect = removePrefix(newLink);
return req.value?.items[selected.value[0]].name ?? "";
};
this.reload = true;
} catch (e) {
this.$showError(e);
}
onMounted(() => {
name.value = oldName();
});
this.closeHovers();
},
},
const submit = async () => {
let oldLink = "";
let newLink = "";
if (!req.value) {
return;
}
if (!isListing.value) {
oldLink = req.value.url;
} else {
oldLink = req.value.items[selected.value[0]].url;
}
newLink = url.removeLastDir(oldLink) + "/" + encodeURIComponent(name.value);
try {
await api.move([{ from: oldLink, to: newLink }]);
if (!isListing.value) {
router.push({ path: newLink });
return;
}
fileStore.preselect = removePrefix(newLink);
fileStore.reload = true;
} catch (e) {
$showError?.(e);
}
closeHovers();
};
</script>

View File

@@ -20,7 +20,7 @@
</button>
<button
class="button button--flat button--blue"
@click="currentPrompt.action"
@click="currentPrompt?.action"
:aria-label="$t('buttons.continue')"
:title="$t('buttons.continue')"
tabindex="2"
@@ -30,7 +30,7 @@
<button
id="focus-prompt"
class="button button--flat button--red"
@click="currentPrompt.confirm"
@click="currentPrompt?.confirm"
:aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')"
tabindex="1"
@@ -41,17 +41,11 @@
</div>
</template>
<script>
import { mapActions, mapState } from "pinia";
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "replace",
computed: {
...mapState(useLayoutStore, ["currentPrompt"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
},
};
const layoutStore = useLayoutStore();
const { currentPrompt } = storeToRefs(layoutStore);
const { closeHovers } = layoutStore;
</script>

View File

@@ -20,7 +20,7 @@
</button>
<button
class="button button--flat button--blue"
@click="(event) => currentPrompt.confirm(event, 'rename')"
@click="(event) => currentPrompt?.confirm(event, 'rename')"
:aria-label="$t('buttons.rename')"
:title="$t('buttons.rename')"
tabindex="2"
@@ -30,7 +30,7 @@
<button
id="focus-prompt"
class="button button--flat button--red"
@click="(event) => currentPrompt.confirm(event, 'overwrite')"
@click="(event) => currentPrompt?.confirm(event, 'overwrite')"
:aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')"
tabindex="1"
@@ -41,17 +41,11 @@
</div>
</template>
<script>
import { mapActions, mapState } from "pinia";
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "replace-rename",
computed: {
...mapState(useLayoutStore, ["currentPrompt"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
},
};
const layoutStore = useLayoutStore();
const { currentPrompt } = storeToRefs(layoutStore);
const { closeHovers } = layoutStore;
</script>

View File

@@ -129,138 +129,146 @@
</div>
</template>
<script>
import { mapActions, mapState } from "pinia";
<script setup lang="ts">
import { ref, computed, inject, onBeforeMount } from "vue";
import { storeToRefs } from "pinia";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import { useFileStore } from "@/stores/file";
import { share as api } from "@/api";
import dayjs from "dayjs";
import { useLayoutStore } from "@/stores/layout";
import { copy } from "@/utils/clipboard";
export default {
name: "share",
data: function () {
return {
time: 0,
unit: "hours",
links: [],
clip: null,
password: "",
listing: true,
};
},
inject: ["$showError", "$showSuccess"],
computed: {
...mapState(useFileStore, [
"req",
"selected",
"selectedCount",
"isListing",
]),
url() {
if (!this.isListing) {
return this.$route.path;
}
const route = useRoute();
const { t } = useI18n();
const $showError = inject<(error: unknown) => void>("$showError");
const $showSuccess = inject<(message: string) => void>("$showSuccess");
if (this.selectedCount === 0 || this.selectedCount > 1) {
// This shouldn't happen.
return;
}
const fileStore = useFileStore();
const layoutStore = useLayoutStore();
return this.req.items[this.selected[0]].url;
const { req, selected, selectedCount, isListing } = storeToRefs(fileStore);
const { closeHovers } = layoutStore;
const time = ref(0);
const unit = ref("hours");
const links = ref<any[]>([]);
const password = ref("");
const listing = ref(true);
const url = computed(() => {
if (!isListing.value) {
return route.path;
}
if (selectedCount.value === 0 || selectedCount.value > 1) {
// This shouldn't happen.
return "";
}
return req.value?.items[selected.value[0]].url ?? "";
});
const copyToClipboard = (text: string) => {
copy({ text }).then(
() => {
// clipboard successfully set
$showSuccess?.(t("success.linkCopied"));
},
},
async beforeMount() {
try {
const links = await api.get(this.url);
this.links = links;
this.sort();
if (this.links.length == 0) {
this.listing = false;
}
} catch (e) {
this.$showError(e);
}
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
copyToClipboard: function (text) {
copy({ text }).then(
() => {
// clipboard write failed
copy({ text }, { permission: true }).then(
() => {
// clipboard successfully set
this.$showSuccess(this.$t("success.linkCopied"));
$showSuccess?.(t("success.linkCopied"));
},
() => {
(e) => {
// clipboard write failed
copy({ text }, { permission: true }).then(
() => {
// clipboard successfully set
this.$showSuccess(this.$t("success.linkCopied"));
},
(e) => {
// clipboard write failed
this.$showError(e);
}
);
$showError?.(e);
}
);
},
submit: async function () {
try {
let res = null;
if (!this.time) {
res = await api.create(this.url, this.password);
} else {
res = await api.create(this.url, this.password, this.time, this.unit);
}
this.links.push(res);
this.sort();
this.time = 0;
this.unit = "hours";
this.password = "";
this.listing = true;
} catch (e) {
this.$showError(e);
}
},
deleteLink: async function (event, link) {
event.preventDefault();
try {
await api.remove(link.hash);
this.links = this.links.filter((item) => item.hash !== link.hash);
if (this.links.length == 0) {
this.listing = false;
}
} catch (e) {
this.$showError(e);
}
},
humanTime(time) {
return dayjs(time * 1000).fromNow();
},
buildLink(share) {
return api.getShareURL(share);
},
sort() {
this.links = this.links.sort((a, b) => {
if (a.expire === 0) return -1;
if (b.expire === 0) return 1;
return new Date(a.expire) - new Date(b.expire);
});
},
switchListing() {
if (this.links.length == 0 && !this.listing) {
this.closeHovers();
}
this.listing = !this.listing;
},
},
}
);
};
const submit = async () => {
try {
let res = null;
if (!time.value) {
res = await api.create(url.value, password.value);
} else {
res = await api.create(
url.value,
password.value,
String(time.value),
unit.value
);
}
links.value.push(res);
sort();
time.value = 0;
unit.value = "hours";
password.value = "";
listing.value = true;
} catch (e) {
$showError?.(e);
}
};
const deleteLink = async (event: Event, link: any) => {
event.preventDefault();
try {
await api.remove(link.hash);
links.value = links.value.filter((item) => item.hash !== link.hash);
if (links.value.length == 0) {
listing.value = false;
}
} catch (e) {
$showError?.(e);
}
};
const humanTime = (time: number) => {
return dayjs(time * 1000).fromNow();
};
const buildLink = (share: any) => {
return api.getShareURL(share);
};
const sort = () => {
links.value = links.value.sort((a, b) => {
if (a.expire === 0) return -1;
if (b.expire === 0) return 1;
return new Date(a.expire).getTime() - new Date(b.expire).getTime();
});
};
const switchListing = () => {
if (links.value.length == 0 && !listing.value) {
closeHovers();
}
listing.value = !listing.value;
};
onBeforeMount(async () => {
try {
const fetchedLinks = await api.get(url.value);
links.value = Array.isArray(fetchedLinks) ? fetchedLinks : [fetchedLinks];
sort();
if (links.value.length == 0) {
listing.value = false;
}
} catch (e) {
$showError?.(e);
}
});
</script>

View File

@@ -27,20 +27,15 @@
</div>
</template>
<script>
import { mapActions, mapState } from "pinia";
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "share-delete",
computed: {
...mapState(useLayoutStore, ["currentPrompt"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
submit: function () {
this.currentPrompt?.confirm();
},
},
const layoutStore = useLayoutStore();
const { currentPrompt } = storeToRefs(layoutStore);
const { closeHovers } = layoutStore;
const submit = () => {
currentPrompt.value?.confirm();
};
</script>

View File

@@ -14,7 +14,7 @@
}}
</h2>
<div class="upload-info">
<div class="upload-speed">{{ speedMbytes }}/s</div>
<div class="upload-speed">{{ speedText }}/s</div>
<div class="upload-eta">{{ formattedETA }} remaining</div>
<div class="upload-percentage">{{ sentPercent }}% Completed</div>
<div class="upload-fraction">
@@ -88,6 +88,7 @@ const uploadStore = useUploadStore();
const { sentBytes, totalBytes } = storeToRefs(uploadStore);
const byteToMbyte = partial({ exponent: 2 });
const byteToKbyte = partial({ exponent: 1 });
const sentPercent = computed(() =>
((uploadStore.sentBytes / uploadStore.totalBytes) * 100).toFixed(2)
@@ -95,11 +96,33 @@ const sentPercent = computed(() =>
const sentMbytes = computed(() => byteToMbyte(uploadStore.sentBytes));
const totalMbytes = computed(() => byteToMbyte(uploadStore.totalBytes));
const speedMbytes = computed(() => byteToMbyte(speed.value));
const speedText = computed(() => {
const bytes = speed.value;
if (bytes < 1024 * 1024) {
const kb = parseFloat(byteToKbyte(bytes));
return `${kb.toFixed(2)} KB`;
} else {
const mb = parseFloat(byteToMbyte(bytes));
return `${mb.toFixed(2)} MB`;
}
});
let lastSpeedUpdate: number = 0;
let recentSpeeds: number[] = [];
let lastThrottleTime = 0;
const throttledCalculateSpeed = (sentBytes: number, oldSentBytes: number) => {
const now = Date.now();
if (now - lastThrottleTime < 100) {
return;
}
lastThrottleTime = now;
calculateSpeed(sentBytes, oldSentBytes);
};
const calculateSpeed = (sentBytes: number, oldSentBytes: number) => {
// Reset the state when the uploads batch is complete
if (sentBytes === 0) {
@@ -149,7 +172,7 @@ const calculateEta = () => {
eta.value = remainingSize / speedBytesPerSecond;
};
watch(sentBytes, calculateSpeed);
watch(sentBytes, throttledCalculateSpeed);
watch(totalBytes, (totalBytes, oldTotalBytes) => {
if (oldTotalBytes !== 0) {

View File

@@ -0,0 +1,28 @@
<template>
<select
name="selectAceEditorTheme"
v-on:change="change"
:value="aceEditorTheme"
>
<option v-for="theme in themes" :value="theme.theme" :key="theme.theme">
{{ theme.name }}
</option>
</select>
</template>
<script setup lang="ts">
import { type SelectHTMLAttributes } from "vue";
import { themes } from "ace-builds/src-noconflict/ext-themelist";
defineProps<{
aceEditorTheme: string;
}>();
const emit = defineEmits<{
(e: "update:aceEditorTheme", val: string | null): void;
}>();
const change = (event: Event) => {
emit("update:aceEditorTheme", (event.target as SelectHTMLAttributes)?.value);
};
</script>

View File

@@ -8,23 +8,27 @@
</div>
</template>
<script>
export default {
name: "permissions",
props: ["commands"],
computed: {
raw: {
get() {
return this.commands.join(" ");
},
set(value) {
if (value !== "") {
this.$emit("update:commands", value.split(" "));
} else {
this.$emit("update:commands", []);
}
},
},
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps<{
commands: string[];
}>();
const emit = defineEmits<{
"update:commands": [commands: string[]];
}>();
const raw = computed({
get() {
return props.commands.join(" ");
},
};
set(value: string) {
if (value !== "") {
emit("update:commands", value.split(" "));
} else {
emit("update:commands", []);
}
},
});
</script>

View File

@@ -6,60 +6,50 @@
</select>
</template>
<script>
<script setup lang="ts">
import { markRaw } from "vue";
export default {
name: "languages",
props: ["locale"],
data() {
const dataObj = {};
const locales = {
he: "עברית",
hu: "Magyar",
ar: "العربية",
ca: "Català",
cs: "Čeština",
de: "Deutsch",
el: "Ελληνικά",
en: "English",
es: "Español",
fr: "Français",
is: "Icelandic",
it: "Italiano",
ja: "日本語",
ko: "한국어",
"nl-be": "Dutch (Belgium)",
no: "Norsk",
pl: "Polski",
"pt-br": "Português",
pt: "Português (Brasil)",
ro: "Romanian",
ru: "Русский",
sk: "Slovenčina",
"sv-se": "Swedish (Sweden)",
tr: "Türkçe",
uk: "Українська",
vi: "Tiếng Việt",
"zh-cn": "中文 (简体)",
"zh-tw": "中文 (繁體)",
};
defineProps<{
locale: string;
}>();
// Vue3 reactivity breaks with this configuration
// so we need to use markRaw as a workaround
// https://github.com/vuejs/core/issues/3024
Object.defineProperty(dataObj, "locales", {
value: markRaw(locales),
configurable: false,
writable: false,
});
const emit = defineEmits<{
"update:locale": [locale: string];
}>();
return dataObj;
},
methods: {
change(event) {
this.$emit("update:locale", event.target.value);
},
},
const locales = markRaw({
he: "עברית",
hr: "Hrvatski",
hu: "Magyar",
ar: "العربية",
ca: "Català",
cs: "Čeština",
de: "Deutsch",
el: "Ελληνικά",
en: "English",
es: "Español",
fr: "Français",
is: "Icelandic",
it: "Italiano",
ja: "日本語",
ko: "한국어",
"nl-be": "Dutch (Belgium)",
no: "Norsk",
pl: "Polski",
"pt-br": "Português",
pt: "Português (Brasil)",
ro: "Romanian",
ru: "Русский",
sk: "Slovenčina",
"sv-se": "Swedish (Sweden)",
tr: "Türkçe",
uk: "Українська",
vi: "Tiếng Việt",
"zh-cn": "中文 (简体)",
"zh-tw": "中文 (繁體)",
});
const change = (event: Event) => {
emit("update:locale", (event.target as HTMLSelectElement).value);
};
</script>

View File

@@ -39,27 +39,28 @@
</div>
</template>
<script>
<script setup lang="ts">
import { computed } from "vue";
import { enableExec } from "@/utils/constants";
export default {
name: "permissions",
props: ["perm"],
computed: {
admin: {
get() {
return this.perm.admin;
},
set(value) {
if (value) {
for (const key in this.perm) {
this.perm[key] = true;
}
}
this.perm.admin = value;
},
},
isExecEnabled: () => enableExec,
const props = defineProps<{
perm: UserPermissions;
}>();
const admin = computed({
get() {
return props.perm.admin;
},
};
set(value: boolean) {
if (value) {
for (const key in props.perm) {
props.perm[key as keyof UserPermissions] = true;
}
}
props.perm.admin = value;
},
});
const isExecEnabled = enableExec;
</script>

View File

@@ -32,32 +32,44 @@
</form>
</template>
<script>
export default {
name: "rules-textarea",
props: ["rules"],
methods: {
remove(event, index) {
event.preventDefault();
const rules = [...this.rules];
rules.splice(index, 1);
this.$emit("update:rules", [...rules]);
},
create(event) {
event.preventDefault();
<script setup lang="ts">
interface Rule {
allow: boolean;
path: string;
regex: boolean;
regexp: {
raw: string;
};
}
this.$emit("update:rules", [
...this.rules,
{
allow: true,
path: "",
regex: false,
regexp: {
raw: "",
},
},
]);
const props = defineProps<{
rules: Rule[];
}>();
const emit = defineEmits<{
"update:rules": [rules: Rule[]];
}>();
const remove = (event: Event, index: number) => {
event.preventDefault();
const rules = [...props.rules];
rules.splice(index, 1);
emit("update:rules", [...rules]);
};
const create = (event: Event) => {
event.preventDefault();
emit("update:rules", [
...props.rules,
{
allow: true,
path: "",
regex: false,
regexp: {
raw: "",
},
},
},
]);
};
</script>

View File

@@ -80,12 +80,20 @@ const { t } = useI18n();
const createUserDirData = ref<boolean | null>(null);
const originalUserScope = ref<string | null>(null);
const props = defineProps<{
user: IUserForm;
isNew: boolean;
isDefault: boolean;
createUserDir?: boolean;
}>();
const props = defineProps<
| {
user: IUserForm;
isNew: boolean;
isDefault: false;
createUserDir?: boolean;
}
| {
user: SettingsDefaults;
isNew: boolean;
isDefault: true;
createUserDir?: boolean;
}
>();
onMounted(() => {
if (props.user.scope) {
@@ -108,6 +116,7 @@ watch(
() => props.user,
() => {
if (!props.user?.perm?.admin) return;
if (props.isDefault) return;
props.user.lockPassword = false;
}
);

View File

@@ -63,8 +63,8 @@
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;
unicode-range:
U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
@@ -75,8 +75,9 @@
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;
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 {
@@ -142,8 +143,8 @@
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;
unicode-range:
U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
@@ -154,8 +155,9 @@
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;
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 {
@@ -221,8 +223,8 @@
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;
unicode-range:
U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
@@ -233,8 +235,9 @@
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;
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;
}
.material-icons {

View File

@@ -45,6 +45,15 @@
animation: 0.2s opac forwards;
}
#login .logout-message {
background: var(--icon-orange);
color: #fff;
padding: 0.5em;
text-align: center;
animation: 0.2s opac forwards;
text-transform: none;
}
@keyframes opac {
0% {
opacity: 0;

View File

@@ -1,6 +1,4 @@
.md_preview {
overflow-y: auto;
max-height: 80vh;
padding: 1rem;
border: 1px solid #000;
font-size: 20px;
@@ -9,5 +7,5 @@
#preview-container {
overflow: auto;
max-height: 80vh; /* Match the max-height of md_preview for scrolling */
flex: 1;
}

View File

@@ -42,7 +42,8 @@
"update": "تحديث",
"upload": "رفع",
"openFile": "فتح الملف",
"discardChanges": "إلغاء التغييرات"
"discardChanges": "إلغاء التغييرات",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "تحميل الملف",
@@ -100,7 +101,10 @@
"submit": "تسجيل دخول",
"username": "إسم المستخدم",
"usernameTaken": "إسم المستخدم غير متاح",
"wrongCredentials": "بيانات دخول خاطئة"
"wrongCredentials": "بيانات دخول خاطئة",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "دائم",
"prompts": {
@@ -154,6 +158,7 @@
"video": "فيديوهات"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "إدارة",
"administrator": "مدير",
"allowCommands": "تنفيذ اﻷوامر",

View File

@@ -42,7 +42,8 @@
"update": "Actualitzar",
"upload": "Pujar",
"openFile": "Obrir fitxer",
"discardChanges": "Descartar"
"discardChanges": "Descartar",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Descarregar fitxer",
@@ -100,7 +101,10 @@
"submit": "Iniciar sessió",
"username": "Usuari",
"usernameTaken": "Nom d'usuari no disponible",
"wrongCredentials": "Usuari i/o contrasenya incorrectes"
"wrongCredentials": "Usuari i/o contrasenya incorrectes",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Permanent",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Vídeo"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrador",
"allowCommands": "Executar comandes",

View File

@@ -42,7 +42,8 @@
"update": "Aktualizovat",
"upload": "Nahrát",
"openFile": "Otevřít soubor",
"discardChanges": "Zrušit změny"
"discardChanges": "Zrušit změny",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Stáhnout soubor",
@@ -100,7 +101,10 @@
"submit": "Přihlásit se",
"username": "Uživatelské jméno",
"usernameTaken": "Uživatelské jméno již existuje",
"wrongCredentials": "Nesprávné přihlašovací údaje"
"wrongCredentials": "Nesprávné přihlašovací údaje",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Trvalý",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrátor",
"allowCommands": "Povolit příkazy",

View File

@@ -42,7 +42,8 @@
"update": "Update",
"upload": "Upload",
"openFile": "Datei öffnen",
"discardChanges": "Verwerfen"
"discardChanges": "Verwerfen",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Download Datei",
@@ -100,7 +101,10 @@
"submit": "Login",
"username": "Benutzername",
"usernameTaken": "Benutzername ist bereits vergeben",
"wrongCredentials": "Falsche Zugangsdaten"
"wrongCredentials": "Falsche Zugangsdaten",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Permanent",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "Befehle ausführen",

View File

@@ -42,7 +42,8 @@
"update": "Ενημέρωση",
"upload": "Μεταφόρτωση",
"openFile": "Άνοιγμα αρχείου",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Λήψη αρχείου",
@@ -100,7 +101,10 @@
"submit": "Είσοδος",
"username": "Όνομα χρήστη",
"usernameTaken": "Το όνομα χρήστη χρησιμοποιείται ήδη",
"wrongCredentials": "Λάθος όνομα ή/και κωδικός πρόσβασης"
"wrongCredentials": "Λάθος όνομα ή/και κωδικός πρόσβασης",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Μόνιμο",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Βίντεο"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Διαχειριστής",
"administrator": "Διαχειριστής",
"allowCommands": "Εκτέλεση εντολών",

View File

@@ -42,7 +42,8 @@
"update": "Update",
"upload": "Upload",
"openFile": "Open file",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Download File",
@@ -100,7 +101,10 @@
"submit": "Login",
"username": "Username",
"usernameTaken": "Username already taken",
"wrongCredentials": "Wrong credentials"
"wrongCredentials": "Wrong credentials",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Permanent",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "Execute commands",

View File

@@ -7,7 +7,7 @@
"copy": "Copiar",
"copyFile": "Copiar archivo",
"copyToClipboard": "Copiar al portapapeles",
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
"copyDownloadLinkToClipboard": "Copiar enlace de descarga al portapapeles",
"create": "Crear",
"delete": "Borrar",
"download": "Descargar",
@@ -42,7 +42,8 @@
"update": "Actualizar",
"upload": "Subir",
"openFile": "Abrir archivo",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Guardar cambios"
},
"download": {
"downloadFile": "Descargar fichero",
@@ -100,7 +101,10 @@
"submit": "Iniciar sesión",
"username": "Usuario",
"usernameTaken": "Nombre usuario no disponible",
"wrongCredentials": "Usuario y/o contraseña incorrectos"
"wrongCredentials": "Usuario y/o contraseña incorrectos",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Permanente",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Vídeo"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrador",
"allowCommands": "Ejecutar comandos",

View File

@@ -42,7 +42,8 @@
"update": "به روز سانی",
"upload": "آپلود",
"openFile": "باز کردن فایل",
"discardChanges": "لغو کردن"
"discardChanges": "لغو کردن",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "دانلود فایل",
@@ -100,7 +101,10 @@
"submit": "ورود",
"username": "نام کاربری",
"usernameTaken": "نام کاربری تکراری",
"wrongCredentials": "خطا در اعتبارسنجی"
"wrongCredentials": "خطا در اعتبارسنجی",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "دائمی",
"prompts": {
@@ -154,6 +158,7 @@
"video": "ویدئو "
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "اجرای دستورات",

View File

@@ -42,7 +42,8 @@
"update": "Mettre à jour",
"upload": "Importer",
"openFile": "Ouvrir le fichier",
"discardChanges": "Annuler"
"discardChanges": "Annuler",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Télécharger le fichier",
@@ -77,14 +78,14 @@
"noPreview": "L'aperçu n'est pas disponible pour ce fichier."
},
"help": {
"click": "Sélectionner un élément",
"click": "Sélectionner un fichier ou dossier",
"ctrl": {
"click": "Sélectionner plusieurs éléments",
"click": "Sélectionner plusieurs fichiers ou dossiers",
"f": "Ouvrir l'invité de recherche",
"s": "Télécharger l'élément actuel"
"s": "Enregistrer un fichier ou télécharger le dossier actuel"
},
"del": "Supprimer les éléments sélectionnés",
"doubleClick": "Ouvrir un élément",
"doubleClick": "Ouvrir un fichier ou dossier",
"esc": "Désélectionner et/ou fermer la boîte de dialogue",
"f1": "Ouvrir l'aide",
"f2": "Renommer le fichier",
@@ -98,9 +99,12 @@
"passwordsDontMatch": "Les mots de passe ne concordent pas",
"signup": "S'inscrire",
"submit": "Se connecter",
"username": "Utilisateur",
"usernameTaken": "Le nom d'utilisateur est déjà pris",
"wrongCredentials": "Identifiants incorrects !"
"username": "Utilisateur·ice",
"usernameTaken": "Le nom d'utilisateur·ice est déjà pris",
"wrongCredentials": "Identifiants incorrects !",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Permanent",
"prompts": {
@@ -110,7 +114,7 @@
"deleteMessageMultiple": "Êtes-vous sûr de vouloir supprimer ces {count} élément(s) ?",
"deleteMessageSingle": "Êtes-vous sûr de vouloir supprimer cet élément ?",
"deleteMessageShare": "Êtes-vous sûr de vouloir supprimer ce partage ({path}) ?",
"deleteUser": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?",
"deleteUser": "Êtes-vous sûr de vouloir supprimer cet·te utilisateur·ice ?",
"deleteTitle": "Supprimer",
"displayName": "Nom :",
"download": "Télécharger",
@@ -120,7 +124,7 @@
"filesSelected": "{count} éléments sélectionnés",
"lastModified": "Dernière modification",
"move": "Déplacer",
"moveMessage": "Choisissez l'emplacement où déplacer la sélection :",
"moveMessage": "Choisissez un nouveau dossier principal pour vos fichier(s)/dossier(s) :",
"newArchetype": "Créer un nouveau post basé sur un archétype. Votre fichier sera créé dans le dossier de contenu.",
"newDir": "Nouveau dossier",
"newDirMessage": "Nom du nouveau dossier :",
@@ -154,13 +158,14 @@
"video": "Vidéo"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrateur",
"administrator": "Administrateur·ice",
"allowCommands": "Exécuter des commandes",
"allowEdit": "Éditer, renommer et supprimer des fichiers ou des dossiers",
"allowNew": "Créer de nouveaux fichiers et dossiers",
"allowPublish": "Publier de nouveaux posts et pages",
"allowSignup": "Autoriser les utilisateurs à s'inscrire",
"allowSignup": "Autoriser les utilisateur·ices à s'inscrire",
"avoidChanges": "(Laisser vide pour conserver l'actuel)",
"branding": "Image de marque",
"brandingDirectoryPath": "Chemin du dossier d'image de marque",
@@ -169,17 +174,17 @@
"commandRunner": "Exécuteur de commandes",
"commandRunnerHelp": "Ici, vous pouvez définir les commandes qui seront exécutées lors des événements nommés précédemments. Vous devez en écrire une par ligne. Les variables d'environnement {0} et {1} seront disponibles, {0} étant relatif à {1}. Pour plus d'informations sur cette fonctionnalité et les variables d'environnement disponibles, veuillez lire la {2}.",
"commandsUpdated": "Commandes mises à jour !",
"createUserDir": "Créer automatiquement un dossier pour l'utilisateur",
"minimumPasswordLength": "Minimum password length",
"createUserDir": "Créer automatiquement un dossier pour l'utilisateur·ice",
"minimumPasswordLength": "Taille minimale du mot de passe",
"tusUploads": "Uploads segmentés",
"tusUploadsHelp": "File Browser prend en charge les uploads segmentés afin de permettre une gestion efficace, fiable et reprenable sur des réseaux instables.",
"tusUploadsChunkSize": "Taille maximale autorisée par segment (les uploads directs seront utilisés pour les fichiers plus petits). Vous pouvez entrer un entier en octets ou une chaîne telle que 10MB, 1GB, etc.",
"tusUploadsRetryCount": "Nombre de tentatives en cas d'échec d'un segment.",
"userHomeBasePath": "Chemin de base pour les répertoires personnels des utilisateurs",
"userHomeBasePath": "Chemin de base pour les dossiers personnels des utilisateur·ices",
"userScopeGenerationPlaceholder": "Le périmètre sera généré automatiquement",
"createUserHomeDirectory": "Créer le répertoire personnel de l'utilisateur",
"createUserHomeDirectory": "Créer le dossier personnel de l'utilisateur·ice",
"customStylesheet": "Feuille de style personnalisée",
"defaultUserDescription": "Paramètres par défaut pour les nouveaux utilisateurs.",
"defaultUserDescription": "Paramètres par défaut pour les nouveaux utilisateur·ices.",
"disableExternalLinks": "Désactiver les liens externes (sauf la documentation)",
"disableUsedDiskPercentage": "Désactiver le graphique de pourcentage d'utilisation du disque",
"documentation": "documentation",
@@ -188,12 +193,12 @@
"executeOnShellDescription": "Par défaut, File Browser exécute les commandes en appelant directement leurs binaires. Si vous voulez les exécuter sur un shell à la place (comme Bash ou PowerShell), vous pouvez le définir ici avec les arguments et les drapeaux requis. S'il est défini, la commande que vous exécutez sera ajoutée en tant qu'argument. Cela s'applique à la fois aux commandes utilisateur et aux crochets d'événements.",
"globalRules": "Il s'agit d'un ensemble global de règles d'autorisation et d'interdiction. Elles s'appliquent à tous les utilisateurs. Vous pouvez définir des règles spécifiques sur les paramètres de chaque utilisateur pour remplacer celles-ci.",
"globalSettings": "Paramètres globaux",
"hideDotfiles": "Cacher les fichiers de configuration utilisateur (dotfiles)",
"hideDotfiles": "Cacher les fichiers de configuration commançant par un point",
"insertPath": "Insérer le chemin",
"insertRegex": "Insérer une expression régulière",
"instanceName": "Nom de l'instance",
"language": "Langue",
"lockPassword": "Empêcher l'utilisateur de changer son mot de passe",
"lockPassword": "Empêcher l'utilisateur·ice de changer son mot de passe",
"newPassword": "Votre nouveau mot de passe",
"newPasswordConfirm": "Confirmation du nouveau mot de passe",
"newUser": "Nouvel utilisateur",
@@ -210,13 +215,13 @@
"share": "Partager des fichiers"
},
"permissions": "Permissions",
"permissionsHelp": "Vous pouvez définir l'utilisateur comme étant un administrateur ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur\", toutes les autres options seront automatiquement activées. La gestion des utilisateurs est un privilège que seul l'administrateur possède.\n",
"permissionsHelp": "Vous pouvez définir l'utilisateur·ice comme étant administrateur·ice ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur·ice\", toutes les autres options seront automatiquement activées. La gestion des utilisateur·ices est un privilège que seul l'administrateur·ice possède.\n",
"profileSettings": "Paramètres du profil",
"ruleExample1": "Bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers",
"ruleExample2": "Bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur",
"ruleExample1": "Bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers.\n",
"ruleExample2": "Bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur·ice.",
"rules": "Règles",
"rulesHelp": "Vous pouvez définir ici un ensemble de règles pour cet utilisateur. Les fichiers bloqués ne seront pas affichés et ne seront pas accessibles par l'utilisateur. Les expressions régulières sont supportées et les chemins d'accès sont relatifs par rapport au dossier de l'utilisateur.\n",
"scope": "Portée du dossier utilisateur",
"rulesHelp": "Vous pouvez définir ici un ensemble de règles pour cet utilisateur·ice. Les fichiers bloqués ne seront pas affichés et ne seront pas accessibles par l'utilisateur·ice. Les expressions régulières sont supportées et les chemins d'accès sont relatifs par rapport au dossier de l'utilisateur·ice.\n",
"scope": "Portée du dossier utilisateur·ice",
"setDateFormat": "Définir le format de la date",
"settingsUpdated": "Les paramètres ont été mis à jour !",
"shareDuration": "Durée du partage",
@@ -224,21 +229,21 @@
"shareDeleted": "Partage supprimé !",
"singleClick": "Utiliser un simple clic pour ouvrir les fichiers et les dossiers",
"themes": {
"default": "System default",
"default": "Par défaut du système",
"dark": "Sombre",
"light": "Clair",
"title": "Thème"
},
"user": "Utilisateur",
"user": "Utilisateur·ice",
"userCommands": "Commandes",
"userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur. Exemple :\n",
"userCreated": "Utilisateur créé !",
"userDefaults": "Paramètres par défaut de l'utilisateur",
"userDeleted": "Utilisateur supprimé !",
"userManagement": "Gestion des utilisateurs",
"userUpdated": "Utilisateur mis à jour !",
"username": "Nom d'utilisateur",
"users": "Utilisateurs"
"userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur·ice. Exemple :\n",
"userCreated": "Utilisateur·ice créé !",
"userDefaults": "Paramètres par défaut de l'utilisateur.ice",
"userDeleted": "Utilisateur·ice supprimé !",
"userManagement": "Gestion des utilisateur·ices",
"userUpdated": "Utilisateur·ice mis à jour !",
"username": "Nom d'utilisateur·ice",
"users": "Utilisateur·ices"
},
"sidebar": {
"help": "Aide",

View File

@@ -42,7 +42,8 @@
"update": "עדכון",
"upload": "העלאה",
"openFile": "פתח קובץ",
"discardChanges": "זריקת השינויים"
"discardChanges": "זריקת השינויים",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "הורד קובץ",
@@ -100,7 +101,10 @@
"submit": "התחברות",
"username": "שם משתמש",
"usernameTaken": "שם המשתמש כבר קיים",
"wrongCredentials": "פרטי התחברות שגויים"
"wrongCredentials": "פרטי התחברות שגויים",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "קבוע",
"prompts": {
@@ -154,6 +158,7 @@
"video": "וידאו"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "מנהל",
"administrator": "מנהל ראשי",
"allowCommands": "הפעלת פקודות",

271
frontend/src/i18n/hr.json Normal file
View File

@@ -0,0 +1,271 @@
{
"buttons": {
"cancel": "Otkaži",
"clear": "Očisti",
"close": "Zatvori",
"continue": "Nastavi",
"copy": "Kopiraj",
"copyFile": "Kopiraj datoteku",
"copyToClipboard": "Kopiraj u međuspremnik",
"copyDownloadLinkToClipboard": "Kopiraj poveznicu za preuzimanje u međuspremnik",
"create": "Stvori",
"delete": "Izbriši",
"download": "Preuzmi",
"file": "Datoteka",
"folder": "Mapa",
"fullScreen": "Prebaci na cijeli zaslon",
"hideDotfiles": "Sakrij datoteke koje započinju točkom",
"info": "Info",
"more": "Više",
"move": "Premjesti",
"moveFile": "Premjesti datoteku",
"new": "Novo",
"next": "Sljedeće",
"ok": "OK",
"permalink": "Dohvati trajnu poveznicu",
"previous": "Prethodno",
"preview": "Pregled",
"publish": "Objavi",
"rename": "Preimenuj",
"replace": "Zamijeni",
"reportIssue": "Prijavi grešku",
"save": "Spremi",
"schedule": "Zakaži",
"search": "Pretraži",
"select": "Označi",
"selectMultiple": "Označi više",
"share": "Podijeli",
"shell": "Promijeni ljusku",
"submit": "Predaj",
"switchView": "Promijeni prikaz",
"toggleSidebar": "Prebaci bočnu traku",
"update": "Ažuriraj",
"upload": "Prenesi",
"openFile": "Otvori datoteku",
"discardChanges": "Odbaci",
"saveChanges": "Spremi promjene"
},
"download": {
"downloadFile": "Preuzmi Datoteku",
"downloadFolder": "Preuzmi Mapu",
"downloadSelected": "Preuzmi Odabrano"
},
"upload": {
"abortUpload": "Jeste li sigurni da hoćete otkazati?"
},
"errors": {
"forbidden": "Nemate dopuštenje pristupiti ovome.",
"internal": "Nešto je stvarno pošlo po zlu.",
"notFound": "Lokacija ne može biti dohvaćena.",
"connection": "Poslužitelj ne može biti dohvaćen."
},
"files": {
"body": "Tijelo",
"closePreview": "Zatvori pregled",
"files": "Datoteke",
"folders": "Mape",
"home": "Dom",
"lastModified": "Zadnje izmijenjeno",
"loading": "Učitavanje...",
"lonely": "Ovdje je tako prazno...",
"metadata": "Metapodaci",
"multipleSelectionEnabled": "Višestruk odabir",
"name": "Naziv",
"size": "Veličina",
"sortByLastModified": "Sortiraj po zadnjoj izmjeni",
"sortByName": "Sortiraj po nazivu",
"sortBySize": "Sortiraj po veličini",
"noPreview": "Pregled nije dostupan za ovu datoteku."
},
"help": {
"click": "odaberi datoteku ili mapu",
"ctrl": {
"click": "odaberi više datoteka ili mapa",
"f": "tražilica",
"s": "spremi datoteku ili preuzmi trenutnu mapu"
},
"del": "izbriši odabrane stavke",
"doubleClick": "otvori datoteku ili mapu",
"esc": "očisti odabir i/ili zatvori upit",
"f1": "ova informacija",
"f2": "preimenuj datoteku",
"help": "Pomoć"
},
"login": {
"createAnAccount": "Stvori korisnički račun",
"loginInstead": "Imam korisnički račun",
"password": "Lozinka",
"passwordConfirm": "Potvrda lozinke",
"passwordsDontMatch": "Lozinke se ne podudaraju",
"signup": "Registracija",
"submit": "Prijava",
"username": "Korisničko ime",
"usernameTaken": "Korisničko ime zauzeto",
"wrongCredentials": "Neispravno korisničko ime/lozinka",
"logout_reasons": {
"inactivity": "Odjavljeni ste zbog neaktivnosti."
}
},
"permanent": "Trajan",
"prompts": {
"copy": "Kopiraj",
"copyMessage": "Odaberite lokaciju za kopiranje datoteka:",
"currentlyNavigating": "Trenutno navigiranje na:",
"deleteMessageMultiple": "Jeste li sigurni da želite izbrisati datoteke: {count}?",
"deleteMessageSingle": "Jeste li sigurni da hoćete izbrisati ovu datoteku/mapu?",
"deleteMessageShare": "Jeste li sigurni da hoćete izbrisati ovo dijeljenje({path})?",
"deleteUser": "Jeste li sigurni da hoćete izbrisati ovaj korisnički račun?",
"deleteTitle": "Izbriši datoteke",
"displayName": "Prikazno Ime:",
"download": "Preuzmi datoteke",
"downloadMessage": "Odaberite format za preuzimanje.",
"error": "Nešto je pošlo po zlu",
"fileInfo": "Informacije o datoteci",
"filesSelected": "{count} datoteka odabrana.",
"lastModified": "Zadnje izmijenjeno",
"move": "Premjesti",
"moveMessage": "Odaberite novi dom za Vašu datoteku(e)/mapu(e):",
"newArchetype": "Stvorite novu objavu na temelju arhetipu. Vaša datoteka bit će stvorena u mapi sadržaja.",
"newDir": "Nova mapa",
"newDirMessage": "Imenujte Vašu novu mapu.",
"newFile": "Nova datoteka",
"newFileMessage": "Imenujte Vašu novu datoteku.",
"numberDirs": "Broj mapa",
"numberFiles": "Broj datoteka",
"rename": "Preimenuj",
"renameMessage": "Umetni novo ime za",
"replace": "Zamijeni",
"replaceMessage": "Jedna od datoteka koju pokušavate prenijeti ima sukobljavajući naziv. Želite li preskočiti ovu datoteku i nastaviti s prijenosom ili zamijeniti postojeću datoteku?\n",
"schedule": "Zakaži",
"scheduleMessage": "Odaberite datum i vrijeme za zakazivanje ove objave.",
"show": "Prikaži",
"size": "Veličina",
"upload": "Prenesi",
"uploadFiles": "Prenošenje {files} datoteka...",
"uploadMessage": "Odaberite opciju za prijenos.",
"optionalPassword": "Opcionalna lozinka",
"resolution": "Rezolucija",
"discardEditorChanges": "Jeste li sigurni da želite odbaciti promjene koje ste napravili?"
},
"search": {
"images": "Slike",
"music": "Glazba",
"pdf": "PDF",
"pressToSearch": "Pritisnite enter za pretraživanje...",
"search": "Pretraživanje...",
"typeToSearch": "Tipkajte za pretraživanje...",
"types": "Tipovi",
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "Izvrši naredbe",
"allowEdit": "Uredi, preimenuj i izbriši datoteke ili mape",
"allowNew": "Stvori nove datoteke i mape",
"allowPublish": "Objavi nove objave i stranice",
"allowSignup": "Dopusti registraciju korisnicima",
"avoidChanges": "(ostavite prazno kako biste izbjegli promjene)",
"branding": "Brendiranje",
"brandingDirectoryPath": "Put brendiranja",
"brandingHelp": "Možete prilagoditi izgled i funkcionalnost Vašeg File Browsera mijenjanjem njegovog naziva, zamjenom logotipa, dodavanjem prilagođenih stilova pa čak i onemogućavanjem vanjskih poveznica na GitHub.\nZa više informacija o prilagođenome brendiranju pogledajte {0}.",
"changePassword": "Promjena lozinke",
"commandRunner": "Izvršitelj naredbi",
"commandRunnerHelp": "Ovdje možete postaviti naredbe koje se izvršuju u imenovanim događajima. Morate napisati jednu po liniji. Varijable okruženja {0} i {1} bit će dostupne, tako da je {0} relativna {1}. Za više informacija o ovoj značajci pogledajte {2}.",
"commandsUpdated": "Naredbe ažurirane!",
"createUserDir": "Automatsko stvaranje kućne mape korisnika pri dodavanju novog korisnika",
"minimumPasswordLength": "Minimalna duljina lozinke",
"tusUploads": "Segmentirani prijenosi",
"tusUploadsHelp": "File Browser podržava segmentirane prijenose datoteka, omogućavajući stvaranje učinkovitih, pouzdanih, obnovljivih i segmentiranih prijenosa datoteka čak i na nepouzdanim mrežama.",
"tusUploadsChunkSize": "Naznačuje maksimalnu veličinu zahtjeva (direktni prijenosi bit će korišteni za manje prijenose). Možete unijeti cijeli broj koji označava veličinu bajta ili niz znakova poput 10MB, 1GB itd.",
"tusUploadsRetryCount": "Broj ponovnih pokušaja ako se dio ne uspije prenijeti.",
"userHomeBasePath": "Bazni put za kućne mape korisnika",
"userScopeGenerationPlaceholder": "Opseg će se automatski generirati",
"createUserHomeDirectory": "Stvori kućnu mapu korisnika",
"customStylesheet": "Prilagođeni Stylesheet",
"defaultUserDescription": "Zadane postavke za nove korisnike.",
"disableExternalLinks": "Onemogući vanjske poveznice (osim dokumentacije)",
"disableUsedDiskPercentage": "Onemogući graf iskorištenosti diska",
"documentation": "dokumentacija",
"examples": "Primjeri",
"executeOnShell": "Izvrši u ljusci",
"executeOnShellDescription": "Po zadanim postavkama, File Browser izvršava naredbe izravnim pozivanjem njihovih binarnih datoteka. Ako ih želite izvršiti u ljusci (kao što su Bash ili PowerShell), možete ih definirati ovdje s potrebnim argumentima i oznakama. Ako je postavljena, naredba koju izvršavate bit će dodana kao argument. To se odnosi i na korisničke naredbe i na događajne kuke.",
"globalRules": "Ovo je globalan skup pravila dopuštanja i zabrane. Primjenjuju se na svakog korisnika. Moguće je definirati specifična pravila u postavkama svakog korisnika da biste nadjačali ove postavke.",
"globalSettings": "Globalne postavke",
"hideDotfiles": "Sakrij datoteke koje započinju točkom",
"insertPath": "Umetni put",
"insertRegex": "Umetni regex izraz",
"instanceName": "Naziv instance",
"language": "Jezik",
"lockPassword": "Onemogući mijenjanje lozinke korisniku",
"newPassword": "Vaša nova lozinka",
"newPasswordConfirm": "Potvrdite Vašu novu lozinku",
"newUser": "Novi Korisnik",
"password": "Lozinka",
"passwordUpdated": "Lozinka ažurirana!",
"path": "Put",
"perm": {
"create": "Stvaranje datoteka i mapa",
"delete": "Brisanje datoteka i mapa",
"download": "Preuzimanje",
"execute": "Izvršavanje naredbi",
"modify": "Uređivanje datoteka",
"rename": "Preimenovanje ili premještanje datoteka i mapa",
"share": "Dijeljenje datoteka"
},
"permissions": "Dopuštenja",
"permissionsHelp": "Korisnika možete postaviti administratorom ili odabrati dopuštenja individualno. Odabirom na \"Administrator\", sve druge opcije bit će automatski odabrane. Upravljanje korisnicima ostaje privilegija administratora.\n",
"profileSettings": "Postavke profila",
"ruleExample1": "onemogućava pristup svakoj datoteci koja započinje točkom (poput .git, .gitignore) u svakoj mapi.\n",
"ruleExample2": "blokira pristup datoteci naziva Caddyfile na korijenu opsega.",
"rules": "Pravila",
"rulesHelp": "Ovdje možete definirati skup pravila dopuštanja i zabrane za ovog specifičnog korisnika. Blokirane datoteke neće se prikazivati u popisima i neće biti dostupne korisniku. Podržavamo regex i puteve relativne opsegu korisnika.\n",
"scope": "Opseg",
"setDateFormat": "Odredi točan format datuma",
"settingsUpdated": "Postavke ažurirane!",
"shareDuration": "Podijeli Trajanje",
"shareManagement": "Upravljanje Dijeljenjem",
"shareDeleted": "Podjela izbrisana!",
"singleClick": "Koristi jednostruke klikove za otvaranje datoteka i mapa",
"themes": {
"default": "Zadano - Sustav",
"dark": "Tamno",
"light": "Svijetlo",
"title": "Tema"
},
"user": "Korisnik",
"userCommands": "Naredbe",
"userCommandsHelp": "Popis dostupnih naredbi za ovog korisnika. Primjer:\n",
"userCreated": "Korisnik stvoren!",
"userDefaults": "Zadane postavke korisnika",
"userDeleted": "Korisnik izbrisan!",
"userManagement": "Upravljanje Korisnicima",
"userUpdated": "Korisnik ažuriran!",
"username": "Korisničko ime",
"users": "Korisnici"
},
"sidebar": {
"help": "Pomoć",
"hugoNew": "Hugo New",
"login": "Prijava",
"logout": "Odjava",
"myFiles": "Moje datoteke",
"newFile": "Nova datoteka",
"newFolder": "Nova mapa",
"preview": "Pregled",
"settings": "Postavke",
"signup": "Registracija",
"siteSettings": "Postavke stranice"
},
"success": {
"linkCopied": "Poveznica kopirana!"
},
"time": {
"days": "Dani",
"hours": "Sati",
"minutes": "Minute",
"seconds": "Sekunde",
"unit": "Jedinica vremena"
}
}

View File

@@ -42,7 +42,8 @@
"update": "Frissítés",
"upload": "Feltöltés",
"openFile": "Fájl megnyitása",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Fájl letöltése",
@@ -100,7 +101,10 @@
"submit": "Belépés",
"username": "Felhasználói név",
"usernameTaken": "A felhasználói név már foglalt",
"wrongCredentials": "Hibás hitelesítő adatok"
"wrongCredentials": "Hibás hitelesítő adatok",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Állandó",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Videó"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Adminisztrátor",
"allowCommands": "Parancsok futtatása",

View File

@@ -8,6 +8,7 @@ import("dayjs/locale/en");
import("dayjs/locale/es");
import("dayjs/locale/fr");
import("dayjs/locale/he");
import("dayjs/locale/hr");
import("dayjs/locale/hu");
import("dayjs/locale/is");
import("dayjs/locale/it");
@@ -41,6 +42,9 @@ export function detectLocale() {
case /^he\b/.test(locale):
locale = "he";
break;
case /^hr\b/.test(locale):
locale = "hr";
break;
case /^hu\b/.test(locale):
locale = "hu";
break;

View File

@@ -42,7 +42,8 @@
"update": "Vista",
"upload": "Hlaða upp",
"openFile": "Open file",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Sækja skjal",
@@ -100,7 +101,10 @@
"submit": "Innskráning",
"username": "Notendanafn",
"usernameTaken": "Þetta norendanafn er þegar í notkun",
"wrongCredentials": "Rangar notendaupplýsingar"
"wrongCredentials": "Rangar notendaupplýsingar",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Varanlegt",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Myndbönd"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Stjórnandi",
"administrator": "Stjórnandi",
"allowCommands": "Senda skipanir",

View File

@@ -7,13 +7,13 @@
"copy": "Copia",
"copyFile": "Copia file",
"copyToClipboard": "Copia negli appunti",
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
"copyDownloadLinkToClipboard": "Copia link di scarica negli appunti",
"create": "Crea",
"delete": "Elimina",
"download": "Scarica",
"file": "File",
"folder": "Folder",
"fullScreen": "Toggle full screen",
"folder": "Cartella",
"fullScreen": "Abilita schermo intero",
"hideDotfiles": "Nascondi dotfile",
"info": "Informazioni",
"more": "Altro",
@@ -24,7 +24,7 @@
"ok": "OK",
"permalink": "Ottieni link permanente",
"previous": "Precedente",
"preview": "Preview",
"preview": "Anteprima",
"publish": "Publica",
"rename": "Rinomina",
"replace": "Sostituisci",
@@ -36,13 +36,14 @@
"selectMultiple": "Seleziona molteplici",
"share": "Condividi",
"shell": "Mostra/nascondi shell",
"submit": "Submit",
"submit": "Invia",
"switchView": "Cambia vista",
"toggleSidebar": "Mostra/nascondi la barra laterale",
"update": "Aggiorna",
"upload": "Carica",
"openFile": "Open file",
"discardChanges": "Discard"
"openFile": "Apri file",
"discardChanges": "Ignora",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Scarica file",
@@ -50,13 +51,13 @@
"downloadSelected": "Scarica selezionati"
},
"upload": {
"abortUpload": "Are you sure you wish to abort?"
"abortUpload": "Sei sicuro di voler abortire la procedura?"
},
"errors": {
"forbidden": "Non hai i permessi per accedere a questo file.",
"internal": "Qualcosa è andato veramente male.",
"notFound": "Questo percorso non può essere raggiunto.",
"connection": "The server can't be reached."
"connection": "Il server non è raggiungibile"
},
"files": {
"body": "Contenuto",
@@ -74,7 +75,7 @@
"sortByLastModified": "Ordina per ultima modifica",
"sortByName": "Ordina per nome",
"sortBySize": "Ordina per dimensione",
"noPreview": "Preview is not available for this file."
"noPreview": "L'anteprima non è disponibile per questo file."
},
"help": {
"click": "seleziona un file o una cartella",
@@ -100,7 +101,10 @@
"submit": "Entra",
"username": "Nome utente",
"usernameTaken": "Username già usato",
"wrongCredentials": "Credenziali errate"
"wrongCredentials": "Credenziali errate",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Permanente",
"prompts": {
@@ -109,8 +113,8 @@
"currentlyNavigating": "Attualmente navigando su:",
"deleteMessageMultiple": "Sei sicuro di voler eliminare {count} file?",
"deleteMessageSingle": "Sei sicuro di voler eliminare questo file/cartella?",
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
"deleteUser": "Are you sure you want to delete this user?",
"deleteMessageShare": "Sei sicuro di voler eliminare questo percorso condiviso ({path})?",
"deleteUser": "Sei sicuro di voler eliminare questo utente?",
"deleteTitle": "Elimina",
"displayName": "Nome visualizzato:",
"download": "Scarica files",
@@ -137,11 +141,11 @@
"show": "Mostra",
"size": "Dimensione",
"upload": "Carica",
"uploadFiles": "Uploading {files} files...",
"uploadFiles": "Inviando {files} file...",
"uploadMessage": "Seleziona un'opzione per il caricamento.",
"optionalPassword": "Optional password",
"resolution": "Resolution",
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
"optionalPassword": "Password opzionale",
"resolution": "Risoluzione",
"discardEditorChanges": "Sei sicuro di voler scartare le modifiche apportate?"
},
"search": {
"images": "Immagini",
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Amministratore",
"allowCommands": "Esegui comandi",
@@ -170,14 +175,14 @@
"commandRunnerHelp": "Qui puoi impostare i comandi da eseguire negli eventi nominati. Ne devi scrivere uno per riga. Le variabili d'ambiente {0} e {1} sono disponibili, essendo {0} relativo a {1}. Per altre informazioni su questa funzionalità e sulle variabili d'ambiente utilizzabili, leggi la {2}.",
"commandsUpdated": "Comandi aggiornati!",
"createUserDir": "Crea automaticamente la home directory dell'utente quando lo aggiungi",
"minimumPasswordLength": "Minimum password length",
"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 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",
"minimumPasswordLength": "Lunghezza minima della password",
"tusUploads": "Tranci di invii",
"tusUploadsHelp": "File Browser supporta tranci di invii fornendo così la possibilità di inviare efficientemente i file anche su reti instabili.",
"tusUploadsChunkSize": "Indica la dimensione massima di una richiesta (invii diretti saranno usati per piccoli invii). Puoi inserire un numero intero per indicare la dimensione in byte, oppure una stringa con l'unità di misura come in 10MB, 1GB, etc.",
"tusUploadsRetryCount": "Numero di tentativi da effettuare se un trancio di file fallisce.",
"userHomeBasePath": "Percorso base per le cartelle utente",
"userScopeGenerationPlaceholder": "La portata verrà autogenerata",
"createUserHomeDirectory": "Crea cartella utente",
"customStylesheet": "Foglio di stile personalizzato",
"defaultUserDescription": "Queste sono le impostazioni predefinite per i nuovi utenti.",
"disableExternalLinks": "Disabilita link esterni (tranne per la documentazione)",
@@ -217,14 +222,14 @@
"rules": "Regole",
"rulesHelp": "Qui è possibile definire una serie di regole e permessi per questo specifico utente. I file bloccati non appariranno negli elenchi e non saranno accessibili dagli utenti. all'utente. Sia regex che i percorsi relativi all'ambito di applicazione degli utenti sono supportati.\n",
"scope": "Scope",
"setDateFormat": "Set exact date format",
"setDateFormat": "Fissa il formato di data esatto",
"settingsUpdated": "Impostazioni aggiornate!",
"shareDuration": "Durata della condivisione",
"shareManagement": "Gestione delle condivisioni",
"shareDeleted": "Share deleted!",
"shareDeleted": "Percorso condiviso eliminato!",
"singleClick": "Usa un singolo click per aprire file e cartelle",
"themes": {
"default": "System default",
"default": "Impostazione predefinita del sistema",
"dark": "Scuro",
"light": "Chiaro",
"title": "Tema"

View File

@@ -42,7 +42,8 @@
"update": "更新",
"upload": "アップロード",
"openFile": "ファイルを開く",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "ファイルのダウンロード",
@@ -100,7 +101,10 @@
"submit": "ログイン",
"username": "ユーザー名",
"usernameTaken": "ユーザー名はすでに取得されています",
"wrongCredentials": "ユーザー名またはパスワードが間違っています"
"wrongCredentials": "ユーザー名またはパスワードが間違っています",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "永久",
"prompts": {
@@ -154,6 +158,7 @@
"video": "動画"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "管理者",
"administrator": "管理者",
"allowCommands": "コマンドの実行",

View File

@@ -3,17 +3,17 @@
"cancel": "취소",
"clear": "지우기",
"close": "닫기",
"continue": "Continue",
"continue": "계속",
"copy": "복사",
"copyFile": "파일 복사",
"copyToClipboard": "클립보드 복사",
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
"copyDownloadLinkToClipboard": "다운로드 링크 복사",
"create": "생성",
"delete": "삭제",
"download": "다운로드",
"file": "File",
"folder": "Folder",
"fullScreen": "Toggle full screen",
"file": "파일",
"folder": "폴더",
"fullScreen": "전체 화면 전환",
"hideDotfiles": "숨김파일(dotfile)을 표시 안함",
"info": "정보",
"more": "더보기",
@@ -24,7 +24,7 @@
"ok": "확인",
"permalink": "링크 얻기",
"previous": "이전",
"preview": "Preview",
"preview": "미리보기",
"publish": "게시",
"rename": "이름 바꾸기",
"replace": "대체",
@@ -36,13 +36,14 @@
"selectMultiple": "다중 선택",
"share": "공유",
"shell": "쉘 전환",
"submit": "Submit",
"submit": "제출",
"switchView": "보기 전환",
"toggleSidebar": "사이드바 전환",
"update": "업데이트",
"upload": "업로드",
"openFile": "Open file",
"discardChanges": "Discard"
"openFile": "파일 열기",
"discardChanges": "변경 사항 취소",
"saveChanges": "변경사항 저장"
},
"download": {
"downloadFile": "파일 다운로드",
@@ -50,13 +51,13 @@
"downloadSelected": "선택 항목 다운로드"
},
"upload": {
"abortUpload": "Are you sure you wish to abort?"
"abortUpload": "업로드를 중단하시겠습니까?"
},
"errors": {
"forbidden": "접근 권한이 없습니다.",
"internal": "오류가 발생하였습니다.",
"notFound": "해당 경로를 찾을 수 없습니다.",
"connection": "The server can't be reached."
"connection": "서버에 연결할 수 없습니다."
},
"files": {
"body": "본문",
@@ -74,7 +75,7 @@
"sortByLastModified": "수정시간순 정렬",
"sortByName": "이름순",
"sortBySize": "크기순",
"noPreview": "Preview is not available for this file."
"noPreview": "미리 보기가 지원되지 않는 파일 유형입니다."
},
"help": {
"click": "파일이나 디렉토리를 선택해주세요.",
@@ -100,7 +101,10 @@
"submit": "로그인",
"username": "사용자 이름",
"usernameTaken": "사용자 이름이 존재합니다",
"wrongCredentials": "사용자 이름 또는 비밀번호를 확인하십시오"
"wrongCredentials": "사용자 이름 또는 비밀번호를 확인하십시오",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "영구",
"prompts": {
@@ -109,8 +113,8 @@
"currentlyNavigating": "현재 위치:",
"deleteMessageMultiple": "{count} 개의 파일을 삭제하시겠습니까?",
"deleteMessageSingle": "파일 혹은 디렉토리를 삭제하시겠습니까?",
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
"deleteUser": "Are you sure you want to delete this user?",
"deleteMessageShare": "이 공유({path})를 삭제하시겠습니까?",
"deleteUser": "이 계정을 삭제하시겠습니까?",
"deleteTitle": "파일 삭제",
"displayName": "게시 이름:",
"download": "파일 다운로드",
@@ -137,11 +141,11 @@
"show": "보기",
"size": "크기",
"upload": "업로드",
"uploadFiles": "Uploading {files} files...",
"uploadFiles": "{files}개의 파일 업로드 중...",
"uploadMessage": "업로드 옵션을 선택하세요.",
"optionalPassword": "Optional password",
"resolution": "Resolution",
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
"optionalPassword": "비밀번호 (선택)",
"resolution": "해상도",
"discardEditorChanges": "변경 사항을 취소하시겠습니까?"
},
"search": {
"images": "이미지",
@@ -154,6 +158,7 @@
"video": "비디오"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "관리자",
"administrator": "관리자",
"allowCommands": "명령 실행",
@@ -170,14 +175,14 @@
"commandRunnerHelp": "이벤트에 해당하는 명령을 설정하세요. 줄당 1개의 명령을 적으세요. 환경 변수{0} 와 {1}이 사용가능하며, {0} 은 {1}에 상대 경로 입니다. 자세한 사항은 {2} 를 참조하세요.",
"commandsUpdated": "명령 수정됨!",
"createUserDir": "Auto create user home dir while adding new user",
"minimumPasswordLength": "Minimum password length",
"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 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",
"minimumPasswordLength": "최소 비밀번호 길이",
"tusUploads": "분할 업로드",
"tusUploadsHelp": "File Browser는 불안정한 네트워크에서도 효율적이고 신뢰성 있는 분할 업로드를 지원합니다.",
"tusUploadsChunkSize": "업로드 요청의 최대 크기 (예: 10MB, 1GB)",
"tusUploadsRetryCount": "업로드 실패 시 재시도 횟수",
"userHomeBasePath": "사용자 홈 폴더 기본 경로",
"userScopeGenerationPlaceholder": "범위는 자동으로 생성됩니다.",
"createUserHomeDirectory": "사용자 홈 폴더 생성",
"customStylesheet": "커스텀 스타일시트",
"defaultUserDescription": "아래 사항은 신규 사용자들에 대한 기본 설정입니다.",
"disableExternalLinks": "외부 링크 감추기",
@@ -217,14 +222,14 @@
"rules": "룰",
"rulesHelp": "사용자별로 규칙을 허용/방지를 지정할 수 있습니다. 방지된 파일은 보이지 않고 사용자들은 접근할 수 없습니다. 사용자의 접근 허용 범위와 관련해 정규표현식(regex)과 경로를 지원합니다.\n",
"scope": "범위",
"setDateFormat": "Set exact date format",
"setDateFormat": "날짜 형식 설정",
"settingsUpdated": "설정 수정됨!",
"shareDuration": "공유 기간",
"shareManagement": "공유 내역 관리",
"shareDeleted": "Share deleted!",
"shareDeleted": "공유 삭제됨!",
"singleClick": "한번 클릭으로 파일과 폴더를 열도록 합니다.",
"themes": {
"default": "System default",
"default": "시스템 기본값",
"dark": "다크테마",
"light": "라이트테마",
"title": "테마"

View File

@@ -42,7 +42,8 @@
"update": "Updaten",
"upload": "Uploaden",
"openFile": "Open file",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Bestand downloaden",
@@ -100,7 +101,10 @@
"submit": "Log in",
"username": "Gebruikersnaam",
"usernameTaken": "Gebruikersnaam reeds in gebruik",
"wrongCredentials": "Verkeerde inloggegevens"
"wrongCredentials": "Verkeerde inloggegevens",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Permanent",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "Commando's uitvoeren",

View File

@@ -42,7 +42,8 @@
"update": "Opptater",
"upload": "Last opp",
"openFile": "Open file",
"discardChanges": "Slett"
"discardChanges": "Slett",
"saveChanges": "Lagre Endringane "
},
"download": {
"downloadFile": "Nedlast filen",
@@ -100,7 +101,10 @@
"submit": "Logg inn",
"username": "Brukernavn",
"usernameTaken": "Brukernavn er allerede i bruk",
"wrongCredentials": "Feil legitimasjon"
"wrongCredentials": "Feil legitimasjon",
"logout_reasons": {
"inactivity": "Du har blitt logget ut på grunn av inaktivitet"
}
},
"permanent": "Permanent",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "Utfør kommandoer",

View File

@@ -42,7 +42,8 @@
"update": "Aktualizuj",
"upload": "Wyślij",
"openFile": "Otwórz plik",
"discardChanges": "Odrzuć"
"discardChanges": "Odrzuć",
"saveChanges": "Zapisz zmiany"
},
"download": {
"downloadFile": "Pobierz plik",
@@ -100,7 +101,10 @@
"submit": "Zaloguj",
"username": "Nazwa użytkownika",
"usernameTaken": "Ta nazwa użytkownika jest zajęta",
"wrongCredentials": "Błędne dane logowania"
"wrongCredentials": "Błędne dane logowania",
"logout_reasons": {
"inactivity": "Wylogowano z powodu braku aktywności."
}
},
"permanent": "Permanentny",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Wideo"
},
"settings": {
"aceEditorTheme": "Motyw edytora Ace",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "Wykonaj polecenie",

View File

@@ -42,7 +42,8 @@
"update": "Atualizar",
"upload": "Enviar",
"openFile": "Abrir",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Baixar arquivo",
@@ -100,7 +101,10 @@
"submit": "Login",
"username": "Nome do usuário",
"usernameTaken": "Nome de usuário já existe",
"wrongCredentials": "Ops! Dados incorretos."
"wrongCredentials": "Ops! Dados incorretos.",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Permanente",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Vídeos"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrador",
"allowCommands": "Executar comandos",

View File

@@ -42,7 +42,8 @@
"update": "Atualizar",
"upload": "Enviar",
"openFile": "Open file",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Descarregar ficheiro",
@@ -100,7 +101,10 @@
"submit": "Entrar na conta",
"username": "Nome de utilizador",
"usernameTaken": "O nome de utilizador já está registado",
"wrongCredentials": "Dados errados"
"wrongCredentials": "Dados errados",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Permanente",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Vídeos"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrador",
"allowCommands": "Executar comandos",

View File

@@ -42,7 +42,8 @@
"update": "Actualizează",
"upload": "Încarcă",
"openFile": "Open file",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Descarcă fișier",
@@ -100,7 +101,10 @@
"submit": "Autentificare",
"username": "Utilizator",
"usernameTaken": "Utilizatorul există",
"wrongCredentials": "Informații greșite"
"wrongCredentials": "Informații greșite",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Permanent",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "Execută comenzi",

View File

@@ -42,7 +42,8 @@
"update": "Обновить",
"upload": "Загрузить",
"openFile": "Открыть файл",
"discardChanges": "Отказаться"
"discardChanges": "Отказаться",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Скачать файл",
@@ -100,7 +101,10 @@
"submit": "Войти",
"username": "Имя пользователя",
"usernameTaken": "Данное имя пользователя уже занято",
"wrongCredentials": "Неверные данные"
"wrongCredentials": "Неверные данные",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Постоянный",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Видео"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Админ",
"administrator": "Администратор",
"allowCommands": "Запуск команд",

View File

@@ -42,7 +42,8 @@
"update": "Aktualizovať",
"upload": "Nahrať",
"openFile": "Otvoriť súbor",
"discardChanges": "Zahodiť"
"discardChanges": "Zahodiť",
"saveChanges": "Uložiť zmeny"
},
"download": {
"downloadFile": "Stiahnuť súbor",
@@ -100,7 +101,10 @@
"submit": "Prihlásiť",
"username": "Používateľské meno",
"usernameTaken": "Meno je už obsadené",
"wrongCredentials": "Nesprávne prihlasovacie údaje"
"wrongCredentials": "Nesprávne prihlasovacie údaje",
"logout_reasons": {
"inactivity": "Boli ste odhlásení z dôvodu nečinnosti."
}
},
"permanent": "Trvalé",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin",
"administrator": "Administrátor",
"allowCommands": "Vykonávať príkazy",

View File

@@ -3,18 +3,18 @@
"cancel": "Avbryt",
"clear": "Rensa",
"close": "Stäng",
"continue": "Continue",
"continue": "Fortsätt",
"copy": "Kopiera",
"copyFile": "Kopiera fil",
"copyToClipboard": "Kopiera till urklipp",
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
"copyDownloadLinkToClipboard": "Kopiera hämtningslänk till urklipp",
"create": "Skapa",
"delete": "Ta bort",
"download": "Ladda ner",
"file": "File",
"folder": "Folder",
"fullScreen": "Toggle full screen",
"hideDotfiles": "Hide dotfiles",
"file": "Fil",
"folder": "Mapp",
"fullScreen": "Växla helskärm",
"hideDotfiles": "Dölj punktfiler",
"info": "Info",
"more": "Mer",
"move": "Flytta",
@@ -24,7 +24,7 @@
"ok": "OK",
"permalink": "Skapa en permanent länk",
"previous": "Föregående",
"preview": "Preview",
"preview": "Förhandsvisa",
"publish": "Publisera",
"rename": "Ändra namn",
"replace": "Ersätt",
@@ -36,27 +36,28 @@
"selectMultiple": "Välj flera",
"share": "Dela",
"shell": "Växla skal",
"submit": "Submit",
"submit": "Skicka",
"switchView": "Byt vy",
"toggleSidebar": "Växla sidofält",
"update": "Uppdatera",
"upload": "Ladda upp",
"openFile": "Open file",
"discardChanges": "Discard"
"openFile": "Öppna fil",
"discardChanges": "Förkasta",
"saveChanges": "Spara ändringar"
},
"download": {
"downloadFile": "Ladda ner fil",
"downloadFolder": "Ladda ner mapp",
"downloadSelected": "Download Selected"
"downloadSelected": "Hämta markerade"
},
"upload": {
"abortUpload": "Are you sure you wish to abort?"
"abortUpload": "Är du säker på att du vill avbryta?"
},
"errors": {
"forbidden": "Du saknar rättigheter till detta",
"internal": "Något gick fel",
"notFound": "Det går inte att nå den här platsen.",
"connection": "The server can't be reached."
"connection": "Servern går inte att nå."
},
"files": {
"body": "Huvud",
@@ -74,7 +75,7 @@
"sortByLastModified": "Sortera på senast ändrad",
"sortByName": "Sortera på namn",
"sortBySize": "Sortera på storlek",
"noPreview": "Preview is not available for this file."
"noPreview": "Förhandsvisning är inte tillgänglig för denna fil."
},
"help": {
"click": "välj fil eller mapp",
@@ -100,7 +101,10 @@
"submit": "Logga in",
"username": "Användarnamn",
"usernameTaken": "Användarnamn upptaget",
"wrongCredentials": "Fel inloggning"
"wrongCredentials": "Fel inloggning",
"logout_reasons": {
"inactivity": "Du har blivit utloggad på grund av inaktivitet."
}
},
"permanent": "Permanent",
"prompts": {
@@ -109,8 +113,8 @@
"currentlyNavigating": "För närvarande navigerar du på:",
"deleteMessageMultiple": "Är du säker på att du vill radera {count} filer(na)?",
"deleteMessageSingle": "Är du säker på att du vill radera denna fil/mapp",
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
"deleteUser": "Are you sure you want to delete this user?",
"deleteMessageShare": "Är du säker på att du vill ta bort denna utdelning({path})?",
"deleteUser": "Är du säker på att du vill ta bort denna användare?",
"deleteTitle": "Ta bort filer",
"displayName": "Visningsnamn:",
"download": "Ladda ner filer",
@@ -136,12 +140,12 @@
"scheduleMessage": "Pick a date and time to schedule the publication of this post.",
"show": "Visa",
"size": "Storlek",
"upload": "Upload",
"uploadFiles": "Uploading {files} files...",
"uploadMessage": "Select an option to upload.",
"optionalPassword": "Optional password",
"resolution": "Resolution",
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
"upload": "Ladda upp",
"uploadFiles": "Laddar upp {files} filer...",
"uploadMessage": "Välj ett alternativ att ladda upp.",
"optionalPassword": "Valfritt lösenord",
"resolution": "Upplösning",
"discardEditorChanges": "Är du säker på att du vill förkasta ändringarna du gjort?"
},
"search": {
"images": "Bilder",
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Tema för Ace editor",
"admin": "Admin",
"administrator": "Administratör",
"allowCommands": "Exekvera kommandon",
@@ -170,14 +175,14 @@
"commandRunnerHelp": "Här kan du ange kommandon som körs i de namngivna händelserna. Du måste skriva en per rad. Miljövariablerna {0} och {1} kommer att vara tillgängliga, och vara {0} i förhållande till {1}. För mer information om den här funktionen och de tillgängliga miljövariablerna, vänligen läs {2}.",
"commandsUpdated": "Kommandon uppdaterade!",
"createUserDir": "Auto skapa användarens hemkatalog när du lägger till nya användare",
"minimumPasswordLength": "Minimum password length",
"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 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",
"minimumPasswordLength": "Minsta lösenordslängd",
"tusUploads": "Uppdelade uppladdningar",
"tusUploadsHelp": "Filbläddraren stöder uppdelade filuppladdningar, vilket möjliggör effektiva, tillförlitliga, återupptagbara och uppdelade filuppladdningar även på otillförlitliga nätverk.",
"tusUploadsChunkSize": "Anger maximal storlek för en begäran (direkta uppladdningar används för mindre uppladdningar). Du kan ange ett helt tal som anger storleken i byte eller en sträng som 10 MB, 1 GB osv.",
"tusUploadsRetryCount": "Antal försök som ska göras om en del inte kan laddas upp.",
"userHomeBasePath": "Bassökväg för användarnas hemkataloger",
"userScopeGenerationPlaceholder": "Omfånget kommer att automatiskt genereras",
"createUserHomeDirectory": "Skapa användarens hemkatalog",
"customStylesheet": "Anpassad formatmall",
"defaultUserDescription": "Detta är standard inställningar för användare.",
"disableExternalLinks": "Inaktivera externa länkar (förutom dokumentation)",
@@ -188,7 +193,7 @@
"executeOnShellDescription": "Som standard kör fil bläddraren kommandona genom att anropa deras binärfiler direkt. Om du vill köra dem på ett skal i stället (till exempel bash eller PowerShell), kan du definiera det här med nödvändiga argument och flaggor. Om det är inställt kommer kommandot du kör att läggas till som ett argument. Detta gäller både användar kommandon och händelse krokar.",
"globalRules": "Det här är en global uppsättning regler för att tillåta och inte tillåta. De gäller för alla användare. Du kan definiera specifika regler för varje användares inställningar för att åsidosätta de här inställningarna.",
"globalSettings": "Globala inställningar",
"hideDotfiles": "Hide dotfiles",
"hideDotfiles": "Dölj punktfiler",
"insertPath": "Ange sökväg",
"insertRegex": "Sätt in regex expression",
"instanceName": "Instans namn",
@@ -199,7 +204,7 @@
"newUser": "Ny användare",
"password": "Lösenord",
"passwordUpdated": "Lösenord uppdaterat",
"path": "Path",
"path": "Sökväg",
"perm": {
"create": "Skapa filer och mappar",
"delete": "Ta bort filer och mappar",
@@ -217,17 +222,17 @@
"rules": "Regler",
"rulesHelp": "Här kan du definiera en uppsättning regler för godkänna och neka för den här specifika användaren. Den blockerade filen kommer inte upp i listningarna och kommer inte att vara tillgänglig till användaren. Vi stöder regex och sökvägar i förhållande till användarnas omfång.\n",
"scope": "Omfattning",
"setDateFormat": "Set exact date format",
"setDateFormat": "Ställ in exakt datumformat",
"settingsUpdated": "Inställning uppdaterad!",
"shareDuration": "Share Duration",
"shareManagement": "Share Management",
"shareDeleted": "Share deleted!",
"singleClick": "Use single clicks to open files and directories",
"shareDuration": "Utdelningstid",
"shareManagement": "Utdelningshantering",
"shareDeleted": "Utdelning borttagen!",
"singleClick": "Använd enkla klick för att öppna filer och kataloger",
"themes": {
"default": "System default",
"dark": "Dark",
"light": "Light",
"title": "Theme"
"default": "Systemet standard",
"dark": "rk",
"light": "Ljus",
"title": "Tema"
},
"user": "Användare",
"userCommands": "Kommandon",

View File

@@ -42,7 +42,8 @@
"update": "Güncelle",
"upload": "Yükle",
"openFile": "Dosyayı aç",
"discardChanges": "Discard"
"discardChanges": "Discard",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Dosyayı indir",
@@ -100,7 +101,10 @@
"submit": "Giriş",
"username": "Kullanıcı adı",
"usernameTaken": "Kullanıcı adı mevcut",
"wrongCredentials": "Yanlış hesap bilgileri"
"wrongCredentials": "Yanlış hesap bilgileri",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Kalıcı",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Yönetim",
"administrator": "Yönetici",
"allowCommands": "Komutları çalıştır",

View File

@@ -42,7 +42,8 @@
"update": "Оновити",
"upload": "Вивантажити",
"openFile": "Відкрити файл",
"discardChanges": "Скасувати"
"discardChanges": "Скасувати",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Завантажити файл",
@@ -100,7 +101,10 @@
"submit": "Увійти",
"username": "Ім'я користувача",
"usernameTaken": "Ім'я користувача вже використовується",
"wrongCredentials": "Неправильне ім'я користувача або пароль"
"wrongCredentials": "Неправильне ім'я користувача або пароль",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Постійний",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Відео"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Адмін",
"administrator": "Адміністратор",
"allowCommands": "Запуск команд",
@@ -173,15 +178,15 @@
"minimumPasswordLength": "Мінімальна довжина паролю",
"tusUploads": "Фрагментовані завантаження",
"tusUploadsHelp": "File Browser підтримує завантаження частинами, дозволяючи створення ефективних, надійних, відновлюваних та фрагментованих завантажень навіть при ненадійному з'єднанні.",
"tusUploadsChunkSize": "Вказує на максимальний розмір запиту (для менших завантажень використовуватиметься пряме завантаження). Ви можете ввести цілочисельне значення у байтах або ж рядок на кшталт 10MB, 1GB тощо.",
"tusUploadsRetryCount": "Кількість повторних спроб які потрібно виконати, якщо фрагмент не вдалося завантажити.",
"tusUploadsChunkSize": "Максимальний розмір запиту (для менших завантажень використовуватиметься пряме завантаження). Ви можете ввести цілочисельне значення у байтах або ж рядок на кшталт 10MB, 1GB тощо",
"tusUploadsRetryCount": "Кількість повторних спроб які потрібно виконати, якщо фрагмент не вдалося завантажити",
"userHomeBasePath": "Основний шлях для домашніх каталогів користувачів",
"userScopeGenerationPlaceholder": "Кореневий каталог буде згенеровано автоматично",
"createUserHomeDirectory": "Створити домашній каталог користувача",
"customStylesheet": "Свій стиль",
"defaultUserDescription": "Це налаштування за замовчуванням для нових користувачів.",
"disableExternalLinks": "Вимкнути зовнішні посилання (крім документації)",
"disableUsedDiskPercentage": "Disable used disk percentage graph",
"disableUsedDiskPercentage": "Вимкнути графік використання диску",
"documentation": "документація",
"examples": "Приклади",
"executeOnShell": "Виконати в командному рядку",
@@ -231,7 +236,7 @@
},
"user": "Користувач",
"userCommands": "Команди",
"userCommandsHelp": "Список команд, доступних користувачу, розділений пробілами. Приклад:\n",
"userCommandsHelp": "Список команд, доступних користувачу, розділений пробілами. Наприклад:\n",
"userCreated": "Користувача створено!",
"userDefaults": "Налаштування користувача за замовчуванням",
"userDeleted": "Користувача видалено!",

View File

@@ -42,7 +42,8 @@
"update": "Cập nhật",
"upload": "Tải lên",
"openFile": "Mở tệp",
"discardChanges": "Hủy bỏ thay đổi"
"discardChanges": "Hủy bỏ thay đổi",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "Tải xuống tệp tin",
@@ -100,7 +101,10 @@
"submit": "Đăng nhập",
"username": "Tên người dùng",
"usernameTaken": "Tên người dùng đã tồn tại",
"wrongCredentials": "Thông tin đăng nhập không đúng"
"wrongCredentials": "Thông tin đăng nhập không đúng",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "Vĩnh viễn",
"prompts": {
@@ -154,6 +158,7 @@
"video": "Video"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Quản trị viên",
"administrator": "Người quản trị",
"allowCommands": "Thực thi lệnh",

View File

@@ -42,7 +42,8 @@
"update": "更新",
"upload": "上传",
"openFile": "打开文件",
"discardChanges": "放弃更改"
"discardChanges": "放弃更改",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "下载文件",
@@ -100,7 +101,10 @@
"submit": "登录",
"username": "用户名",
"usernameTaken": "用户名已经被使用",
"wrongCredentials": "用户名或密码错误"
"wrongCredentials": "用户名或密码错误",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "永久",
"prompts": {
@@ -154,6 +158,7 @@
"video": "视频"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "管理员",
"administrator": "管理员",
"allowCommands": "执行命令Shell 命令)",

View File

@@ -42,7 +42,8 @@
"update": "更新",
"upload": "上傳",
"openFile": "開啟檔案",
"discardChanges": "放棄變更"
"discardChanges": "放棄變更",
"saveChanges": "Save changes"
},
"download": {
"downloadFile": "下載檔案",
@@ -100,7 +101,10 @@
"submit": "登入",
"username": "帳號",
"usernameTaken": "用戶名已存在",
"wrongCredentials": "帳號或密碼錯誤"
"wrongCredentials": "帳號或密碼錯誤",
"logout_reasons": {
"inactivity": "You have been logged out due to inactivity."
}
},
"permanent": "永久",
"prompts": {
@@ -154,6 +158,7 @@
"video": "影片"
},
"settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "管理員",
"administrator": "管理員",
"allowCommands": "執行命令",

View File

@@ -7,9 +7,11 @@ export const useAuthStore = defineStore("auth", {
state: (): {
user: IUser | null;
jwt: string;
logoutTimer: number | null;
} => ({
user: null,
jwt: "",
logoutTimer: null,
}),
getters: {
// user and jwt getter removed, no longer needed
@@ -37,5 +39,8 @@ export const useAuthStore = defineStore("auth", {
clearUser() {
this.$reset();
},
setLogoutTimer(logoutTimer: number | null) {
this.logoutTimer = logoutTimer;
},
},
});

View File

@@ -41,6 +41,7 @@ export const useLayoutStore = defineStore("layout", {
prompt: value,
confirm: null,
action: undefined,
saveAction: undefined,
props: null,
close: null,
});
@@ -51,6 +52,7 @@ export const useLayoutStore = defineStore("layout", {
prompt: value.prompt,
confirm: value?.confirm,
action: value?.action,
saveAction: value?.saveAction,
props: value?.props,
close: value?.close,
});

View File

@@ -2,6 +2,7 @@ interface PopupProps {
prompt: string;
confirm?: any;
action?: PopupAction;
saveAction?: () => void;
props?: any;
close?: (() => Promise<string>) | null;
}

View File

@@ -17,10 +17,11 @@ interface SettingsDefaults {
viewMode: ViewModeType;
singleClick: boolean;
sorting: Sorting;
perm: Permissions;
perm: UserPermissions;
commands: any[];
hideDotfiles: boolean;
dateFormat: boolean;
aceEditorTheme: string;
}
interface SettingsBranding {

View File

@@ -4,7 +4,7 @@ interface IUser {
password: string;
scope: string;
locale: string;
perm: Permissions;
perm: UserPermissions;
commands: string[];
rules: IRule[];
lockPassword: boolean;
@@ -13,26 +13,27 @@ interface IUser {
dateFormat: boolean;
viewMode: ViewModeType;
sorting?: Sorting;
aceEditorTheme: string;
}
type ViewModeType = "list" | "mosaic" | "mosaic gallery";
interface IUserForm {
id?: number;
username?: string;
password?: string;
scope?: string;
locale?: string;
perm?: Permissions;
commands?: string[];
rules?: IRule[];
lockPassword?: boolean;
hideDotfiles?: boolean;
singleClick?: boolean;
dateFormat?: boolean;
username: string;
password: string;
scope: string;
locale: string;
perm: UserPermissions;
commands: string[];
rules: IRule[];
lockPassword: boolean;
hideDotfiles: boolean;
singleClick: boolean;
dateFormat: boolean;
}
interface Permissions {
interface UserPermissions {
admin: boolean;
copy: boolean;
create: boolean;

View File

@@ -4,6 +4,7 @@ import type { JwtPayload } from "jwt-decode";
import { jwtDecode } from "jwt-decode";
import { baseURL, noAuth } from "./constants";
import { StatusError } from "@/api/utils";
import { setSafeTimeout } from "@/api/utils";
export function parseToken(token: string) {
// falsy or malformed jwt will throw InvalidTokenError
@@ -16,6 +17,18 @@ export function parseToken(token: string) {
const authStore = useAuthStore();
authStore.jwt = token;
authStore.setUser(data.user);
if (authStore.logoutTimer) {
clearTimeout(authStore.logoutTimer);
}
const expiresAt = new Date(data.exp! * 1000);
const timeout = expiresAt.getTime() - Date.now();
authStore.setLogoutTimer(
setSafeTimeout(() => {
logout("inactivity");
}, timeout)
);
}
export async function validateLogin() {
@@ -92,7 +105,7 @@ export async function signup(username: string, password: string) {
}
}
export function logout() {
export function logout(reason?: string) {
document.cookie = "auth=; Max-Age=0; Path=/; SameSite=Strict;";
const authStore = useAuthStore();
@@ -102,6 +115,15 @@ export function logout() {
if (noAuth) {
window.location.reload();
} else {
router.push({ path: "/login" });
if (typeof reason === "string" && reason.trim() !== "") {
router.push({
path: "/login",
query: { "logout-reason": reason },
});
} else {
router.push({
path: "/login",
});
}
}
}

View File

@@ -1,4 +1,6 @@
import { theme } from "./constants";
import "ace-builds";
import { themesByName } from "ace-builds/src-noconflict/ext-themelist";
export const getTheme = (): UserTheme => {
return (document.documentElement.className as UserTheme) || theme;
@@ -32,3 +34,17 @@ export const getMediaPreference = (): UserTheme => {
return "light";
}
};
export const getEditorTheme = (themeName: string) => {
if (!themeName.startsWith("ace/theme/")) {
themeName = `ace/theme/${themeName}`;
}
const themeKey = themeName.replace("ace/theme/", "");
if (themesByName[themeKey] !== undefined) {
return themeName;
} else if (getTheme() === "dark") {
return "ace/theme/twilight";
} else {
return "ace/theme/chrome";
}
};

View File

@@ -3,6 +3,9 @@
<form @submit="submit">
<img :src="logoURL" alt="File Browser" />
<h1>{{ name }}</h1>
<p v-if="reason != null" class="logout-message">
{{ t(`login.logout_reasons.${reason}`) }}
</p>
<div v-if="error !== ''" class="wrong">{{ error }}</div>
<input
@@ -70,6 +73,8 @@ const toggleMode = () => (createMode.value = !createMode.value);
const $showError = inject<IToastError>("$showError")!;
const reason = route.query["logout-reason"] ?? null;
const submit = async (event: Event) => {
event.preventDefault();
event.stopPropagation();

Some files were not shown because too many files have changed in this diff Show More