Compare commits

...

157 Commits

Author SHA1 Message Date
Oleg Lobanov
e1f658633d chore(release): 2.13.0 2021-03-14 20:02:02 +01:00
Oleg Lobanov
9c79105c02 chore: prevent deleting .gitignore from dist folder 2021-03-14 19:59:55 +01:00
Jürgen Hötzel
6d5ceae8b4 fix: wait for async command exit (#1326)
This prevents the accumulation of zombie processes when using
async (&) event commands. Also log async command failures.
2021-03-14 19:32:14 +01:00
Oleg Lobanov
381f09087a Merge pull request #1321 from ramiresviana/fixes-6 2021-03-14 14:19:50 +01:00
Ramires Viana
426b38bb33 fix: root path name on archive 2021-03-12 15:52:52 +00:00
Ramires Viana
488d98045e fix: download current dir on file listing 2021-03-12 15:28:49 +00:00
Ramires Viana
7955e0720b fix: encoded file path on share 2021-03-12 15:15:56 +00:00
Ramires Viana
e017a19985 fix: full file path on share 2021-03-12 12:14:58 +00:00
Ramires Viana
f8df76f526 fix: header dropdown icon color on previewer 2021-03-11 16:01:54 +00:00
Ramires Viana
11ebaec5f0 fix: modified time on info prompt 2021-03-11 15:20:37 +00:00
Ramires Viana
326b35a7ac fix: item dragging on file listing 2021-03-11 12:09:12 +00:00
Ramires Viana
5bf15548d0 fix: check rules on http resource handlers 2021-03-10 17:38:11 +00:00
Ramires Viana
6a734c0139 fix: stuck icon on header button 2021-03-10 15:32:10 +00:00
Ramires Viana
81b6f4d6f6 fix: update image cache when replacing 2021-03-10 15:14:01 +00:00
Ramires Viana
0b92d94570 chore: split POST method on resource http handler 2021-03-10 13:32:11 +00:00
Oleg Lobanov
fc5506179a refactor: migrate from rice to embed.FS 2021-03-09 19:09:32 +01:00
Oleg Lobanov
0fe34ad224 Merge pull request #1307 from ramiresviana/tweaks-1
Frontend code quality changes
2021-03-09 18:26:46 +01:00
Ramires Viana
54f35701a2 fix: archive contains parent path on Windows 2021-03-09 15:54:54 +00:00
FrzMtrsprt
a809404ce1 chore: update zh-cn.json (#1311) 2021-03-08 10:24:16 +01:00
Po Chen
fb32e44b47 chore: update zh-cn.json (#1309) 2021-03-07 15:41:05 +01:00
Oleg Lobanov
e9c0369062 chore(release): 2.12.1 2021-03-07 15:25:34 +01:00
Oleg Lobanov
7358b3fe31 fix: add missing default config into the docker image 2021-03-07 15:23:12 +01:00
Ramires Viana
2a1f759e9e chore: remove prompts events 2021-03-04 14:40:18 +00:00
Oleg Lobanov
2fccb8c367 chore(release): 2.12.0 2021-03-04 13:12:19 +01:00
Oleg Lobanov
e039d95192 chore: fix major docker tag name 2021-03-04 13:12:03 +01:00
Oleg Lobanov
0f96031d6f Merge pull request #1305 from filebrowser/github_actions 2021-03-04 11:39:30 +01:00
Oleg Lobanov
6214fc84fa chore: add github action badge 2021-03-04 11:35:52 +01:00
Oleg Lobanov
47578e02e3 ci: migrate to github actions 2021-03-04 11:35:52 +01:00
Oleg Lobanov
35a4379b67 chore: go mod tidy 2021-03-04 00:57:35 +01:00
Oleg Lobanov
23f84642e6 build: use make for building the project (#1304) 2021-03-04 00:10:08 +01:00
Ramires Viana
edb9e85efd chore: share view logic responsability 2021-03-03 17:46:37 +00:00
Oleg Lobanov
2d2c598fa6 feat: add homebrew tap 2021-03-03 16:25:03 +01:00
Oleg Lobanov
cf4836dc75 feat: build multi-arch docker images 2021-03-03 13:46:22 +01:00
Ramires Viana
d8306559fd chore: breadcrumbs component 2021-03-03 12:25:59 +00:00
WeidiDeng
e8c9d1c539 feat: added tiff files preview support (#1222) 2021-03-02 12:14:32 +01:00
Alvaro Aleman
d8f415f8ab feat: allow to password protect shares (#1252)
This changes allows to password protect shares. It works by:
* Allowing to optionally pass a password when creating a share
* If set, the password + salt that is configured via a new flag will be
  hashed via bcrypt and the hash stored together with the rest of the
  share
* Additionally, a random 96 byte long token gets generated and stored
  as part of the share
* When the backend retrieves an unauthenticated request for a share that
  has authentication configured, it will return a http 401
* The frontend detects this and will show a login prompt
* The actual download links are protected via an url arg that contains
  the previously generated token. This allows us to avoid buffering the
  download in the browser and allows pasting the link without breaking
  it
2021-03-02 12:00:18 +01:00
Ramires Viana
7b6579ac8a chore: files view dynamic component 2021-03-01 16:12:17 +00:00
Ramires Viana
057307181e chore: removed header buttons components 2021-03-01 13:41:35 +00:00
Ramires Viana
4fb832c042 feat: increased header button counter size 2021-03-01 12:57:39 +00:00
Ramires Viana
e503cb69f2 chore: files pages logic responsability 2021-02-26 15:10:21 +00:00
Ramires Viana
95811e99bc chore: header bar component 2021-02-25 18:37:07 +00:00
Ramires Viana
62fff5ca60 feat: larger previewer content 2021-02-22 16:01:13 +00:00
Ramires Viana
5b28aa0848 feat: improved settings navbar 2021-02-19 16:01:43 +00:00
Ramires Viana
db5aad8eb6 feat: dual pane settings view 2021-02-19 13:15:46 +00:00
Alvaro Aleman
977ec33918 chore: close preview on esc key press (#1288) 2021-02-16 17:15:04 +01:00
Ramires Viana
1819377897 feat: improved sharing prompt 2021-02-16 15:39:11 +00:00
niubility000
f1b7bd59f6 fix: double click to zoom pics in phone's browser (#1274)
fixed this : https://github.com/filebrowser/filebrowser/issues/1266
2021-02-08 09:54:40 +01:00
WeidiDeng
f3afd5cb79 fix: environmental variables not expanded in command (#1241) 2021-01-19 17:02:46 +01:00
Oleg Lobanov
e6a5bf116e chore: remove empty string from locales (closes #1245) 2021-01-19 16:56:06 +01:00
Oleg Lobanov
21b5a76fa7 chore: refactor search function 2021-01-12 19:14:23 +01:00
WeidiDeng
b6263eb607 chore: search by type with empty file name (#1228) 2021-01-12 18:08:23 +01:00
WeidiDeng
c8257e848e chore: move single click to user profile page (#1236)
Single clicks applies to FileList component.
2021-01-12 00:15:26 +01:00
叫我彭一凡
05bb7c8553 fix: fetch resource api once when sorting (closes #1172) (#1202) 2021-01-12 00:00:40 +01:00
WeidiDeng
b600b11415 feat: share management delete confirm (#1212) 2021-01-11 23:54:16 +01:00
Oleg Lobanov
019ce80fc5 fix: don't allow to remove root user 2021-01-11 22:33:36 +01:00
Adam Dobrawy
8cea2f75b3 chore: fix url to documentation in templates (#1231) 2021-01-07 11:48:53 +01:00
WeidiDeng
6914063853 feat: allow disabling file detections by reading header (#1175) 2021-01-07 11:30:17 +01:00
Oleg Lobanov
43e0d4a856 chore: fix translation files indent 2020-12-31 12:31:26 +01:00
Oleg Lobanov
066d8e8d6c chore: add ru translation 2020-12-31 12:26:49 +01:00
Oleg Lobanov
948e05c083 chore(release): 2.11.0 2020-12-28 17:37:19 +01:00
WeidiDeng
fb5b28d9cb feat: download shared subdirectory (#1184)
Co-authored-by: Oleg Lobanov <oleg@lobanov.me>
2020-12-28 17:35:29 +01:00
WeidiDeng
677bce376b feat: add sharing management (#1178) (closes #1000) 2020-12-24 19:02:28 +01:00
Alexis Lefebvre
8faa96f5e6 chore: fix typo costumize -> costumize (#1194) 2020-12-24 18:24:15 +01:00
WeidiDeng
f62806f6c9 fix: check user input to prevent permission elevation (#1196) (closes #1195) 2020-12-24 18:22:48 +01:00
Oleg Lobanov
58835b7e53 fix: move files between different volumes (closes #1177) 2020-12-24 17:50:27 +01:00
WeidiDeng
7a5298a755 fix: delete extra remove prefix (#1186)
Fix file actions within /files dir
2020-12-16 16:08:56 +01:00
WeidiDeng
bc4a6462ce chore: use command key to select multiple files on mac (#1183) 2020-12-12 14:09:50 +01:00
WeidiDeng
ac3673e111 fix: recaptcha race condition (#1176) 2020-12-08 11:26:29 +01:00
Oleg Lobanov
c746c1931d chore(release): 2.10.0 2020-11-24 12:00:10 +01:00
Oleg Lobanov
586d198d47 fix: fix hanging when reading a named pipe file (closes #1155) 2020-11-24 11:37:31 +01:00
Matt Doyle
9515ceeb42 feat: automatically jump to the next photo when deleting while previewing (#1143) 2020-11-23 19:08:14 +01:00
Julien Loir
e8b4e9af46 feat: add single click mode (#1139) 2020-11-23 19:06:37 +01:00
Tiger Nie
10e399b3c3 feat: add hide dotfiles param (#1148) 2020-11-20 11:51:28 +01:00
Oleg Lobanov
dcbc3286e2 Merge pull request #1133 from ramiresviana/fixes-5
Some fixes and shared view improvements
2020-11-04 17:58:18 +01:00
xufanglu
b185f9b56e chore: fix typo in config_init.go (#1131) 2020-11-04 17:47:36 +01:00
Ramires Viana
7096b3dab9 fix: empty folder in archive 2020-11-04 15:56:27 +00:00
Ramires Viana
36cacdf598 feat: shared item information 2020-11-04 15:56:27 +00:00
Ramires Viana
4e48ffc14d fix: previewer title overflow 2020-11-04 15:56:27 +00:00
Ramires Viana
e119bc55ea feat: shared folder file listing 2020-11-04 15:56:05 +00:00
Ramires Viana
1ce3068a99 fix: resource rename action invalid path 2020-11-03 12:30:56 +00:00
Liubomyr Piadyk
d562d1a60d chore: fix readme typo (#1128)
Commander runner -> Command runner
At other places it's referenced as Command runner.
2020-10-29 17:29:29 +01:00
Oleg Lobanov
9f858398ab chore(release): 2.9.0 2020-10-21 16:52:29 +02:00
Aiden McClelland
0ac80e8387 feat: support WKWebview custom protocol (#1113) 2020-10-21 16:41:09 +02:00
Hissy
0dca0b92d1 chore: update zh-cn and zh-tw (#1121) 2020-10-21 16:39:41 +02:00
Oleg Lobanov
c9b36ba32e Merge pull request #1124 from ramiresviana/fixes-4 2020-10-21 16:37:39 +02:00
Ramires Viana
f2c4e78381 fix: allow start from Windows explorer 2020-10-19 13:41:40 +00:00
Ramires Viana
05bff54b71 fix: preview case sensitive file extension 2020-10-19 13:25:09 +00:00
Ramires Viana
2bd163d92a fix: search missing path slash 2020-10-19 13:14:36 +00:00
Ramires Viana
5e27ba5c8c fix: file upload missing path slash 2020-10-19 13:11:26 +00:00
Oleg Lobanov
5aaeb3b76d chore(release): 2.8.0 2020-10-05 09:53:09 +02:00
Daniel Pham
36fb9f562a fix: fix empty command name (#1106) 2020-10-05 09:52:27 +02:00
Xabi
ad99bf1801 fix: fix panic when accessing nonexistent .js file in static path (#1105) 2020-10-02 15:09:03 +02:00
Oleg Lobanov
4c2a094255 Merge pull request #1100 from ramiresviana/fixes-3 2020-10-01 16:53:35 +02:00
Keagan McClelland
97693cc611 feat: add disable exec flag (#1090) 2020-10-01 16:45:24 +02:00
Ramires Viana
c6d4fcd08f fix: empty commands setting 2020-09-29 14:05:03 +00:00
Ramires Viana
dd7b9ddd85 fix: preview key shortcut conflict 2020-09-29 14:04:55 +00:00
Ramires Viana
26d62e4117 fix: search results absolute url 2020-09-29 14:04:43 +00:00
Ramires Viana
babd7783af fix: file upload path encoding 2020-09-29 14:04:03 +00:00
Oleg Lobanov
1529e796df chore(release): 2.7.0 2020-09-11 19:21:08 +02:00
Oleg Lobanov
d4b904b92b chore: pass docker password via stdin 2020-09-11 18:57:14 +02:00
Oleg Lobanov
12d4177823 build: bump go version to 1.15.2 (#1081) 2020-09-11 18:07:01 +02:00
Oleg Lobanov
8142b32f38 feat: put selected files in the root of the archive (closes #1065) 2020-09-11 16:54:22 +02:00
Oleg Lobanov
c5abbb4e1c chore: fix lint errors 2020-09-11 16:02:16 +02:00
Oleg Lobanov
65ac73414f feat: add --socket-perm flag to control unix socket file permissions (closes #1060) 2020-09-11 15:59:06 +02:00
Oleg Lobanov
ede4213c8e chore: fix french translation (closes #1071) 2020-09-11 15:16:58 +02:00
Agneev Mukherjee
b60d291490 chore: fix URLs for assets (#1074) 2020-09-05 15:23:42 +02:00
Oleg Lobanov
b9ede79888 Merge pull request #1066 from ramiresviana/preview-mobile-dropdown 2020-08-25 16:33:57 +02:00
Ramires Viana
3d2cb838d1 feat: preview size button 2020-08-25 14:14:15 +00:00
Ramires Viana
778734419d feat: preview mobile dropdown 2020-08-18 12:47:23 +00:00
Oleg Lobanov
be8683f556 chore(release): 2.6.2 2020-08-05 11:55:16 +02:00
Davide Maggio
c3450f4614 chore: return text/plain header in auth response (#1051) 2020-08-05 10:48:03 +02:00
Oleg Lobanov
5881bc9ab0 chore: fix preview of files with non-latin names (closes #1056) 2020-08-05 10:40:03 +02:00
Oleg Lobanov
a2fb499a20 chore(release): 2.6.1 2020-07-28 13:40:19 +02:00
Oleg Lobanov
411a928fea chore: fix lint errors 2020-07-28 13:40:06 +02:00
Oleg Lobanov
f5d02cdde9 fix: delete cached previews when deleting file 2020-07-28 11:59:55 +02:00
Oleg Lobanov
c9340af8d0 fix: escape special characters in preview url (closes #1002) 2020-07-28 11:59:32 +02:00
Oleg Lobanov
a722bcc13f chore(release): 2.6.0 2020-07-27 19:52:48 +02:00
Oleg Lobanov
77fe3cfc60 ci: fix go version on release step 2020-07-27 19:51:09 +02:00
Oleg Lobanov
470f93cefc Merge pull request #1044 from filebrowser/fix_img_resize 2020-07-27 19:39:08 +02:00
Oleg Lobanov
92fde4dd12 build: set limit for vuejs build threads 2020-07-27 19:35:02 +02:00
Oleg Lobanov
95bc92955f feat: cache resized images 2020-07-27 19:26:45 +02:00
Oleg Lobanov
f2f914221c chore: bump go to 1.14.6 2020-07-27 19:26:45 +02:00
Oleg Lobanov
c2d8038c63 chore: add testing step to ci 2020-07-27 19:26:44 +02:00
Oleg Lobanov
cb8ac5ebf1 chore: add resize tests 2020-07-27 19:26:44 +02:00
Oleg Lobanov
aa78e3ab1f feat: add param to disable img resizing 2020-07-27 19:26:44 +02:00
Oleg Lobanov
bc00165094 feat: add lazy load of image thumbnails 2020-07-27 19:26:44 +02:00
Oleg Lobanov
94ef59602f feat: limit image resize workers 2020-07-27 19:26:44 +02:00
Oleg Lobanov
14e2f84ceb Merge pull request #1042 from ramiresviana/fixes-2 2020-07-23 15:03:10 +02:00
Ramires Viana
f228fa5540 fix: conflict handling on upload button 2020-07-23 12:02:09 +00:00
Ramires Viana
f2d2c1cbf8 fix: drop feedback 2020-07-23 12:02:09 +00:00
Ramires Viana
d9be370e24 fix: missing error message 2020-07-23 12:02:09 +00:00
Ramires Viana
727c63b98e fix: parent verification on copy 2020-07-23 12:02:02 +00:00
Ramires Viana
34dfb49b71 fix: path separator inconsistency on rename 2020-07-20 17:45:45 +00:00
Henrique Dias
0b0a704d44 chore: remove hacdias/fileutils dep (#1037) 2020-07-18 20:10:22 +02:00
Oleg Lobanov
2d99d0bf13 chore(release): 2.5.0 2020-07-17 18:12:00 +02:00
Oleg Lobanov
1790df2090 Merge pull request #1026 from ramiresviana/fixes 2020-07-17 17:41:17 +02:00
Ramires Viana
10570ade44 fix: reset clipboard after pasting cutted files 2020-07-17 14:11:23 +00:00
Ramires Viana
43526d9d1a feat: duplicate files in the same directory 2020-07-17 14:11:23 +00:00
Ramires Viana
2636f876ab feat: rename option on replace prompt 2020-07-17 14:11:15 +00:00
Ramires Viana
eed9da1471 feat: file copy, move and paste conflict checking 2020-07-17 12:37:52 +00:00
Ramires Viana
9a2ebbabe2 fix: blinking previewer 2020-07-17 12:37:52 +00:00
Ramires Viana
716396a726 feat: add previewer title and loading indicator 2020-07-17 12:32:21 +00:00
Ramires Viana
0727496601 fix: remove incomplete uploaded files 2020-07-14 00:21:15 +00:00
Ramires Viana
194030fcfc fix: prompt before closing window 2020-07-14 00:12:41 +00:00
Ramires Viana
b3b644527d fix: dark theme colors 2020-07-14 00:12:33 +00:00
Ramires Viana
7e5beeff46 fix: directory conflict checking 2020-07-13 14:20:56 +00:00
Oleg Lobanov
a47b69bcec Merge pull request #1021 from ramiresviana/upload-queue 2020-07-13 11:20:59 +02:00
Ramires Viana
6ec6a23861 feat: upload queue 2020-07-10 00:01:37 +00:00
Ramires Viana
c9cc0d3d5d refactor: upload vuex module 2020-07-10 00:01:37 +00:00
Ramires Viana
28d2b35718 refactor: upload utils 2020-07-10 00:01:37 +00:00
Ramires Viana
b4f131be50 refactor: uploading counters vuex state 2020-07-10 00:01:37 +00:00
Oleg Lobanov
d0b359561f chore(release): 2.4.0 2020-07-07 16:53:51 +02:00
Fabian Fritzsche
453636dfe2 fix: add preview bypass for .gif files (#1012) 2020-07-07 16:47:11 +02:00
Oleg Lobanov
b1605aa6d3 Merge pull request #1014 from ramiresviana/full-screen-editor 2020-07-06 17:06:12 +02:00
Oleg Lobanov
23503b80a4 Merge pull request #1015 from ramiresviana/prompt-key-shortcut-conflict 2020-07-06 17:03:05 +02:00
Ramires Viana
0d69fbd9a3 fix: prompt key shortcut conflict 2020-07-04 14:19:03 +00:00
Ramires Viana
0d665e528f feat: full screen editor 2020-07-04 03:11:51 +00:00
176 changed files with 7892 additions and 4208 deletions

View File

@@ -1,79 +0,0 @@
version: 2
jobs:
lint:
docker:
- image: golangci/golangci-lint:v1.27.0
steps:
- checkout
- run: golangci-lint run -v
build-node:
docker:
- image: circleci/node
steps:
- checkout
- run:
name: "Build"
command: ./wizard.sh -a
- run:
name: "Cleanup"
command: rm -rf frontend/node_modules
- persist_to_workspace:
root: .
paths:
- '*'
build-go:
docker:
- image: circleci/golang:1.14.3
steps:
- attach_workspace:
at: '~/project'
- run:
name: "Compile"
command: GOOS=linux GOARCH=amd64 ./wizard.sh -c
- run:
name: "Cleanup"
command: |
rm -rf frontend/build
git checkout -- go.sum # TODO: why is it being changed?
- persist_to_workspace:
root: .
paths:
- '*'
release:
docker:
- image: circleci/golang:1.14.3
steps:
- attach_workspace:
at: '~/project'
- setup_remote_docker
- run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- run: curl -sL https://git.io/goreleaser | bash
- run: docker logout
workflows:
version: 2
build-workflow:
jobs:
- lint:
filters:
tags:
only: /.*/
- build-node:
filters:
tags:
only: /.*/
- build-go:
filters:
tags:
only: /.*/
requires:
- build-node
- lint
- release:
context: deploy
requires:
- build-go
filters:
tags:
only: /^v.*/
branches:
ignore: /.*/

View File

@@ -1,3 +1,2 @@
testdata/ *
.github/ !filebrowser
**.git

5
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,5 @@
# 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

66
.github/workflows/main.yaml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: main
on:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: 1.16
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm i -g commitlint
- run: make lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: 1.16
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: make test
release:
runs-on: ubuntu-latest
needs: [lint, test]
if: startsWith(github.event.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-go@v2
with:
go-version: 1.16
- uses: actions/setup-node@v2
with:
node-version: '14'
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build fronetend
run: make build-frontend
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}

4
.gitignore vendored
View File

@@ -5,11 +5,9 @@ _old
rice-box.go rice-box.go
.idea/ .idea/
filebrowser filebrowser
dist/
.DS_Store .DS_Store
node_modules node_modules
/frontend/dist
# local env files # local env files
.env.local .env.local
@@ -28,3 +26,5 @@ yarn-error.log*
*.njsproj *.njsproj
*.sln *.sln
*.sw* *.sw*
bin/
build/

View File

@@ -63,7 +63,6 @@ linters:
- goconst - goconst
- gocritic - gocritic
- gocyclo - gocyclo
- gofmt
- goimports - goimports
- golint - golint
- gomnd - gomnd

View File

@@ -3,10 +3,6 @@ project_name: filebrowser
env: env:
- GO111MODULE=on - GO111MODULE=on
before:
hooks:
- go mod download
build: build:
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
@@ -19,10 +15,6 @@ build:
- linux - linux
- windows - windows
- freebsd - freebsd
- netbsd
- openbsd
- dragonfly
- solaris
goarch: goarch:
- amd64 - amd64
- 386 - 386
@@ -35,14 +27,8 @@ build:
ignore: ignore:
- goos: darwin - goos: darwin
goarch: 386 goarch: 386
- goos: openbsd
goarch: arm
- goos: freebsd - goos: freebsd
goarch: arm goarch: arm
- goos: netbsd
goarch: arm
- goos: solaris
goarch: arm
archives: archives:
- -
@@ -55,53 +41,106 @@ archives:
dockers: dockers:
- -
dockerfile: Dockerfile dockerfile: Dockerfile
binaries: use_buildx: true
- filebrowser build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/amd64"
goos: linux goos: linux
goarch: amd64 goarch: amd64
goarm: ''
image_templates: image_templates:
- "filebrowser/filebrowser:latest" - "filebrowser/filebrowser:{{ .Tag }}-amd64"
- "filebrowser/filebrowser:{{ .Tag }}" - "filebrowser/filebrowser:v{{ .Major }}-amd64"
- "filebrowser/filebrowser:v{{ .Major }}"
extra_files: extra_files:
- .docker.json - .docker.json
- -
dockerfile: Dockerfile dockerfile: Dockerfile
binaries: use_buildx: true
- filebrowser build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm64"
goos: linux
goarch: arm64
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
extra_files:
- .docker.json
-
dockerfile: Dockerfile
use_buildx: true
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm/v6"
goos: linux goos: linux
goarch: arm goarch: arm
goarm: '5' goarm: '6'
image_templates: image_templates:
- "filebrowser/filebrowser:pi" - "filebrowser/filebrowser:{{ .Tag }}-armv6"
- "filebrowser/filebrowser:{{ .Tag }}-pi" - "filebrowser/filebrowser:v{{ .Major }}-armv6"
- "filebrowser/filebrowser:v{{ .Major }}-pi"
extra_files: extra_files:
- .docker.json - .docker.json
- -
dockerfile: Dockerfile.alpine dockerfile: Dockerfile
binaries: use_buildx: true
- filebrowser build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm/v7"
goos: linux goos: linux
goarch: amd64 goarch: arm
goarm: '' goarm: '7'
image_templates: image_templates:
- "filebrowser/filebrowser:alpine" - "filebrowser/filebrowser:{{ .Tag }}-armv7"
- "filebrowser/filebrowser:{{ .Tag }}-alpine" - "filebrowser/filebrowser:v{{ .Major }}-armv7"
- "filebrowser/filebrowser:v{{ .Major }}-alpine"
extra_files: extra_files:
- .docker.json - .docker.json
- docker_manifests:
dockerfile: Dockerfile.debian - name_template: "filebrowser/filebrowser:latest"
binaries:
- filebrowser
goos: linux
goarch: amd64
goarm: ''
image_templates: image_templates:
- "filebrowser/filebrowser:debian" - "filebrowser/filebrowser:{{ .Tag }}-amd64"
- "filebrowser/filebrowser:{{ .Tag }}-debian" - "filebrowser/filebrowser:{{ .Tag }}-arm64"
- "filebrowser/filebrowser:v{{ .Major }}-debian" - "filebrowser/filebrowser:{{ .Tag }}-armv6"
extra_files: - "filebrowser/filebrowser:{{ .Tag }}-armv7"
- .docker.json - name_template: "filebrowser/filebrowser:{{ .Tag }}"
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
- "filebrowser/filebrowser:{{ .Tag }}-armv6"
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
- name_template: "filebrowser/filebrowser:v{{ .Major }}"
image_templates:
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
- "filebrowser/filebrowser:v{{ .Major }}-armv6"
- "filebrowser/filebrowser:v{{ .Major }}-armv7"
brews:
- name: filebrowser
tap:
owner: filebrowser
name: homebrew-tap
folder: Formula
homepage: https://filebrowser.org
commit_author:
name: FileBrowser Robot
email: robot@filebrowser.org
description: File Browser is a create-your-own-cloud-kind of software where you can install it on a server, direct it to a path and then access your files through a nice web interface
license: "MIT"

14
.versionrc Normal file
View File

@@ -0,0 +1,14 @@
{
"types": [
{ "type": "feat", "section": "Features" },
{ "type": "fix", "section": "Bug Fixes" },
{ "type": "perf", "section": "Performance improvements" },
{ "type": "revert", "section": "Reverts" },
{ "type": "refactor", "section": "Refactorings" },
{ "type": "build", "section": "Build" },
{ "type": "ci", "hidden": true },
{ "type": "test", "hidden": true },
{ "type": "chore", "hidden": true },
{ "type": "docs", "hidden": true }
]
}

View File

@@ -2,6 +2,210 @@
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 [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [2.13.0](https://github.com/filebrowser/filebrowser/compare/v2.12.1...v2.13.0) (2021-03-14)
### Features
* dual pane settings view ([db5aad8](https://github.com/filebrowser/filebrowser/commit/db5aad8eb679cfe1b1ace5142cf342951217f0f7))
* improved settings navbar ([5b28aa0](https://github.com/filebrowser/filebrowser/commit/5b28aa0848710b9d3ee02a2aa912856395f48bd2))
* improved sharing prompt ([1819377](https://github.com/filebrowser/filebrowser/commit/18193778971e27d18b5a35df8c2d0e2953b48111))
* increased header button counter size ([4fb832c](https://github.com/filebrowser/filebrowser/commit/4fb832c0422107e16f22b7aa928224f36de4978f))
* larger previewer content ([62fff5c](https://github.com/filebrowser/filebrowser/commit/62fff5ca60da1f887c1f95fa4808d3753596dab2))
### Bug Fixes
* archive contains parent path on Windows ([54f3570](https://github.com/filebrowser/filebrowser/commit/54f35701a2bd5cb7ec0628ca9789047072c073db))
* check rules on http resource handlers ([5bf1554](https://github.com/filebrowser/filebrowser/commit/5bf15548d0ad147acfad5000277531be2671f7ce))
* download current dir on file listing ([488d980](https://github.com/filebrowser/filebrowser/commit/488d98045e7476ed11e53c13d9498a9db3165bbc))
* encoded file path on share ([7955e07](https://github.com/filebrowser/filebrowser/commit/7955e0720baef3710106c7e69bbbf078d5489220))
* full file path on share ([e017a19](https://github.com/filebrowser/filebrowser/commit/e017a199850e19dd51b960ba59402c215fd8f1af))
* header dropdown icon color on previewer ([f8df76f](https://github.com/filebrowser/filebrowser/commit/f8df76f52684f10722ce123fec2c90e321ddf103))
* item dragging on file listing ([326b35a](https://github.com/filebrowser/filebrowser/commit/326b35a7ac7871afcdf892ca150349665b7f6379))
* modified time on info prompt ([11ebaec](https://github.com/filebrowser/filebrowser/commit/11ebaec5f0671ec02ebe55d4a73a514bce3a6713))
* root path name on archive ([426b38b](https://github.com/filebrowser/filebrowser/commit/426b38bb3362d2d477d0d8aa27d880664d537431))
* stuck icon on header button ([6a734c0](https://github.com/filebrowser/filebrowser/commit/6a734c01391b437c2842f5d97fb63f29a0017510))
* update image cache when replacing ([81b6f4d](https://github.com/filebrowser/filebrowser/commit/81b6f4d6f6a01886583016f61f4f1951a59f244d))
* wait for async command exit ([#1326](https://github.com/filebrowser/filebrowser/issues/1326)) ([6d5ceae](https://github.com/filebrowser/filebrowser/commit/6d5ceae8b454edd749b3b65c88aacc0a31ce9215))
### Refactorings
* migrate from rice to embed.FS ([fc55061](https://github.com/filebrowser/filebrowser/commit/fc5506179a64e9e2f57f7b6d6cce4b95f5ebc235))
### [2.12.1](https://github.com/filebrowser/filebrowser/compare/v2.12.0...v2.12.1) (2021-03-07)
### Bug Fixes
* add missing default config into the docker image ([7358b3f](https://github.com/filebrowser/filebrowser/commit/7358b3fe3178c20007b4b5ef5c03705badd538c4))
## [2.12.0](https://github.com/filebrowser/filebrowser/compare/v2.11.0...v2.12.0) (2021-03-04)
### Features
* add homebrew tap ([2d2c598](https://github.com/filebrowser/filebrowser/commit/2d2c598fa6bd1ecaf39c542182890c8dd9b1cad0))
* added tiff files preview support ([#1222](https://github.com/filebrowser/filebrowser/issues/1222)) ([e8c9d1c](https://github.com/filebrowser/filebrowser/commit/e8c9d1c53989b4b52f6fba2a8ac41ae612c03a7c))
* allow disabling file detections by reading header ([#1175](https://github.com/filebrowser/filebrowser/issues/1175)) ([6914063](https://github.com/filebrowser/filebrowser/commit/6914063853a8a3f3cecfa4b21f223820c2a0b7df))
* allow to password protect shares ([#1252](https://github.com/filebrowser/filebrowser/issues/1252)) ([d8f415f](https://github.com/filebrowser/filebrowser/commit/d8f415f8abd0c4301803bd968c54429dd3fe4b59))
* build multi-arch docker images ([cf4836d](https://github.com/filebrowser/filebrowser/commit/cf4836dc757ef79ad615179bb7a6c7bbd3b09c2c))
* share management delete confirm ([#1212](https://github.com/filebrowser/filebrowser/issues/1212)) ([b600b11](https://github.com/filebrowser/filebrowser/commit/b600b11415fd1fb90ff2f5136be95a9c737ae1cb))
### Bug Fixes
* don't allow to remove root user ([019ce80](https://github.com/filebrowser/filebrowser/commit/019ce80fc529a0437984fdc3d1ab6916f34dd594))
* double click to zoom pics in phone's browser ([#1274](https://github.com/filebrowser/filebrowser/issues/1274)) ([f1b7bd5](https://github.com/filebrowser/filebrowser/commit/f1b7bd59f67e719b7bfd203b0d7ec016fd21ab49))
* environmental variables not expanded in command ([#1241](https://github.com/filebrowser/filebrowser/issues/1241)) ([f3afd5c](https://github.com/filebrowser/filebrowser/commit/f3afd5cb79d6ad8b9cc8d54cb8fc2344b7c07d3d))
* fetch resource api once when sorting (closes [#1172](https://github.com/filebrowser/filebrowser/issues/1172)) ([#1202](https://github.com/filebrowser/filebrowser/issues/1202)) ([05bb7c8](https://github.com/filebrowser/filebrowser/commit/05bb7c85531349f3e9d1d8a523bb1243587b2ebc))
### Build
* use make for building the project ([#1304](https://github.com/filebrowser/filebrowser/issues/1304)) ([23f8464](https://github.com/filebrowser/filebrowser/commit/23f84642e6c1e07f89f98d2c1bb6fc9da36cc71c))
## [2.11.0](https://github.com/filebrowser/filebrowser/compare/v2.10.0...v2.11.0) (2020-12-28)
### Features
* add sharing management ([#1178](https://github.com/filebrowser/filebrowser/issues/1178)) (closes [#1000](https://github.com/filebrowser/filebrowser/issues/1000)) ([677bce3](https://github.com/filebrowser/filebrowser/commit/677bce376b024d9ff38f34e74243034fe5a1ec3c))
* download shared subdirectory ([#1184](https://github.com/filebrowser/filebrowser/issues/1184)) ([fb5b28d](https://github.com/filebrowser/filebrowser/commit/fb5b28d9cbdee10d38fcd719b9fd832121be58ef))
### Bug Fixes
* check user input to prevent permission elevation ([#1196](https://github.com/filebrowser/filebrowser/issues/1196)) (closes [#1195](https://github.com/filebrowser/filebrowser/issues/1195)) ([f62806f](https://github.com/filebrowser/filebrowser/commit/f62806f6c9e9c7f392d1b747d65b8fe40b313e89))
* delete extra remove prefix ([#1186](https://github.com/filebrowser/filebrowser/issues/1186)) ([7a5298a](https://github.com/filebrowser/filebrowser/commit/7a5298a7556f7dcc52f59b8ea76d040d3ddc3d12))
* move files between different volumes (closes [#1177](https://github.com/filebrowser/filebrowser/issues/1177)) ([58835b7](https://github.com/filebrowser/filebrowser/commit/58835b7e535cc96e1c8a5d85821c1545743ca757))
* recaptcha race condition ([#1176](https://github.com/filebrowser/filebrowser/issues/1176)) ([ac3673e](https://github.com/filebrowser/filebrowser/commit/ac3673e111afac6616af9650ca07028b6c27e6cd))
## [2.10.0](https://github.com/filebrowser/filebrowser/compare/v2.9.0...v2.10.0) (2020-11-24)
### Features
* add hide dotfiles param ([#1148](https://github.com/filebrowser/filebrowser/issues/1148)) ([10e399b](https://github.com/filebrowser/filebrowser/commit/10e399b3c3dbdcfb4465a9d4138e1da6bae0873d))
* add single click mode ([#1139](https://github.com/filebrowser/filebrowser/issues/1139)) ([e8b4e9a](https://github.com/filebrowser/filebrowser/commit/e8b4e9af46d6e99dbeb965dd9727d9ed017d52a2))
* automatically jump to the next photo when deleting while previewing ([#1143](https://github.com/filebrowser/filebrowser/issues/1143)) ([9515cee](https://github.com/filebrowser/filebrowser/commit/9515ceeb42e5ef5267400220a2082dec775e843d))
* shared folder file listing ([e119bc5](https://github.com/filebrowser/filebrowser/commit/e119bc55ea82cefcbcc0571650107dfd5d73f570))
* shared item information ([36cacdf](https://github.com/filebrowser/filebrowser/commit/36cacdf598e4e09f064c8ace0ca7a6c24b23028e))
### Bug Fixes
* empty folder in archive ([7096b3d](https://github.com/filebrowser/filebrowser/commit/7096b3dab92441981c9964e4a6175af0a255d2be))
* fix hanging when reading a named pipe file (closes [#1155](https://github.com/filebrowser/filebrowser/issues/1155)) ([586d198](https://github.com/filebrowser/filebrowser/commit/586d198d47b525eeccc6fe587573a3ad83adb4f6))
* previewer title overflow ([4e48ffc](https://github.com/filebrowser/filebrowser/commit/4e48ffc14d09dabeea12dc495144277db62b5b7d))
* resource rename action invalid path ([1ce3068](https://github.com/filebrowser/filebrowser/commit/1ce3068a99c80c153fd41359255d173bce6e79e8))
## [2.9.0](https://github.com/filebrowser/filebrowser/compare/v2.8.0...v2.9.0) (2020-10-21)
### Features
* support WKWebview custom protocol ([#1113](https://github.com/filebrowser/filebrowser/issues/1113)) ([0ac80e8](https://github.com/filebrowser/filebrowser/commit/0ac80e8387a69924284259bde448af2813d84ed1))
### Bug Fixes
* allow start from Windows explorer ([f2c4e78](https://github.com/filebrowser/filebrowser/commit/f2c4e78381610879eda5316d38a999c89df6c14a))
* file upload missing path slash ([5e27ba5](https://github.com/filebrowser/filebrowser/commit/5e27ba5c8c1be603c6ae7fec8de48e3532dea1f7))
* preview case sensitive file extension ([05bff54](https://github.com/filebrowser/filebrowser/commit/05bff54b71543fd232f1089c40504d0cbfd106be))
* search missing path slash ([2bd163d](https://github.com/filebrowser/filebrowser/commit/2bd163d92a856d65c8d4615e37898470c1edf2f4))
## [2.8.0](https://github.com/filebrowser/filebrowser/compare/v2.7.0...v2.8.0) (2020-10-05)
### Features
* add disable exec flag ([#1090](https://github.com/filebrowser/filebrowser/issues/1090)) ([97693cc](https://github.com/filebrowser/filebrowser/commit/97693cc6117ce1c956baede91de5dd48b904e175))
### Bug Fixes
* empty commands setting ([c6d4fcd](https://github.com/filebrowser/filebrowser/commit/c6d4fcd08f5f1531c2cef514dc86019e23e7289f))
* file upload path encoding ([babd778](https://github.com/filebrowser/filebrowser/commit/babd7783afe85b790e1c558375d7b5013b2d366f))
* fix empty command name ([#1106](https://github.com/filebrowser/filebrowser/issues/1106)) ([36fb9f5](https://github.com/filebrowser/filebrowser/commit/36fb9f562a2c005ca4390fdebde0b4690201dff9))
* fix panic when accessing nonexistent .js file in static path ([#1105](https://github.com/filebrowser/filebrowser/issues/1105)) ([ad99bf1](https://github.com/filebrowser/filebrowser/commit/ad99bf180197e0e6d82231a86457585de16366a8))
* preview key shortcut conflict ([dd7b9dd](https://github.com/filebrowser/filebrowser/commit/dd7b9ddd8546361060ef99e838a691b2fc6c495a))
* search results absolute url ([26d62e4](https://github.com/filebrowser/filebrowser/commit/26d62e411716a5eb9a5a703e47484cfb3fbf3bd0))
## [2.7.0](https://github.com/filebrowser/filebrowser/compare/v2.6.2...v2.7.0) (2020-09-11)
### Features
* add --socket-perm flag to control unix socket file permissions (closes [#1060](https://github.com/filebrowser/filebrowser/issues/1060)) ([65ac734](https://github.com/filebrowser/filebrowser/commit/65ac73414fadc4686c94803a93ff319e8f7ce9d1))
* preview mobile dropdown ([7787344](https://github.com/filebrowser/filebrowser/commit/778734419de314d4cb64d07109bbab73f8e2e42a))
* preview size button ([3d2cb83](https://github.com/filebrowser/filebrowser/commit/3d2cb838d111ee61047599f49e76de80c821f341))
* put selected files in the root of the archive (closes [#1065](https://github.com/filebrowser/filebrowser/issues/1065)) ([8142b32](https://github.com/filebrowser/filebrowser/commit/8142b32f3865eccd3331328e0d087f805d186ed5))
### [2.6.2](https://github.com/filebrowser/filebrowser/compare/v2.6.1...v2.6.2) (2020-08-05)
### [2.6.1](https://github.com/filebrowser/filebrowser/compare/v2.6.0...v2.6.1) (2020-07-28)
### Bug Fixes
* delete cached previews when deleting file ([f5d02cd](https://github.com/filebrowser/filebrowser/commit/f5d02cdde97923b963878abf5a300393b9feb348))
* escape special characters in preview url (closes [#1002](https://github.com/filebrowser/filebrowser/issues/1002)) ([c9340af](https://github.com/filebrowser/filebrowser/commit/c9340af8d045671ad3338c5d2d887c335ab92de4))
## [2.6.0](https://github.com/filebrowser/filebrowser/compare/v2.5.0...v2.6.0) (2020-07-27)
### Features
* add lazy load of image thumbnails ([bc00165](https://github.com/filebrowser/filebrowser/commit/bc001650944ae963b12b5b2538a68de7cd0d8f82))
* add param to disable img resizing ([aa78e3a](https://github.com/filebrowser/filebrowser/commit/aa78e3ab1fcae6f618e811ba4e315a7a209f9df2))
* cache resized images ([95bc929](https://github.com/filebrowser/filebrowser/commit/95bc92955f391ece22c40d9592f2a3e6e26907b9))
* limit image resize workers ([94ef596](https://github.com/filebrowser/filebrowser/commit/94ef59602fb50fc21b1164feda90a3b9aeb5e972))
### Bug Fixes
* conflict handling on upload button ([f228fa5](https://github.com/filebrowser/filebrowser/commit/f228fa55408824618e9f0879da67c86d22b0d324))
* drop feedback ([f2d2c1c](https://github.com/filebrowser/filebrowser/commit/f2d2c1cbf85fba3edffb7b079f121ed3f0bc1e02))
* missing error message ([d9be370](https://github.com/filebrowser/filebrowser/commit/d9be370e2474b8070fa58db920c9481270cc4a48))
* parent verification on copy ([727c63b](https://github.com/filebrowser/filebrowser/commit/727c63b98e2964d0960d25914c296570f6c79478))
* path separator inconsistency on rename ([34dfb49](https://github.com/filebrowser/filebrowser/commit/34dfb49b719c948e709a4639b4af2c5cb73b3887))
## [2.5.0](https://github.com/filebrowser/filebrowser/compare/v2.4.0...v2.5.0) (2020-07-17)
### Features
* add previewer title and loading indicator ([716396a](https://github.com/filebrowser/filebrowser/commit/716396a726329f0ba42fc34167dd07497c5bf47c))
* duplicate files in the same directory ([43526d9](https://github.com/filebrowser/filebrowser/commit/43526d9d1a8c837245e3f5059e0b4737583eeaeb))
* file copy, move and paste conflict checking ([eed9da1](https://github.com/filebrowser/filebrowser/commit/eed9da1471723ed3fbe6c00b1d6362b1c5fd8b04))
* rename option on replace prompt ([2636f87](https://github.com/filebrowser/filebrowser/commit/2636f876ab8f88eea6d9548de524ca2339eb0843))
* upload queue ([6ec6a23](https://github.com/filebrowser/filebrowser/commit/6ec6a2386173410f5cab9941dbf1bacb6b70ddd2))
### Bug Fixes
* blinking previewer ([9a2ebba](https://github.com/filebrowser/filebrowser/commit/9a2ebbabe2e9f0c292701d33f36f9b7a457b1164))
* dark theme colors ([b3b6445](https://github.com/filebrowser/filebrowser/commit/b3b644527d5673e16e61d404ff58a3c7bd6b6637))
* directory conflict checking ([7e5beef](https://github.com/filebrowser/filebrowser/commit/7e5beeff464e75ab185c430cd96e7cc67209ccc1))
* prompt before closing window ([194030f](https://github.com/filebrowser/filebrowser/commit/194030fcfcf54a2cf5e2f8ececcbb4754474d8f8))
* remove incomplete uploaded files ([0727496](https://github.com/filebrowser/filebrowser/commit/0727496601a9918c8131c56f62419bfac7ac589a))
* reset clipboard after pasting cutted files ([10570ad](https://github.com/filebrowser/filebrowser/commit/10570ade442b573ebe00af08369e28b1b0688df6))
## [2.4.0](https://github.com/filebrowser/filebrowser/compare/v2.3.0...v2.4.0) (2020-07-07)
### Features
* full screen editor ([0d665e5](https://github.com/filebrowser/filebrowser/commit/0d665e528f880ceda0976ceed66070ac34de7969))
### Bug Fixes
* add preview bypass for .gif files ([#1012](https://github.com/filebrowser/filebrowser/issues/1012)) ([453636d](https://github.com/filebrowser/filebrowser/commit/453636dfe2bbf177c74617862eb763485d4774bf))
* prompt key shortcut conflict ([0d69fbd](https://github.com/filebrowser/filebrowser/commit/0d69fbd9a342aa2695859021df0c423e3ae4a4fa))
## [2.3.0](https://github.com/filebrowser/filebrowser/compare/v2.2.0...v2.3.0) (2020-06-26) ## [2.3.0](https://github.com/filebrowser/filebrowser/compare/v2.2.0...v2.3.0) (2020-06-26)

View File

@@ -1,11 +1,7 @@
FROM alpine:latest as alpine FROM alpine:latest
RUN apk --update add ca-certificates RUN apk --update add ca-certificates
RUN apk --update add mailcap RUN apk --update add mailcap
FROM scratch
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=alpine /etc/mime.types /etc/mime.types
VOLUME /srv VOLUME /srv
EXPOSE 80 EXPOSE 80

View File

@@ -1,11 +0,0 @@
FROM alpine:latest as alpine
RUN apk --update add ca-certificates
RUN apk --update add mailcap
VOLUME /srv
EXPOSE 80
COPY .docker.json /.filebrowser.json
COPY filebrowser /filebrowser
ENTRYPOINT [ "/filebrowser" ]

View File

@@ -1,9 +0,0 @@
FROM debian:buster
VOLUME /srv
EXPOSE 80
COPY .docker.json /.filebrowser.json
COPY filebrowser /filebrowser
ENTRYPOINT [ "/filebrowser" ]

94
Makefile Normal file
View File

@@ -0,0 +1,94 @@
SHELL := /bin/bash
BASE_PATH := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
VERSION ?= $(shell git describe --tags --always --match=v* 2> /dev/null || \
cat $(CURDIR)/.version 2> /dev/null || echo v0)
VERSION_HASH = $(shell git rev-parse HEAD)
BIN = $(BASE_PATH)/bin
PATH := $(BIN):$(PATH)
export PATH
# printing
V = 0
Q = $(if $(filter 1,$V),,@)
M = $(shell printf "\033[34;1m▶\033[0m")
GO = GOGC=off go
# go module
MODULE = $(shell env GO111MODULE=on $(GO) list -m)
DATE ?= $(shell date +%FT%T%z)
VERSION ?= $(shell git describe --tags --always --match=v* 2> /dev/null || \
cat $(CURDIR)/.version 2> /dev/null || echo v0)
VERSION_HASH = $(shell git rev-parse HEAD)
BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
LDFLAGS += -X "$(MODULE)/varsion.Version=$(VERSION)" -X "$(MODULE)/varsion.CommitSHA=$(VERSION_HASH)"
# tools
$(BIN):
@mkdir -p $@
$(BIN)/%: | $(BIN) ; $(info $(M) installing $(PACKAGE))
$Q env GOBIN=$(BIN) $(GO) install $(PACKAGE)
GOLANGCI_LINT = $(BIN)/golangci-lint
$(BIN)/golangci-lint: PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lint@v1.37.1
GOIMPORTS = $(BIN)/goimports
$(BIN)/goimports: PACKAGE=golang.org/x/tools/cmd/goimports@v0.1.0
## build: Build
.PHONY: build
build: | build-frontend build-backend ; $(info $(M) building)
## build-frontend: Build frontend
.PHONY: build-frontend
build-frontend: | ; $(info $(M) building frontend)
$Q cd frontend && npm ci && npm run build
## build-backend: Build backend
.PHONY: build-backend
build-backend: | ; $(info $(M) building backend)
$Q $(GO) build -ldflags '$(LDFLAGS)' -o filebrowser
## test: Run all tests
.PHONY: test
test: | test-frontend test-backend ; $(info $(M) running tests)
## test-frontend: Run frontend tests
.PHONY: test-frontend
test-frontend: | ; $(info $(M) running frontend tests)
## test-backend: Run backend tests
.PHONY: test-backend
test-backend: | $(RICE) ; $(info $(M) running backend tests)
$Q $(GO) test -v ./...
## lint: Lint
.PHONY: lint
lint: lint-frontend lint-backend lint-commits | ; $(info $(M) running all linters)
## lint-frontend: Lint frontend
.PHONY: lint-frontend
lint-frontend: | ; $(info $(M) running frontend linters)
$Q cd frontend && npm ci && npm run lint
## lint-backend: Lint backend
.PHONY: lint-backend
lint-backend: | $(GOLANGCI_LINT) ; $(info $(M) running backend linters)
$Q $(GOLANGCI_LINT) run
## lint-commits: Lint commits
.PHONY: lint-commits
lint-commits: | ; $(info $(M) running commitlint)
$Q ./scripts/commitlint.sh
## bump-version: Bump app version
.PHONY: bump-version
bump-version: | ; $(info $(M) creating a new release)
$Q ./scripts/bump_version.sh
## help: Show this help
.PHONY: help
help:
@sed -n 's/^## //p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's/^/ /' | sort

View File

@@ -4,7 +4,7 @@
![Preview](https://user-images.githubusercontent.com/5447088/50716739-ebd26700-107a-11e9-9817-14230c53efd2.gif) ![Preview](https://user-images.githubusercontent.com/5447088/50716739-ebd26700-107a-11e9-9817-14230c53efd2.gif)
[![Travis](https://img.shields.io/travis/com/filebrowser/filebrowser.svg?style=flat-square)](https://travis-ci.com/filebrowser/filebrowser) [![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?style=flat-square)](https://goreportcard.com/report/github.com/filebrowser/filebrowser) [![Go Report Card](https://goreportcard.com/badge/github.com/filebrowser/filebrowser?style=flat-square)](https://goreportcard.com/report/github.com/filebrowser/filebrowser)
[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/filebrowser/filebrowser) [![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/filebrowser/filebrowser)
[![Version](https://img.shields.io/github/release/filebrowser/filebrowser.svg?style=flat-square)](https://github.com/filebrowser/filebrowser/releases/latest) [![Version](https://img.shields.io/github/release/filebrowser/filebrowser.svg?style=flat-square)](https://github.com/filebrowser/filebrowser/releases/latest)
@@ -24,7 +24,7 @@ For installation instructions please refer to our docs at [https://filebrowser.o
[Authentication Method](https://filebrowser.org/configuration/authentication-method) - You can change the way the user authenticates with the filebrowser server [Authentication Method](https://filebrowser.org/configuration/authentication-method) - You can change the way the user authenticates with the filebrowser server
[Commander Runner](https://filebrowser.org/configuration/command-runner) - The command runner is a feature that enables you to execute any shell command you want before or after a certain event. [Command Runner](https://filebrowser.org/configuration/command-runner) - The command runner is a feature that enables you to execute any shell command you want before or after a certain event.
[Custom Branding](https://filebrowser.org/configuration/custom-branding) - You can customize your File Browser installation by change its name to any other you want, by adding a global custom style sheet and by using your own logotype if you want. [Custom Branding](https://filebrowser.org/configuration/custom-branding) - You can customize your File Browser installation by change its name to any other you want, by adding a global custom style sheet and by using your own logotype if you want.

View File

@@ -9,7 +9,7 @@ import (
// Auther is the authentication interface. // Auther is the authentication interface.
type Auther interface { type Auther interface {
// Auth is called to authenticate a request. // Auth is called to authenticate a request.
Auth(r *http.Request, s *users.Storage, root string) (*users.User, error) Auth(r *http.Request, s users.Store, root string) (*users.User, error)
// LoginPage indicates if this auther needs a login page. // LoginPage indicates if this auther needs a login page.
LoginPage() bool LoginPage() bool
} }

View File

@@ -26,7 +26,7 @@ type JSONAuth struct {
} }
// Auth authenticates the user via a json in content body. // Auth authenticates the user via a json in content body.
func (a JSONAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) { func (a JSONAuth) Auth(r *http.Request, sto users.Store, root string) (*users.User, error) {
var cred jsonCred var cred jsonCred
if r.Body == nil { if r.Body == nil {
@@ -40,7 +40,7 @@ func (a JSONAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users
// If ReCaptcha is enabled, check the code. // If ReCaptcha is enabled, check the code.
if a.ReCaptcha != nil && len(a.ReCaptcha.Secret) > 0 { if a.ReCaptcha != nil && len(a.ReCaptcha.Secret) > 0 {
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:shadow ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:govet
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -14,7 +14,7 @@ const MethodNoAuth settings.AuthMethod = "noauth"
type NoAuth struct{} type NoAuth struct{}
// Auth uses authenticates user 1. // Auth uses authenticates user 1.
func (a NoAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) { func (a NoAuth) Auth(r *http.Request, sto users.Store, root string) (*users.User, error) {
return sto.Get(root, uint(1)) return sto.Get(root, uint(1))
} }

View File

@@ -18,7 +18,7 @@ type ProxyAuth struct {
} }
// Auth authenticates the user via an HTTP header. // Auth authenticates the user via an HTTP header.
func (a ProxyAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) { func (a ProxyAuth) Auth(r *http.Request, sto users.Store, root string) (*users.User, error) {
username := r.Header.Get(a.Header) username := r.Header.Get(a.Header)
user, err := sto.Get(root, username) user, err := sto.Get(root, username)
if err == errors.ErrNotExist { if err == errors.ErrNotExist {

View File

@@ -14,7 +14,7 @@ var cmdsAddCmd = &cobra.Command{
Use: "add <event> <command>", Use: "add <event> <command>",
Short: "Add a command to run on a specific event", Short: "Add a command to run on a specific event",
Long: `Add a command to run on a specific event.`, Long: `Add a command to run on a specific event.`,
Args: cobra.MinimumNArgs(2), //nolint:mnd Args: cobra.MinimumNArgs(2), //nolint:gomnd
Run: python(func(cmd *cobra.Command, args []string, d pythonData) { Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
s, err := d.store.Settings.Get() s, err := d.store.Settings.Get()
checkErr(err) checkErr(err)

View File

@@ -23,7 +23,7 @@ You can also specify an optional parameter (index_end) so
you can remove all commands from 'index' to 'index_end', you can remove all commands from 'index' to 'index_end',
including 'index_end'.`, including 'index_end'.`,
Args: func(cmd *cobra.Command, args []string) error { Args: func(cmd *cobra.Command, args []string) error {
if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil { //nolint:mnd if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil { //nolint:gomnd
return err return err
} }
@@ -43,7 +43,7 @@ including 'index_end'.`,
i, err := strconv.Atoi(args[1]) i, err := strconv.Atoi(args[1])
checkErr(err) checkErr(err)
f := i f := i
if len(args) == 3 { //nolint:mnd if len(args) == 3 { //nolint:gomnd
f, err = strconv.Atoi(args[2]) f, err = strconv.Atoi(args[2])
checkErr(err) checkErr(err)
} }

View File

@@ -140,10 +140,12 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address) fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert) fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey) fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec)
fmt.Fprintln(w, "\nDefaults:") fmt.Fprintln(w, "\nDefaults:")
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope) fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale) fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode) fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode)
fmt.Fprintf(w, "\tSingle Click:\t%t\n", set.Defaults.SingleClick)
fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " ")) fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " "))
fmt.Fprintf(w, "\tSorting:\n") fmt.Fprintf(w, "\tSorting:\n")
fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By) fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)

View File

@@ -2,7 +2,6 @@ package cmd
import ( import (
"fmt" "fmt"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -32,7 +31,7 @@ override the options.`,
s := &settings.Settings{ s := &settings.Settings{
Key: generateKey(), Key: generateKey(),
Signup: mustGetBool(flags, "signup"), Signup: mustGetBool(flags, "signup"),
Shell: strings.Split(strings.TrimSpace(mustGetString(flags, "shell")), " "), Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
AuthMethod: authMethod, AuthMethod: authMethod,
Defaults: defaults, Defaults: defaults,
Branding: settings.Branding{ Branding: settings.Branding{
@@ -62,7 +61,7 @@ override the options.`,
fmt.Printf(` fmt.Printf(`
Congratulations! You've set up your database to use with File Browser. Congratulations! You've set up your database to use with File Browser.
Now add your first user via 'filebrowser users new' and then you just Now add your first user via 'filebrowser users add' and then you just
need to call the main command to boot up the server. need to call the main command to boot up the server.
`) `)
printSettings(ser, s, auther) printSettings(ser, s, auther)

View File

@@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
@@ -50,7 +48,7 @@ you want to change. Other options will remain unchanged.`,
case "auth.method": case "auth.method":
hasAuth = true hasAuth = true
case "shell": case "shell":
set.Shell = strings.Split(strings.TrimSpace(mustGetString(flags, flag.Name)), " ") set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
case "branding.name": case "branding.name":
set.Branding.Name = mustGetString(flags, flag.Name) set.Branding.Name = mustGetString(flags, flag.Name)
case "branding.disableExternal": case "branding.disableExternal":

View File

@@ -3,6 +3,7 @@ package cmd
import ( import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"io/fs"
"io/ioutil" "io/ioutil"
"log" "log"
"net" "net"
@@ -14,13 +15,17 @@ import (
"syscall" "syscall"
homedir "github.com/mitchellh/go-homedir" homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/afero"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
v "github.com/spf13/viper" v "github.com/spf13/viper"
lumberjack "gopkg.in/natefinch/lumberjack.v2" lumberjack "gopkg.in/natefinch/lumberjack.v2"
"github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/diskcache"
"github.com/filebrowser/filebrowser/v2/frontend"
fbhttp "github.com/filebrowser/filebrowser/v2/http" fbhttp "github.com/filebrowser/filebrowser/v2/http"
"github.com/filebrowser/filebrowser/v2/img"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
@@ -32,6 +37,7 @@ var (
func init() { func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
cobra.MousetrapHelpText = ""
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n") rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
@@ -55,7 +61,14 @@ func addServerFlags(flags *pflag.FlagSet) {
flags.StringP("key", "k", "", "tls key") flags.StringP("key", "k", "", "tls key")
flags.StringP("root", "r", ".", "root to prepend to relative paths") flags.StringP("root", "r", ".", "root to prepend to relative paths")
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)") flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
flags.Uint32("socket-perm", 0666, "unix socket file permissions")
flags.StringP("baseurl", "b", "", "base url") flags.StringP("baseurl", "b", "", "base url")
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
flags.Int("img-processors", 4, "image processors count")
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
flags.Bool("disable-exec", false, "disables Command Runner feature")
flags.Bool("disable-type-detection-by-header", false, "disables type detection by reading file headers")
} }
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
@@ -103,6 +116,24 @@ user created with the credentials from options "username" and "password".`,
quickSetup(cmd.Flags(), d) quickSetup(cmd.Flags(), d)
} }
// build img service
workersCount, err := cmd.Flags().GetInt("img-processors")
checkErr(err)
if workersCount < 1 {
log.Fatal("Image resize workers count could not be < 1")
}
imgSvc := img.New(workersCount)
var fileCache diskcache.Interface = diskcache.NewNoOp()
cacheDir, err := cmd.Flags().GetString("cache-dir")
checkErr(err)
if cacheDir != "" {
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet
log.Fatalf("can't make directory %s: %s", cacheDir, err)
}
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
}
server := getRunParams(cmd.Flags(), d.store) server := getRunParams(cmd.Flags(), d.store)
setupLog(server.Log) setupLog(server.Log)
@@ -118,13 +149,20 @@ user created with the credentials from options "username" and "password".`,
case server.Socket != "": case server.Socket != "":
listener, err = net.Listen("unix", server.Socket) listener, err = net.Listen("unix", server.Socket)
checkErr(err) checkErr(err)
case server.TLSKey != "" && server.TLSCert != "": socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:shadow
checkErr(err) checkErr(err)
listener, err = tls.Listen("tcp", adr, &tls.Config{Certificates: []tls.Certificate{cer}}) //nolint:shadow err = os.Chmod(server.Socket, os.FileMode(socketPerm))
checkErr(err)
case server.TLSKey != "" && server.TLSCert != "":
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet
checkErr(err)
listener, err = tls.Listen("tcp", adr, &tls.Config{
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{cer}},
)
checkErr(err) checkErr(err)
default: default:
listener, err = net.Listen("tcp", adr) //nolint:shadow listener, err = net.Listen("tcp", adr)
checkErr(err) checkErr(err)
} }
@@ -132,7 +170,12 @@ user created with the credentials from options "username" and "password".`,
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
go cleanupHandler(listener, sigc) go cleanupHandler(listener, sigc)
handler, err := fbhttp.NewHandler(d.store, server) assetsFs, err := fs.Sub(frontend.Assets(), "dist")
if err != nil {
panic(err)
}
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
checkErr(err) checkErr(err)
defer listener.Close() defer listener.Close()
@@ -205,6 +248,18 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
server.Socket = "" server.Socket = ""
} }
_, disableThumbnails := getParamB(flags, "disable-thumbnails")
server.EnableThumbnails = !disableThumbnails
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
server.ResizePreview = !disablePreviewResize
_, disableTypeDetectionByHeader := getParamB(flags, "disable-type-detection-by-header")
server.TypeDetectionByHeader = !disableTypeDetectionByHeader
_, disableExec := getParamB(flags, "disable-exec")
server.EnableExec = !disableExec
return server return server
} }
@@ -261,8 +316,9 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
Signup: false, Signup: false,
CreateUserDir: false, CreateUserDir: false,
Defaults: settings.UserDefaults{ Defaults: settings.UserDefaults{
Scope: ".", Scope: ".",
Locale: "en", Locale: "en",
SingleClick: false,
Perm: users.Permissions{ Perm: users.Permissions{
Admin: false, Admin: false,
Execute: true, Execute: true,

View File

@@ -44,7 +44,7 @@ including 'index_end'.`,
i, err := strconv.Atoi(args[0]) i, err := strconv.Atoi(args[0])
checkErr(err) checkErr(err)
f := i f := i
if len(args) == 2 { //nolint:mnd if len(args) == 2 { //nolint:gomnd
f, err = strconv.Atoi(args[1]) f, err = strconv.Atoi(args[1])
checkErr(err) checkErr(err)
} }

View File

@@ -27,15 +27,16 @@ var usersCmd = &cobra.Command{
func printUsers(usrs []*users.User) { func printUsers(usrs []*users.User) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock") fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
for _, u := range usrs { for _, u := range usrs {
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n", fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n",
u.ID, u.ID,
u.Username, u.Username,
u.Scope, u.Scope,
u.Locale, u.Locale,
u.ViewMode, u.ViewMode,
u.SingleClick,
u.Perm.Admin, u.Perm.Admin,
u.Perm.Execute, u.Perm.Execute,
u.Perm.Create, u.Perm.Create,
@@ -75,6 +76,7 @@ func addUserFlags(flags *pflag.FlagSet) {
flags.String("scope", ".", "scope for users") flags.String("scope", ".", "scope for users")
flags.String("locale", "en", "locale for users") flags.String("locale", "en", "locale for users")
flags.String("viewMode", string(users.ListViewMode), "view mode for users") flags.String("viewMode", string(users.ListViewMode), "view mode for users")
flags.Bool("singleClick", false, "use single clicks only")
} }
func getViewMode(flags *pflag.FlagSet) users.ViewMode { func getViewMode(flags *pflag.FlagSet) users.ViewMode {
@@ -95,6 +97,8 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
defaults.Locale = mustGetString(flags, flag.Name) defaults.Locale = mustGetString(flags, flag.Name)
case "viewMode": case "viewMode":
defaults.ViewMode = getViewMode(flags) defaults.ViewMode = getViewMode(flags)
case "singleClick":
defaults.SingleClick = mustGetBool(flags, flag.Name)
case "perm.admin": case "perm.admin":
defaults.Perm.Admin = mustGetBool(flags, flag.Name) defaults.Perm.Admin = mustGetBool(flags, flag.Name)
case "perm.execute": case "perm.execute":

View File

@@ -15,7 +15,7 @@ var usersAddCmd = &cobra.Command{
Use: "add <username> <password>", Use: "add <username> <password>",
Short: "Create a new user", Short: "Create a new user",
Long: `Create a new user and add it to the database.`, Long: `Create a new user and add it to the database.`,
Args: cobra.ExactArgs(2), //nolint:mnd Args: cobra.ExactArgs(2), //nolint:gomnd
Run: python(func(cmd *cobra.Command, args []string, d pythonData) { Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
s, err := d.store.Settings.Get() s, err := d.store.Settings.Get()
checkErr(err) checkErr(err)

View File

@@ -67,7 +67,7 @@ list or set it to 0.`,
// with the new username. If there is, print an error and cancel the // with the new username. If there is, print an error and cancel the
// operation // operation
if user.Username != onDB.Username { if user.Username != onDB.Username {
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:shadow if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID)) checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
} }
} }

View File

@@ -41,17 +41,19 @@ options you want to change.`,
checkErr(err) checkErr(err)
defaults := settings.UserDefaults{ defaults := settings.UserDefaults{
Scope: user.Scope, Scope: user.Scope,
Locale: user.Locale, Locale: user.Locale,
ViewMode: user.ViewMode, ViewMode: user.ViewMode,
Perm: user.Perm, SingleClick: user.SingleClick,
Sorting: user.Sorting, Perm: user.Perm,
Commands: user.Commands, Sorting: user.Sorting,
Commands: user.Commands,
} }
getUserDefaults(flags, &defaults, false) getUserDefaults(flags, &defaults, false)
user.Scope = defaults.Scope user.Scope = defaults.Scope
user.Locale = defaults.Locale user.Locale = defaults.Locale
user.ViewMode = defaults.ViewMode user.ViewMode = defaults.ViewMode
user.SingleClick = defaults.SingleClick
user.Perm = defaults.Perm user.Perm = defaults.Perm
user.Commands = defaults.Commands user.Commands = defaults.Commands
user.Sorting = defaults.Sorting user.Sorting = defaults.Sorting

View File

@@ -7,6 +7,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -71,7 +72,7 @@ func dbExists(path string) (bool, error) {
d := filepath.Dir(path) d := filepath.Dir(path)
_, err = os.Stat(d) _, err = os.Stat(d)
if os.IsNotExist(err) { if os.IsNotExist(err) {
if err := os.MkdirAll(d, 0700); err != nil { //nolint:shadow if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet
return false, err return false, err
} }
return false, nil return false, nil
@@ -178,3 +179,15 @@ func cleanUpMapValue(v interface{}) interface{} {
return v return v
} }
} }
// convertCmdStrToCmdArray checks if cmd string is blank (whitespace included)
// then returns empty string array, else returns the splitted word array of cmd.
// This is to ensure the result will never be []string{""}
func convertCmdStrToCmdArray(cmd string) []string {
var cmdArray []string
trimmedCmdStr := strings.TrimSpace(cmd)
if trimmedCmdStr != "" {
cmdArray = strings.Split(trimmedCmdStr, " ")
}
return cmdArray
}

34
commitlint.config.js Normal file
View File

@@ -0,0 +1,34 @@
module.exports = {
rules: {
'body-leading-blank': [1, 'always'],
'body-max-line-length': [2, 'always', 100],
'footer-leading-blank': [1, 'always'],
'footer-max-line-length': [2, 'always', 100],
'header-max-length': [2, 'always', 100],
'scope-case': [2, 'always', 'lower-case'],
'subject-case': [
2,
'never',
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
],
'subject-full-stop': [2, 'never', '.'],
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'revert',
'refactor',
'build',
'ci',
'test',
'chore',
'docs',
],
],
},
};

11
diskcache/cache.go Normal file
View File

@@ -0,0 +1,11 @@
package diskcache
import (
"context"
)
type Interface interface {
Store(ctx context.Context, key string, value []byte) error
Load(ctx context.Context, key string) (value []byte, exist bool, err error)
Delete(ctx context.Context, key string) error
}

110
diskcache/file_cache.go Normal file
View File

@@ -0,0 +1,110 @@
package diskcache
import (
"context"
"crypto/sha1" //nolint:gosec
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/spf13/afero"
)
type FileCache struct {
fs afero.Fs
// granular locks
scopedLocks struct {
sync.Mutex
sync.Once
locks map[string]sync.Locker
}
}
func New(fs afero.Fs, root string) *FileCache {
return &FileCache{
fs: afero.NewBasePathFs(fs, root),
}
}
func (f *FileCache) Store(ctx context.Context, key string, value []byte) error {
mu := f.getScopedLocks(key)
mu.Lock()
defer mu.Unlock()
fileName := f.getFileName(key)
if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil {
return err
}
if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil {
return err
}
return nil
}
func (f *FileCache) Load(ctx context.Context, key string) (value []byte, exist bool, err error) {
r, ok, err := f.open(key)
if err != nil || !ok {
return nil, ok, err
}
defer r.Close()
value, err = ioutil.ReadAll(r)
if err != nil {
return nil, false, err
}
return value, true, nil
}
func (f *FileCache) Delete(ctx context.Context, key string) error {
mu := f.getScopedLocks(key)
mu.Lock()
defer mu.Unlock()
fileName := f.getFileName(key)
if err := f.fs.Remove(fileName); err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
return nil
}
func (f *FileCache) open(key string) (afero.File, bool, error) {
fileName := f.getFileName(key)
file, err := f.fs.Open(fileName)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, false, nil
}
return nil, false, err
}
return file, true, nil
}
// getScopedLocks pull lock from the map if found or create a new one
func (f *FileCache) getScopedLocks(key string) (lock sync.Locker) {
f.scopedLocks.Do(func() { f.scopedLocks.locks = map[string]sync.Locker{} })
f.scopedLocks.Lock()
lock, ok := f.scopedLocks.locks[key]
if !ok {
lock = &sync.Mutex{}
f.scopedLocks.locks[key] = lock
}
f.scopedLocks.Unlock()
return lock
}
func (f *FileCache) getFileName(key string) string {
hasher := sha1.New() //nolint:gosec
_, _ = hasher.Write([]byte(key))
hash := hex.EncodeToString(hasher.Sum(nil))
return fmt.Sprintf("%s/%s/%s", hash[:1], hash[1:3], hash)
}

View File

@@ -0,0 +1,55 @@
package diskcache
import (
"context"
"path/filepath"
"testing"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
)
func TestFileCache(t *testing.T) {
ctx := context.Background()
const (
key = "key"
value = "some text"
newValue = "new text"
cacheRoot = "/cache"
cachedFilePath = "a/62/a62f2225bf70bfaccbc7f1ef2a397836717377de"
)
fs := afero.NewMemMapFs()
cache := New(fs, "/cache")
// store new key
err := cache.Store(ctx, key, []byte(value))
require.NoError(t, err)
checkValue(t, ctx, fs, filepath.Join(cacheRoot, cachedFilePath), cache, key, value)
// update existing key
err = cache.Store(ctx, key, []byte(newValue))
require.NoError(t, err)
checkValue(t, ctx, fs, filepath.Join(cacheRoot, cachedFilePath), cache, key, newValue)
// delete key
err = cache.Delete(ctx, key)
require.NoError(t, err)
exists, err := afero.Exists(fs, filepath.Join(cacheRoot, cachedFilePath))
require.NoError(t, err)
require.False(t, exists)
}
func checkValue(t *testing.T, ctx context.Context, fs afero.Fs, fileFullPath string, cache *FileCache, key, wantValue string) { //nolint:golint
t.Helper()
// check actual file content
b, err := afero.ReadFile(fs, fileFullPath)
require.NoError(t, err)
require.Equal(t, wantValue, string(b))
// check cache content
b, ok, err := cache.Load(ctx, key)
require.NoError(t, err)
require.True(t, ok)
require.Equal(t, wantValue, string(b))
}

24
diskcache/noop_cache.go Normal file
View File

@@ -0,0 +1,24 @@
package diskcache
import (
"context"
)
type NoOp struct {
}
func NewNoOp() *NoOp {
return &NoOp{}
}
func (n *NoOp) Store(ctx context.Context, key string, value []byte) error {
return nil
}
func (n *NoOp) Load(ctx context.Context, key string) (value []byte, exist bool, err error) {
return nil, false, nil
}
func (n *NoOp) Delete(ctx context.Context, key string) error {
return nil
}

View File

@@ -16,4 +16,6 @@ var (
ErrInvalidAuthMethod = errors.New("invalid auth method") ErrInvalidAuthMethod = errors.New("invalid auth method")
ErrPermissionDenied = errors.New("permission denied") ErrPermissionDenied = errors.New("permission denied")
ErrInvalidRequestParams = errors.New("invalid request params") ErrInvalidRequestParams = errors.New("invalid request params")
ErrSourceIsParent = errors.New("source is parent")
ErrRootUserDeletion = errors.New("user with id 1 can't be deleted")
) )

View File

@@ -38,15 +38,18 @@ type FileInfo struct {
Subtitles []string `json:"subtitles,omitempty"` Subtitles []string `json:"subtitles,omitempty"`
Content string `json:"content,omitempty"` Content string `json:"content,omitempty"`
Checksums map[string]string `json:"checksums,omitempty"` Checksums map[string]string `json:"checksums,omitempty"`
Token string `json:"token,omitempty"`
} }
// FileOptions are the options when getting a file info. // FileOptions are the options when getting a file info.
type FileOptions struct { type FileOptions struct {
Fs afero.Fs Fs afero.Fs
Path string Path string
Modify bool Modify bool
Expand bool Expand bool
Checker rules.Checker ReadHeader bool
Token string
Checker rules.Checker
} }
// NewFileInfo creates a File object from a path and a given user. This File // NewFileInfo creates a File object from a path and a given user. This File
@@ -71,17 +74,18 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
IsDir: info.IsDir(), IsDir: info.IsDir(),
Size: info.Size(), Size: info.Size(),
Extension: filepath.Ext(info.Name()), Extension: filepath.Ext(info.Name()),
Token: opts.Token,
} }
if opts.Expand { if opts.Expand {
if file.IsDir { if file.IsDir {
if err := file.readListing(opts.Checker); err != nil { //nolint:shadow if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
return nil, err return nil, err
} }
return file, nil return file, nil
} }
err = file.detectType(opts.Modify, true) err = file.detectType(opts.Modify, true, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -134,11 +138,60 @@ func (i *FileInfo) Checksum(algo string) error {
//nolint:goconst //nolint:goconst
//TODO: use constants //TODO: use constants
func (i *FileInfo) detectType(modify, saveContent bool) error { func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
if IsNamedPipe(i.Mode) {
i.Type = "blob"
return nil
}
// failing to detect the type should not return error. // failing to detect the type should not return error.
// imagine the situation where a file in a dir with thousands // imagine the situation where a file in a dir with thousands
// of files couldn't be opened: we'd have immediately // of files couldn't be opened: we'd have immediately
// a 500 even though it doesn't matter. So we just log it. // a 500 even though it doesn't matter. So we just log it.
var buffer []byte
mimetype := mime.TypeByExtension(i.Extension)
if mimetype == "" && readHeader {
buffer = i.readFirstBytes()
mimetype = http.DetectContentType(buffer)
}
switch {
case strings.HasPrefix(mimetype, "video"):
i.Type = "video"
i.detectSubtitles()
return nil
case strings.HasPrefix(mimetype, "audio"):
i.Type = "audio"
return nil
case strings.HasPrefix(mimetype, "image"):
i.Type = "image"
return nil
case (strings.HasPrefix(mimetype, "text") || (len(buffer) > 0 && !isBinary(buffer))) && i.Size <= 10*1024*1024: // 10 MB
i.Type = "text"
if !modify {
i.Type = "textImmutable"
}
if saveContent {
afs := &afero.Afero{Fs: i.Fs}
content, err := afs.ReadFile(i.Path)
if err != nil {
return err
}
i.Content = string(content)
}
return nil
default:
i.Type = "blob"
}
return nil
}
func (i *FileInfo) readFirstBytes() []byte {
reader, err := i.Fs.Open(i.Path) reader, err := i.Fs.Open(i.Path)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
@@ -155,44 +208,7 @@ func (i *FileInfo) detectType(modify, saveContent bool) error {
return nil return nil
} }
mimetype := mime.TypeByExtension(i.Extension) return buffer[:n]
if mimetype == "" {
mimetype = http.DetectContentType(buffer[:n])
}
switch {
case strings.HasPrefix(mimetype, "video"):
i.Type = "video"
i.detectSubtitles()
return nil
case strings.HasPrefix(mimetype, "audio"):
i.Type = "audio"
return nil
case strings.HasPrefix(mimetype, "image"):
i.Type = "image"
return nil
case isBinary(buffer[:n], n) || i.Size > 10*1024*1024: // 10 MB
i.Type = "blob"
return nil
default:
i.Type = "text"
if !modify {
i.Type = "textImmutable"
}
if saveContent {
afs := &afero.Afero{Fs: i.Fs}
content, err := afs.ReadFile(i.Path)
if err != nil {
return err
}
i.Content = string(content)
}
}
return nil
} }
func (i *FileInfo) detectSubtitles() { func (i *FileInfo) detectSubtitles() {
@@ -211,7 +227,7 @@ func (i *FileInfo) detectSubtitles() {
} }
} }
func (i *FileInfo) readListing(checker rules.Checker) error { func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
afs := &afero.Afero{Fs: i.Fs} afs := &afero.Afero{Fs: i.Fs}
dir, err := afs.ReadDir(i.Path) dir, err := afs.ReadDir(i.Path)
if err != nil { if err != nil {
@@ -232,9 +248,9 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
continue continue
} }
if strings.HasPrefix(f.Mode().String(), "L") { if IsSymlink(f.Mode()) {
// It's a symbolic link. We try to follow it. If it doesn't work, // It's a symbolic link. We try to follow it. If it doesn't work,
// we stay with the link information instead if the target's. // we stay with the link information instead of the target's.
info, err := i.Fs.Stat(fPath) info, err := i.Fs.Stat(fPath)
if err == nil { if err == nil {
f = info f = info
@@ -257,7 +273,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
} else { } else {
listing.NumFiles++ listing.NumFiles++
err := file.detectType(true, false) err := file.detectType(true, false, readHeader)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -1,10 +1,11 @@
package files package files
import ( import (
"os"
"unicode/utf8" "unicode/utf8"
) )
func isBinary(content []byte, _ int) bool { func isBinary(content []byte) bool {
maybeStr := string(content) maybeStr := string(content)
runeCnt := utf8.RuneCount(content) runeCnt := utf8.RuneCount(content)
runeIndex := 0 runeIndex := 0
@@ -48,3 +49,11 @@ func isBinary(content []byte, _ int) bool {
} }
return false return false
} }
func IsNamedPipe(mode os.FileMode) bool {
return mode&os.ModeNamedPipe != 0
}
func IsSymlink(mode os.FileMode) bool {
return mode&os.ModeSymlink != 0
}

View File

@@ -2,11 +2,32 @@ package fileutils
import ( import (
"io" "io"
"os"
"path"
"path/filepath" "path/filepath"
"github.com/spf13/afero" "github.com/spf13/afero"
) )
// MoveFile moves file from src to dst.
// By default the rename filesystem system call is used. If src and dst point to different volumes
// the file copy is used as a fallback
func MoveFile(fs afero.Fs, src, dst string) error {
if fs.Rename(src, dst) == nil {
return nil
}
// fallback
err := CopyFile(fs, src, dst)
if err != nil {
_ = fs.Remove(dst)
return err
}
if err := fs.Remove(src); err != nil {
return err
}
return nil
}
// CopyFile copies a file from source to dest and returns // CopyFile copies a file from source to dest and returns
// an error if any. // an error if any.
func CopyFile(fs afero.Fs, source, dest string) error { func CopyFile(fs afero.Fs, source, dest string) error {
@@ -25,7 +46,7 @@ func CopyFile(fs afero.Fs, source, dest string) error {
} }
// Create the destination file. // Create the destination file.
dst, err := fs.Create(dest) dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil { if err != nil {
return err return err
} }
@@ -37,15 +58,71 @@ func CopyFile(fs afero.Fs, source, dest string) error {
return err return err
} }
// Copy the mode if the user can't // Copy the mode
// open the file.
info, err := fs.Stat(source) info, err := fs.Stat(source)
if err != nil { if err != nil {
err = fs.Chmod(dest, info.Mode()) return err
if err != nil { }
return err err = fs.Chmod(dest, info.Mode())
} if err != nil {
return err
} }
return nil return nil
} }
// CommonPrefix returns common directory path of provided files
func CommonPrefix(sep byte, paths ...string) string {
// Handle special cases.
switch len(paths) {
case 0:
return ""
case 1:
return path.Clean(paths[0])
}
// Note, we treat string as []byte, not []rune as is often
// done in Go. (And sep as byte, not rune). This is because
// most/all supported OS' treat paths as string of non-zero
// bytes. A filename may be displayed as a sequence of Unicode
// runes (typically encoded as UTF-8) but paths are
// not required to be valid UTF-8 or in any normalized form
// (e.g. "é" (U+00C9) and "é" (U+0065,U+0301) are different
// file names.
c := []byte(path.Clean(paths[0]))
// We add a trailing sep to handle the case where the
// common prefix directory is included in the path list
// (e.g. /home/user1, /home/user1/foo, /home/user1/bar).
// path.Clean will have cleaned off trailing / separators with
// the exception of the root directory, "/" (in which case we
// make it "//", but this will get fixed up to "/" bellow).
c = append(c, sep)
// Ignore the first path since it's already in c
for _, v := range paths[1:] {
// Clean up each path before testing it
v = path.Clean(v) + string(sep)
// Find the first non-common byte and truncate c
if len(v) < len(c) {
c = c[:len(v)]
}
for i := 0; i < len(c); i++ {
if v[i] != c[i] {
c = c[:i]
break
}
}
}
// Remove trailing non-separator characters and the final separator
for i := len(c) - 1; i >= 0; i-- {
if c[i] == sep {
c = c[:i]
break
}
}
return string(c)
}

46
fileutils/file_test.go Normal file
View File

@@ -0,0 +1,46 @@
package fileutils
import "testing"
func TestCommonPrefix(t *testing.T) {
testCases := map[string]struct {
paths []string
want string
}{
"same lvl": {
paths: []string{
"/home/user/file1",
"/home/user/file2",
},
want: "/home/user",
},
"sub folder": {
paths: []string{
"/home/user/folder",
"/home/user/folder/file",
},
want: "/home/user/folder",
},
"relative path": {
paths: []string{
"/home/user/folder",
"/home/user/folder/../folder2",
},
want: "/home/user",
},
"no common path": {
paths: []string{
"/home/user/folder",
"/etc/file",
},
want: "",
},
}
for name, tt := range testCases {
t.Run(name, func(t *testing.T) {
if got := CommonPrefix('/', tt.paths...); got != tt.want {
t.Errorf("CommonPrefix() = %v, want %v", got, tt.want)
}
})
}
}

10
frontend/assets.go Normal file
View File

@@ -0,0 +1,10 @@
package frontend
import "embed"
//go:embed dist/*
var assets embed.FS
func Assets() embed.FS {
return assets
}

4
frontend/dist/.gitignore vendored Normal file
View File

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

View File

@@ -9929,8 +9929,7 @@
"pako": { "pako": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz",
"integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==", "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA=="
"dev": true
}, },
"parallel-transform": { "parallel-transform": {
"version": "1.1.0", "version": "1.1.0",
@@ -12895,6 +12894,14 @@
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
"dev": true "dev": true
}, },
"utif": {
"version": "3.1.0",
"resolved": "http://mirrors.cloud.tencent.com/npm/utif/-/utif-3.1.0.tgz",
"integrity": "sha512-WEo4D/xOvFW53K5f5QTaTbbiORcm2/pCL9P6qmJnup+17eYfKaEhDeX9PeQkuyEoIxlbGklDuGl8xwuXYMrrXQ==",
"requires": {
"pako": "^1.0.5"
}
},
"util": { "util": {
"version": "0.11.1", "version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
@@ -13030,6 +13037,11 @@
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.15.3.tgz", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.15.3.tgz",
"integrity": "sha512-PVNgo6yhOmacZVFjSapZ314oewwLyXHjJwAqjnaPN1GJAJd/dvsrShGzSiJuCX4Hc36G4epJvNXUwO8y7wEKew==" "integrity": "sha512-PVNgo6yhOmacZVFjSapZ314oewwLyXHjJwAqjnaPN1GJAJd/dvsrShGzSiJuCX4Hc36G4epJvNXUwO8y7wEKew=="
}, },
"vue-lazyload": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/vue-lazyload/-/vue-lazyload-1.3.3.tgz",
"integrity": "sha512-uHnq0FTEeNmqnbBC2aRKlmtd9LofMZ6Q3mWvgfLa+i9vhxU8fDK+nGs9c1iVT85axSua/AUnMttIq3xPaU9G3A=="
},
"vue-loader": { "vue-loader": {
"version": "15.8.3", "version": "15.8.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.8.3.tgz", "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.8.3.tgz",

View File

@@ -4,8 +4,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean",
"watch": "vue-cli-service build --watch", "watch": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --watch --no-clean",
"lint": "vue-cli-service lint --fix" "lint": "vue-cli-service lint --fix"
}, },
"dependencies": { "dependencies": {
@@ -19,8 +19,10 @@
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"noty": "^3.2.0-beta", "noty": "^3.2.0-beta",
"qrcode.vue": "^1.7.0", "qrcode.vue": "^1.7.0",
"utif": "^3.1.0",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-i18n": "^8.15.3", "vue-i18n": "^8.15.3",
"vue-lazyload": "^1.3.3",
"vue-router": "^3.1.3", "vue-router": "^3.1.3",
"vuex": "^3.1.2", "vuex": "^3.1.2",
"vuex-router-sync": "^5.0.0" "vuex-router-sync": "^5.0.0"

View File

@@ -13,18 +13,19 @@
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
<!-- Add to home screen for Android and modern mobile browsers --> <!-- Add to home screen for Android and modern mobile browsers -->
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials"> <link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
<meta name="theme-color" content="#2979ff"> <meta name="theme-color" content="#2979ff">
<!-- Add to home screen for Safari on iOS --> <!-- Add to home screen for Safari on iOS/iPadOS -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="assets"> <meta name="apple-mobile-web-app-title" content="assets">
<link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon-152x152.png"> <link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png">
<!-- Add to home screen for Windows --> <!-- Add to home screen for Windows -->
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/msapplication-icon-144x144.png"> <meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png">
<meta name="msapplication-TileColor" content="#2979ff"> <meta name="msapplication-TileColor" content="#2979ff">
<!-- Inject Some Variables and generate the manifest json --> <!-- Inject Some Variables and generate the manifest json -->

View File

@@ -1,7 +1,7 @@
:root { :root {
--background: #121212; --background: #141D24;
--surfacePrimary: #171819; --surfacePrimary: #20292F;
--surfaceSecondary: #212528; --surfaceSecondary: #3A4147;
--divider: rgba(255, 255, 255, 0.12); --divider: rgba(255, 255, 255, 0.12);
--icon: #ffffff; --icon: #ffffff;
--textPrimary: rgba(255, 255, 255, 0.87); --textPrimary: rgba(255, 255, 255, 0.87);
@@ -16,7 +16,7 @@ body {
#loading { #loading {
background: var(--background); background: var(--background);
} }
#loading .spinner div { #loading .spinner div, #previewer .loading .spinner div {
background: var(--icon); background: var(--icon);
} }
@@ -30,25 +30,34 @@ header {
#search #input { #search #input {
background: var(--surfaceSecondary); background: var(--surfaceSecondary);
border-color: var(--surfacePrimary);
} }
#search.active #input, #search #input input::placeholder {
#search.active .boxes { color: var(--textSecondary);
}
#search.active #input {
background: var(--surfacePrimary); background: var(--surfacePrimary);
} }
#search.active input { #search.active input {
color: var(--textPrimary); color: var(--textPrimary);
} }
#search.active #result { #search #result {
background: var(--background); background: var(--background);
color: var(--textPrimary); color: var(--textPrimary);
} }
#search.active .boxes h3 { #search .boxes {
background: var(--surfaceSecondary);
}
#search .boxes h3 {
color: var(--textPrimary); color: var(--textPrimary);
} }
.action { .action {
color: var(--textPrimary) !important; color: var(--textPrimary) !important;
} }
.action:hover {
background-color: rgba(255, 255, 255, .1);
}
.action i { .action i {
color: var(--icon) !important; color: var(--icon) !important;
} }
@@ -60,13 +69,16 @@ nav > div {
border-color: var(--divider); border-color: var(--divider);
} }
#breadcrumbs { .breadcrumbs {
border-color: var(--divider); border-color: var(--divider);
color: var(--textPrimary) !important; color: var(--textPrimary) !important;
} }
#breadcrumbs span { .breadcrumbs span {
color: var(--textPrimary) !important; color: var(--textPrimary) !important;
} }
.breadcrumbs a:hover {
background-color: rgba(255, 255, 255, .1);
}
#listing .item { #listing .item {
background: var(--surfacePrimary); background: var(--surfacePrimary);
@@ -93,6 +105,10 @@ nav > div {
background: var(--background); background: var(--background);
} }
.message {
color: var(--textPrimary);
}
.card { .card {
background: var(--surfacePrimary); background: var(--surfacePrimary);
color: var(--textPrimary); color: var(--textPrimary);
@@ -101,17 +117,38 @@ nav > div {
background: var(--surfaceSecondary); background: var(--surfaceSecondary);
} }
.dashboard #nav ul li {
color: var(--textSecondary);
}
.dashboard #nav ul li:hover {
background: var(--surfaceSecondary);
}
.card h3, .card h3,
.dashboard #nav, .dashboard #nav,
.dashboard p label { .dashboard p label {
color: var(--textPrimary); color: var(--textPrimary);
} }
.card#share input,
.card#share select,
.input { .input {
background: var(--surfaceSecondary); background: var(--surfaceSecondary);
color: var(--textPrimary); color: var(--textPrimary);
border: 1px solid rgba(255, 255, 255, 0.05);
}
.input:hover,
.input:focus {
border-color: rgba(255, 255, 255, 0.15);
}
.input--red {
background: #73302D;
} }
.dashboard #nav li, .input--green {
background: #147A41;
}
.dashboard #nav .wrapper,
.collapsible { .collapsible {
border-color: var(--divider); border-color: var(--divider);
} }
@@ -119,10 +156,35 @@ nav > div {
color: var(--textPrimary); color: var(--textPrimary);
} }
table th {
color: var(--textSecondary);
}
.file-list li:hover {
background: var(--surfaceSecondary);
}
.file-list li:before {
color: var(--textSecondary);
}
.file-list li[aria-selected=true]:before {
color: var(--icon);
}
.shell { .shell {
background: var(--surfacePrimary); background: var(--surfacePrimary);
color: var(--textPrimary); color: var(--textPrimary);
} }
.shell__result {
border-top: 1px solid var(--divider);
}
#editor-container {
background: var(--background);
}
#editor-container .bar {
background: var(--surfacePrimary);
}
@media (max-width: 736px) { @media (max-width: 736px) {
#file-selection { #file-selection {
@@ -138,3 +200,12 @@ nav > div {
background: var(--surfaceSecondary) !important; background: var(--surfaceSecondary) !important;
} }
} }
.share__box {
background: var(--surfacePrimary) !important;
color: var(--textPrimary);
}
.share__box__element {
border-top-color: var(--divider);
}

View File

@@ -43,7 +43,7 @@ async function resourceAction (url, method, content) {
const res = await fetchURL(`/api/resources${url}`, opts) const res = await fetchURL(`/api/resources${url}`, opts)
if (res.status !== 200) { if (res.status !== 200) {
throw new Error(res.responseText) throw new Error(await res.text())
} else { } else {
return res return res
} }
@@ -74,17 +74,25 @@ export function download (format, ...files) {
url += `/?files=${arg}&` url += `/?files=${arg}&`
} }
if (format !== null) { if (format) {
url += `algo=${format}&` url += `algo=${format}&`
} }
url += `auth=${store.state.jwt}` if (store.state.jwt){
url += `auth=${store.state.jwt}&`
}
window.open(url) window.open(url)
} }
export async function post (url, content = '', overwrite = false, onupload) { export async function post (url, content = '', overwrite = false, onupload) {
url = removePrefix(url) url = removePrefix(url)
let bufferContent
if (content instanceof Blob && !['http:', 'https:'].includes(window.location.protocol)) {
bufferContent = await new Response(content).arrayBuffer()
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let request = new XMLHttpRequest() let request = new XMLHttpRequest()
request.open('POST', `${baseURL}/api/resources${url}?override=${overwrite}`, true) request.open('POST', `${baseURL}/api/resources${url}?override=${overwrite}`, true)
@@ -94,9 +102,6 @@ export async function post (url, content = '', overwrite = false, onupload) {
request.upload.onprogress = onupload request.upload.onprogress = onupload
} }
// Send a message to user before closing the tab during file upload
window.onbeforeunload = () => "Files are being uploaded."
request.onload = () => { request.onload = () => {
if (request.status === 200) { if (request.status === 200) {
resolve(request.responseText) resolve(request.responseText)
@@ -111,30 +116,29 @@ export async function post (url, content = '', overwrite = false, onupload) {
reject(error) reject(error)
} }
request.send(content) request.send(bufferContent || content)
// Upload is done no more message before closing the tab })
}).finally(() => { window.onbeforeunload = null })
} }
function moveCopy (items, copy = false) { function moveCopy (items, copy = false, overwrite = false, rename = false) {
let promises = [] let promises = []
for (let item of items) { for (let item of items) {
const from = removePrefix(item.from) const from = item.from
const to = encodeURIComponent(removePrefix(item.to)) const to = encodeURIComponent(removePrefix(item.to))
const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}` const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}&override=${overwrite}&rename=${rename}`
promises.push(resourceAction(url, 'PATCH')) promises.push(resourceAction(url, 'PATCH'))
} }
return Promise.all(promises) return Promise.all(promises)
} }
export function move (items) { export function move (items, overwrite = false, rename = false) {
return moveCopy(items) return moveCopy(items, false, overwrite, rename)
} }
export function copy (items) { export function copy (items, overwrite = false, rename = false) {
return moveCopy(items, true) return moveCopy(items, true, overwrite, rename)
} }
export async function checksum (url, algo) { export async function checksum (url, algo) {

View File

@@ -2,6 +2,7 @@ import * as files from './files'
import * as share from './share' import * as share from './share'
import * as users from './users' import * as users from './users'
import * as settings from './settings' import * as settings from './settings'
import * as pub from './pub'
import search from './search' import search from './search'
import commands from './commands' import commands from './commands'
@@ -10,6 +11,7 @@ export {
share, share,
users, users,
settings, settings,
pub,
commands, commands,
search search
} }

61
frontend/src/api/pub.js Normal file
View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { fetchURL, fetchJSON, removePrefix } from './utils' import { fetchURL, fetchJSON, removePrefix } from './utils'
export async function getHash(hash) { export async function list() {
return fetchJSON(`/api/public/share/${hash}`) return fetchJSON('/api/shares')
} }
export async function get(url) { export async function get(url) {
@@ -19,14 +19,18 @@ export async function remove(hash) {
} }
} }
export async function create(url, expires = '', unit = 'hours') { export async function create(url, password = '', expires = '', unit = 'hours') {
url = removePrefix(url) url = removePrefix(url)
url = `/api/share${url}` url = `/api/share${url}`
if (expires !== '') { if (expires !== '') {
url += `?expires=${expires}&unit=${unit}` url += `?expires=${expires}&unit=${unit}`
} }
let body = '{}';
if (password != '' || expires !== '' || unit !== 'hours') {
body = JSON.stringify({password: password, expires: expires, unit: unit})
}
return fetchJSON(url, { return fetchJSON(url, {
method: 'POST' method: 'POST',
body: body,
}) })
} }

View File

@@ -34,9 +34,7 @@ export async function fetchJSON (url, opts) {
} }
export function removePrefix (url) { export function removePrefix (url) {
if (url.startsWith('/files')) { url = url.split('/').splice(2).join('/')
url = url.slice(6)
}
if (url === '') url = '/' if (url === '') url = '/'
if (url[0] !== '/') url = '/' + url if (url[0] !== '/') url = '/' + url

View File

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

View File

@@ -1,189 +0,0 @@
<template>
<header>
<div>
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action">
<i class="material-icons">menu</i>
</button>
<img :src="logoURL" alt="File Browser">
<search v-if="isLogged"></search>
</div>
<div>
<template v-if="isLogged">
<button @click="openSearch" :aria-label="$t('buttons.search')" :title="$t('buttons.search')" class="search-button action">
<i class="material-icons">search</i>
</button>
<button v-show="showSaveButton" :aria-label="$t('buttons.save')" :title="$t('buttons.save')" class="action" id="save-button">
<i class="material-icons">save</i>
</button>
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
<i class="material-icons">more_vert</i>
</button>
<!-- Menu that shows on listing AND mobile when there are files selected -->
<div id="file-selection" v-if="isMobile && isListing">
<span v-if="selectedCount > 0">{{ selectedCount }} selected</span>
<share-button v-show="showShareButton"></share-button>
<rename-button v-show="showRenameButton"></rename-button>
<copy-button v-show="showCopyButton"></copy-button>
<move-button v-show="showMoveButton"></move-button>
<delete-button v-show="showDeleteButton"></delete-button>
</div>
<!-- This buttons are shown on a dropdown on mobile phones -->
<div id="dropdown" :class="{ active: showMore }">
<div v-if="!isListing || !isMobile">
<share-button v-show="showShareButton"></share-button>
<rename-button v-show="showRenameButton"></rename-button>
<copy-button v-show="showCopyButton"></copy-button>
<move-button v-show="showMoveButton"></move-button>
<delete-button v-show="showDeleteButton"></delete-button>
</div>
<shell-button v-show="user.perm.execute" />
<switch-button v-show="isListing"></switch-button>
<download-button v-show="showDownloadButton"></download-button>
<upload-button v-show="showUpload"></upload-button>
<info-button v-show="isFiles"></info-button>
<button v-show="isListing" @click="toggleMultipleSelection" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action" >
<i class="material-icons">check_circle</i>
<span>{{ $t('buttons.select') }}</span>
</button>
</div>
</template>
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
</div>
</header>
</template>
<script>
import Search from './Search'
import InfoButton from './buttons/Info'
import DeleteButton from './buttons/Delete'
import RenameButton from './buttons/Rename'
import UploadButton from './buttons/Upload'
import DownloadButton from './buttons/Download'
import SwitchButton from './buttons/SwitchView'
import MoveButton from './buttons/Move'
import CopyButton from './buttons/Copy'
import ShareButton from './buttons/Share'
import ShellButton from './buttons/Shell'
import {mapGetters, mapState} from 'vuex'
import { logoURL } from '@/utils/constants'
import * as api from '@/api'
import buttons from '@/utils/buttons'
export default {
name: 'header-layout',
components: {
Search,
InfoButton,
DeleteButton,
ShareButton,
RenameButton,
DownloadButton,
CopyButton,
UploadButton,
SwitchButton,
MoveButton,
ShellButton
},
data: function () {
return {
width: window.innerWidth,
pluginData: {
api,
buttons,
'store': this.$store,
'router': this.$router
}
}
},
created () {
window.addEventListener('resize', () => {
this.width = window.innerWidth
})
},
computed: {
...mapGetters([
'selectedCount',
'isFiles',
'isEditor',
'isListing',
'isLogged'
]),
...mapState([
'req',
'user',
'loading',
'reload',
'multiple'
]),
logoURL: () => logoURL,
isMobile () {
return this.width <= 736
},
showUpload () {
return this.isListing && this.user.perm.create
},
showSaveButton () {
return this.isEditor && this.user.perm.modify
},
showDownloadButton () {
return this.isFiles && this.user.perm.download
},
showDeleteButton () {
return this.isFiles && (this.isListing
? (this.selectedCount !== 0 && this.user.perm.delete)
: this.user.perm.delete)
},
showRenameButton () {
return this.isFiles && (this.isListing
? (this.selectedCount === 1 && this.user.perm.rename)
: this.user.perm.rename)
},
showShareButton () {
return this.isFiles && (this.isListing
? (this.selectedCount === 1 && this.user.perm.share)
: this.user.perm.share)
},
showMoveButton () {
return this.isFiles && (this.isListing
? (this.selectedCount > 0 && this.user.perm.rename)
: this.user.perm.rename)
},
showCopyButton () {
return this.isFiles && (this.isListing
? (this.selectedCount > 0 && this.user.perm.create)
: this.user.perm.create)
},
showMore () {
return this.isFiles && this.$store.state.show === 'more'
},
showOverlay () {
return this.showMore
}
},
methods: {
openSidebar () {
this.$store.commit('showHover', 'sidebar')
},
openMore () {
this.$store.commit('showHover', 'more')
},
openSearch () {
this.$store.commit('showHover', 'search')
},
toggleMultipleSelection () {
this.$store.commit('multiple', !this.multiple)
this.resetPrompts()
},
resetPrompts () {
this.$store.commit('closeHovers')
}
}
}
</script>

View File

@@ -49,7 +49,7 @@
</template> </template>
<ul v-show="results.length > 0"> <ul v-show="results.length > 0">
<li v-for="(s,k) in filteredResults" :key="k"> <li v-for="(s,k) in filteredResults" :key="k">
<router-link @click.native="close" :to="'./' + s.path"> <router-link @click.native="close" :to="s.url">
<i v-if="s.dir" class="material-icons">folder</i> <i v-if="s.dir" class="material-icons">folder</i>
<i v-else class="material-icons">insert_drive_file</i> <i v-else class="material-icons">insert_drive_file</i>
<span>./{{ s.path }}</span> <span>./{{ s.path }}</span>
@@ -136,12 +136,6 @@ export default {
} }
}, },
mounted() { mounted() {
window.addEventListener("keydown", event => {
if (event.keyCode === 27) {
this.closeHovers()
}
})
this.$refs.result.addEventListener('scroll', event => { this.$refs.result.addEventListener('scroll', event => {
if (event.target.offsetHeight + event.target.scrollTop >= event.target.scrollHeight - 100) { if (event.target.offsetHeight + event.target.scrollTop >= event.target.scrollHeight - 100) {
this.resultsCount += 50 this.resultsCount += 50
@@ -189,8 +183,12 @@ export default {
this.ongoing = true this.ongoing = true
try {
this.results = await search(path, this.value)
} catch (error) {
this.$showError(error)
}
this.results = await search(path, this.value)
this.ongoing = false this.ongoing = false
} }
} }

View File

@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" id="copy-button">
<i class="material-icons">content_copy</i>
<span>{{ $t('buttons.copyFile') }}</span>
</button>
</template>
<script>
export default {
name: 'copy-button',
methods: {
show: function () {
this.$store.commit('showHover', 'copy')
}
}
}
</script>

View File

@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
<i class="material-icons">delete</i>
<span>{{ $t('buttons.delete') }}</span>
</button>
</template>
<script>
export default {
name: 'delete-button',
methods: {
show: function () {
this.$store.commit('showHover', 'delete')
}
}
}
</script>

View File

@@ -1,35 +0,0 @@
<template>
<button @click="download" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" id="download-button" class="action">
<i class="material-icons">file_download</i>
<span>{{ $t('buttons.download') }}</span>
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
</button>
</template>
<script>
import {mapGetters, mapState} from 'vuex'
import { files as api } from '@/api'
export default {
name: 'download-button',
computed: {
...mapState(['req', 'selected']),
...mapGetters(['isListing', 'selectedCount'])
},
methods: {
download: function () {
if (!this.isListing) {
api.download(null, this.$route.path)
return
}
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
api.download(null, this.req.items[this.selected[0]].url)
return
}
this.$store.commit('showHover', 'download')
}
}
}
</script>

View File

@@ -1,17 +0,0 @@
<template>
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="show">
<i class="material-icons">info</i>
<span>{{ $t('buttons.info') }}</span>
</button>
</template>
<script>
export default {
name: 'info-button',
methods: {
show: function () {
this.$store.commit('showHover', 'info')
}
}
}
</script>

View File

@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
<i class="material-icons">forward</i>
<span>{{ $t('buttons.moveFile') }}</span>
</button>
</template>
<script>
export default {
name: 'move-button',
methods: {
show: function () {
this.$store.commit('showHover', 'move')
}
}
}
</script>

View File

@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" id="rename-button">
<i class="material-icons">mode_edit</i>
<span>{{ $t('buttons.rename') }}</span>
</button>
</template>
<script>
export default {
name: 'rename-button',
methods: {
show: function () {
this.$store.commit('showHover', 'rename')
}
}
}
</script>

View File

@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.share')" :title="$t('buttons.share')" class="action">
<i class="material-icons">share</i>
<span>{{ $t('buttons.share') }}</span>
</button>
</template>
<script>
export default {
name: 'share-button',
methods: {
show () {
this.$store.commit('showHover', 'share')
}
}
}
</script>

View File

@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.shell')" :title="$t('buttons.shell')" class="action">
<i class="material-icons">code</i>
<span>{{ $t('buttons.shell') }}</span>
</button>
</template>
<script>
export default {
name: 'shell-button',
methods: {
show: function () {
this.$store.commit('toggleShell')
}
}
}
</script>

View File

@@ -1,40 +0,0 @@
<template>
<button @click="change" :aria-label="$t('buttons.switchView')" :title="$t('buttons.switchView')" class="action" id="switch-view-button">
<i class="material-icons">{{ icon }}</i>
<span>{{ $t('buttons.switchView') }}</span>
</button>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import { users as api } from '@/api'
export default {
name: 'switch-button',
computed: {
...mapState(['user']),
icon: function () {
if (this.user.viewMode === 'mosaic') return 'view_list'
return 'view_module'
}
},
methods: {
...mapMutations([ 'updateUser', 'closeHovers' ]),
change: async function () {
this.closeHovers()
const data = {
id: this.user.id,
viewMode: (this.icon === 'view_list') ? 'list' : 'mosaic'
}
try {
await api.update(data, ['viewMode'])
this.updateUser(data)
} catch (e) {
this.$showError(e)
}
}
}
}
</script>

View File

@@ -1,21 +0,0 @@
<template>
<button @click="upload" :aria-label="$t('buttons.upload')" :title="$t('buttons.upload')" class="action" id="upload-button">
<i class="material-icons">file_upload</i>
<span>{{ $t('buttons.upload') }}</span>
</button>
</template>
<script>
export default {
name: 'upload-button',
methods: {
upload: function () {
if (typeof(DataTransferItem.prototype.webkitGetAsEntry) !== 'undefined') {
this.$store.commit('showHover', 'upload')
} else {
document.getElementById('upload-input').click();
}
}
}
}
</script>

View File

@@ -10,10 +10,13 @@
@mouseup="mouseUp" @mouseup="mouseUp"
@wheel="wheelMove" @wheel="wheelMove"
> >
<img :src="src" class="image-ex-img" ref="imgex" @load="setCenter"> <img src="" class="image-ex-img image-ex-img-center" ref="imgex" @load="onLoad">
</div> </div>
</template> </template>
<script> <script>
import throttle from 'lodash.throttle'
import UTIF from 'utif'
export default { export default {
props: { props: {
src: String, src: String,
@@ -50,10 +53,18 @@ export default {
inDrag: false, inDrag: false,
lastTouchDistance: 0, lastTouchDistance: 0,
moveDisabled: false, moveDisabled: false,
disabledTimer: null disabledTimer: null,
imageLoaded: false,
position: {
center: { x: 0, y: 0 },
relative: { x: 0, y: 0 }
}
} }
}, },
mounted() { mounted() {
if (!this.decodeUTIF()) {
this.$refs.imgex.src = this.src
}
let container = this.$refs.container let container = this.$refs.container
this.classList.forEach(className => container.classList.add(className)) this.classList.forEach(className => container.classList.add(className))
// set width and height if they are zero // set width and height if they are zero
@@ -63,24 +74,68 @@ export default {
if (getComputedStyle(container).height === "0px") { if (getComputedStyle(container).height === "0px") {
container.style.height = "100%" container.style.height = "100%"
} }
window.addEventListener('resize', this.onResize)
},
beforeDestroy () {
window.removeEventListener('resize', this.onResize)
document.removeEventListener('mouseup', this.onMouseUp)
},
watch: {
src: function () {
this.scale = 1
this.setZoom()
this.setCenter()
}
}, },
methods: { methods: {
// Modified from UTIF.replaceIMG
decodeUTIF() {
const sufs = ["tif", "tiff", "dng", "cr2", "nef"]
let suff = document.location.pathname.split(".").pop().toLowerCase()
if (sufs.indexOf(suff) == -1) return false
let xhr = new XMLHttpRequest()
UTIF._xhrs.push(xhr)
UTIF._imgs.push(this.$refs.imgex)
xhr.open("GET", this.src)
xhr.responseType = "arraybuffer"
xhr.onload = UTIF._imgLoaded
xhr.send()
return true
},
onLoad() {
let img = this.$refs.imgex
this.imageLoaded = true
if (img === undefined) {
return
}
img.classList.remove('image-ex-img-center')
this.setCenter()
img.classList.add('image-ex-img-ready')
document.addEventListener('mouseup', this.onMouseUp)
},
onMouseUp() {
this.inDrag = false
},
onResize: throttle(function() {
if (this.imageLoaded) {
this.setCenter()
this.doMove(this.position.relative.x, this.position.relative.y)
}
}, 100),
setCenter() { setCenter() {
let container = this.$refs.container let container = this.$refs.container
let img = this.$refs.imgex let img = this.$refs.imgex
let rate = Math.min( this.position.center.x = Math.floor((container.clientWidth - img.clientWidth) / 2)
container.clientWidth / img.clientWidth, this.position.center.y = Math.floor((container.clientHeight - img.clientHeight) / 2)
container.clientHeight / img.clientHeight
) img.style.left = this.position.center.x + 'px'
if (!this.autofill && rate > 1) { img.style.top = this.position.center.y + 'px'
rate = 1
}
// height will be auto set
img.width = Math.floor(img.clientWidth * rate)
img.style.top = `${Math.floor((container.clientHeight - img.clientHeight) / 2)}px`
img.style.left = `${Math.floor((container.clientWidth - img.clientWidth) / 2)}px`
document.addEventListener('mouseup', () => this.inDrag = false )
}, },
mousedownStart(event) { mousedownStart(event) {
this.lastX = null this.lastX = null
@@ -101,6 +156,15 @@ export default {
this.lastX = null this.lastX = null
this.lastY = null this.lastY = null
this.lastTouchDistance = null this.lastTouchDistance = null
if (event.targetTouches.length < 2) {
setTimeout(() => {
this.touches = 0
}, 300)
this.touches++
if (this.touches > 1) {
this.zoomAuto(event)
}
}
event.preventDefault() event.preventDefault()
}, },
zoomAuto(event) { zoomAuto(event) {
@@ -114,6 +178,7 @@ export default {
default: default:
case 4: case 4:
this.scale = 1 this.scale = 1
this.setCenter()
break break
} }
this.setZoom() this.setZoom()
@@ -159,8 +224,22 @@ export default {
}, },
doMove(x, y) { doMove(x, y) {
let style = this.$refs.imgex.style let style = this.$refs.imgex.style
style.left = `${this.pxStringToNumber(style.left) + x}px` let posX = this.pxStringToNumber(style.left) + x
style.top = `${this.pxStringToNumber(style.top) + y}px` let posY = this.pxStringToNumber(style.top) + y
style.left = posX + 'px'
style.top = posY + 'px'
this.position.relative.x = Math.abs(this.position.center.x - posX)
this.position.relative.y = Math.abs(this.position.center.y - posY)
if (posX < this.position.center.x) {
this.position.relative.x = this.position.relative.x * -1
}
if (posY < this.position.center.y) {
this.position.relative.y = this.position.relative.y * -1
}
}, },
wheelMove(event) { wheelMove(event) {
this.scale += (event.wheelDeltaY / 100) * this.zoomStep this.scale += (event.wheelDeltaY / 100) * this.zoomStep
@@ -185,9 +264,20 @@ export default {
} }
.image-ex-img { .image-ex-img {
position: absolute;
}
.image-ex-img-center {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
position: absolute;
transition: none;
}
.image-ex-img-ready {
left: 0; left: 0;
top: 0; top: 0;
position: absolute;
transition: transform 0.1s ease; transition: transform 0.1s ease;
} }
</style> </style>

View File

@@ -1,578 +0,0 @@
<template>
<div v-if="(req.numDirs + req.numFiles) == 0">
<h2 class="message">
<i class="material-icons">sentiment_dissatisfied</i>
<span>{{ $t('files.lonely') }}</span>
</h2>
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
<input style="display:none" type="file" id="upload-folder-input" @change="uploadInput($event)" webkitdirectory multiple>
</div>
<div v-else id="listing"
:class="user.viewMode"
@dragenter="dragEnter"
@dragend="dragEnd">
<div>
<div class="item header">
<div></div>
<div>
<p :class="{ active: nameSorted }" class="name"
role="button"
tabindex="0"
@click="sort('name')"
:title="$t('files.sortByName')"
:aria-label="$t('files.sortByName')">
<span>{{ $t('files.name') }}</span>
<i class="material-icons">{{ nameIcon }}</i>
</p>
<p :class="{ active: sizeSorted }" class="size"
role="button"
tabindex="0"
@click="sort('size')"
:title="$t('files.sortBySize')"
:aria-label="$t('files.sortBySize')">
<span>{{ $t('files.size') }}</span>
<i class="material-icons">{{ sizeIcon }}</i>
</p>
<p :class="{ active: modifiedSorted }" class="modified"
role="button"
tabindex="0"
@click="sort('modified')"
:title="$t('files.sortByLastModified')"
:aria-label="$t('files.sortByLastModified')">
<span>{{ $t('files.lastModified') }}</span>
<i class="material-icons">{{ modifiedIcon }}</i>
</p>
</div>
</div>
</div>
<h2 v-if="req.numDirs > 0">{{ $t('files.folders') }}</h2>
<div v-if="req.numDirs > 0">
<item v-for="(item) in dirs"
:key="base64(item.name)"
v-bind:index="item.index"
v-bind:name="item.name"
v-bind:isDir="item.isDir"
v-bind:url="item.url"
v-bind:modified="item.modified"
v-bind:type="item.type"
v-bind:size="item.size">
</item>
</div>
<h2 v-if="req.numFiles > 0">{{ $t('files.files') }}</h2>
<div v-if="req.numFiles > 0">
<item v-for="(item) in files"
:key="base64(item.name)"
v-bind:index="item.index"
v-bind:name="item.name"
v-bind:isDir="item.isDir"
v-bind:url="item.url"
v-bind:modified="item.modified"
v-bind:type="item.type"
v-bind:size="item.size">
</item>
</div>
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
<input style="display:none" type="file" id="upload-folder-input" @change="uploadInput($event)" webkitdirectory multiple>
<div :class="{ active: $store.state.multiple }" id="multiple-selection">
<p>{{ $t('files.multipleSelectionEnabled') }}</p>
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" :title="$t('files.clear')" :aria-label="$t('files.clear')" class="action">
<i class="material-icons">clear</i>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import throttle from 'lodash.throttle'
import Item from './ListingItem'
import css from '@/utils/css'
import { users, files as api } from '@/api'
import buttons from '@/utils/buttons'
import url from '@/utils/url'
export default {
name: 'listing',
components: { Item },
data: function () {
return {
show: 50,
uploading: {
id: 0,
count: 0,
size: 0,
progress: []
}
}
},
computed: {
...mapState(['req', 'selected', 'user']),
nameSorted () {
return (this.req.sorting.by === 'name')
},
sizeSorted () {
return (this.req.sorting.by === 'size')
},
modifiedSorted () {
return (this.req.sorting.by === 'modified')
},
ascOrdered () {
return this.req.sorting.asc
},
items () {
const dirs = []
const files = []
this.req.items.forEach((item) => {
if (item.isDir) {
dirs.push(item)
} else {
files.push(item)
}
})
return { dirs, files }
},
dirs () {
return this.items.dirs.slice(0, this.show)
},
files () {
let show = this.show - this.items.dirs.length
if (show < 0) show = 0
return this.items.files.slice(0, show)
},
nameIcon () {
if (this.nameSorted && !this.ascOrdered) {
return 'arrow_upward'
}
return 'arrow_downward'
},
sizeIcon () {
if (this.sizeSorted && this.ascOrdered) {
return 'arrow_downward'
}
return 'arrow_upward'
},
modifiedIcon () {
if (this.modifiedSorted && this.ascOrdered) {
return 'arrow_downward'
}
return 'arrow_upward'
}
},
mounted: function () {
// Check the columns size for the first time.
this.resizeEvent()
// Add the needed event listeners to the window and document.
window.addEventListener('keydown', this.keyEvent)
window.addEventListener('resize', this.resizeEvent)
window.addEventListener('scroll', this.scrollEvent)
document.addEventListener('dragover', this.preventDefault)
document.addEventListener('drop', this.drop)
},
beforeDestroy () {
// Remove event listeners before destroying this page.
window.removeEventListener('keydown', this.keyEvent)
window.removeEventListener('resize', this.resizeEvent)
window.removeEventListener('scroll', this.scrollEvent)
document.removeEventListener('dragover', this.preventDefault)
document.removeEventListener('drop', this.drop)
},
methods: {
...mapMutations([ 'updateUser', 'addSelected' ]),
base64: function (name) {
return window.btoa(unescape(encodeURIComponent(name)))
},
keyEvent (event) {
if (!event.ctrlKey && !event.metaKey) {
return
}
let key = String.fromCharCode(event.which).toLowerCase()
switch (key) {
case 'f':
event.preventDefault()
this.$store.commit('showHover', 'search')
break
case 'c':
case 'x':
this.copyCut(event, key)
break
case 'v':
this.paste(event)
break
case 'a':
event.preventDefault()
for (let file of this.items.files) {
if (this.$store.state.selected.indexOf(file.index) === -1) {
this.addSelected(file.index)
}
}
for (let dir of this.items.dirs) {
if (this.$store.state.selected.indexOf(dir.index) === -1) {
this.addSelected(dir.index)
}
}
break
}
},
preventDefault (event) {
// Wrapper around prevent default.
event.preventDefault()
},
copyCut (event, key) {
if (event.target.tagName.toLowerCase() === 'input') {
return
}
let items = []
for (let i of this.selected) {
items.push({
from: this.req.items[i].url,
name: encodeURIComponent(this.req.items[i].name)
})
}
if (items.length == 0) {
return
}
this.$store.commit('updateClipboard', {
key: key,
items: items
})
},
paste (event) {
if (event.target.tagName.toLowerCase() === 'input') {
return
}
let items = []
for (let item of this.$store.state.clipboard.items) {
const from = item.from.endsWith('/') ? item.from.slice(0, -1) : item.from
const to = this.$route.path + item.name
items.push({ from, to })
}
if (items.length === 0) {
return
}
if (this.$store.state.clipboard.key === 'x') {
api.move(items).then(() => {
this.$store.commit('setReload', true)
}).catch(this.$showError)
return
}
api.copy(items).then(() => {
this.$store.commit('setReload', true)
}).catch(this.$showError)
},
resizeEvent () {
// Update the columns size based on the window width.
let columns = Math.floor(document.querySelector('main').offsetWidth / 300)
let items = css(['#listing.mosaic .item', '.mosaic#listing .item'])
if (columns === 0) columns = 1
items.style.width = `calc(${100 / columns}% - 1em)`
},
scrollEvent () {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
this.show += 50
}
},
dragEnter () {
// When the user starts dragging an item, put every
// file on the listing with 50% opacity.
let items = document.getElementsByClassName('item')
Array.from(items).forEach(file => {
file.style.opacity = 0.5
})
},
dragEnd () {
this.resetOpacity()
},
drop: function (event) {
event.preventDefault()
this.resetOpacity()
let dt = event.dataTransfer
let el = event.target
if (dt.files.length <= 0) return
for (let i = 0; i < 5; i++) {
if (el !== null && !el.classList.contains('item')) {
el = el.parentElement
}
}
let base = ''
if (el !== null && el.classList.contains('item') && el.dataset.dir === 'true') {
base = el.querySelector('.name').innerHTML + '/'
}
if (base === '') {
this.scanFiles(dt).then((result) => {
this.checkConflict(result, this.req.items, base)
})
} else {
this.scanFiles(dt).then((result) => {
api.fetch(this.$route.path + base)
.then(req => {
this.checkConflict(result, req.items, base)
})
.catch(this.$showError)
})
}
},
checkConflict (files, items, base) {
if (typeof items === 'undefined' || items === null) {
items = []
}
let folder_upload = false
if (files[0].fullPath !== undefined) {
folder_upload = true
}
let conflict = false
for (let i = 0; i < files.length; i++) {
let file = files[i]
let name = file.name
if (folder_upload) {
let dirs = file.fullPath.split("/")
if (dirs.length > 1) {
name = dirs[0]
}
}
let res = items.findIndex(function hasConflict (element) {
return (element.name === this)
}, name)
if (res >= 0) {
conflict = true
break
}
}
if (!conflict) {
this.handleFiles(files, base)
return
}
this.$store.commit('showHover', {
prompt: 'replace',
confirm: (event) => {
event.preventDefault()
this.$store.commit('closeHovers')
this.handleFiles(files, base, true)
}
})
},
uploadInput (event) {
this.$store.commit('closeHovers')
let files = event.currentTarget.files
let folder_upload = files[0].webkitRelativePath !== undefined && files[0].webkitRelativePath !== ''
if (folder_upload) {
for (let i = 0; i < files.length; i++) {
let file = files[i]
files[i].fullPath = file.webkitRelativePath
}
}
this.checkConflict(files, this.req.items, '')
},
resetOpacity () {
let items = document.getElementsByClassName('item')
Array.from(items).forEach(file => {
file.style.opacity = 1
})
},
scanFiles(dt) {
return new Promise((resolve) => {
let reading = 0
const contents = []
if (dt.items !== undefined) {
for (let item of dt.items) {
if (item.kind === "file" && typeof item.webkitGetAsEntry === "function") {
const entry = item.webkitGetAsEntry()
readEntry(entry)
}
}
} else {
resolve(dt.files)
}
function readEntry(entry, directory = "") {
if (entry.isFile) {
reading++
entry.file(file => {
reading--
file.fullPath = `${directory}${file.name}`
contents.push(file)
if (reading === 0) {
resolve(contents)
}
})
} else if (entry.isDirectory) {
const dir = {
isDir: true,
path: `${directory}${entry.name}`
}
contents.push(dir)
readReaderContent(entry.createReader(), `${directory}${entry.name}`)
}
}
function readReaderContent(reader, directory) {
reading++
reader.readEntries(function (entries) {
reading--
if (entries.length > 0) {
for (const entry of entries) {
readEntry(entry, `${directory}/`)
}
readReaderContent(reader, `${directory}/`)
}
if (reading === 0) {
resolve(contents)
}
})
}
})
},
setProgress: throttle(function() {
if (this.uploading.count == 0) {
return
}
let sum = this.uploading.progress.reduce((acc, val) => acc + val)
this.$store.commit('setProgress', Math.ceil(sum / this.uploading.size * 100))
}, 100, {leading: false, trailing: true}),
handleFiles (files, base, overwrite = false) {
if (this.uploading.count == 0) {
buttons.loading('upload')
}
let promises = []
let onupload = (id) => (event) => {
this.uploading.progress[id] = event.loaded
this.setProgress()
}
for (let i = 0; i < files.length; i++) {
let file = files[i]
if (!file.isDir) {
let filename = (file.fullPath !== undefined) ? file.fullPath : file.name
let filenameEncoded = url.encodeRFC5987ValueChars(filename)
let id = this.uploading.id
this.uploading.size += file.size
this.uploading.id++
this.uploading.count++
let promise = api.post(this.$route.path + base + filenameEncoded, file, overwrite, throttle(onupload(id), 100)).finally(() => {
this.uploading.count--
})
promises.push(promise)
} else {
let uri = this.$route.path + base
let folders = file.path.split("/")
for (let i = 0; i < folders.length; i++) {
let folder = folders[i]
let folderEncoded = encodeURIComponent(folder)
uri += folderEncoded + "/"
}
api.post(uri)
}
}
let finish = () => {
if (this.uploading.count > 0) {
return
}
buttons.success('upload')
this.$store.commit('setProgress', 0)
this.$store.commit('setReload', true)
this.uploading.id = 0
this.uploading.sizes = []
this.uploading.progress = []
}
Promise.all(promises)
.then(() => {
finish()
})
.catch(error => {
finish()
this.$showError(error)
})
return false
},
async sort (by) {
let asc = false
if (by === 'name') {
if (this.nameIcon === 'arrow_upward') {
asc = true
}
} else if (by === 'size') {
if (this.sizeIcon === 'arrow_upward') {
asc = true
}
} else if (by === 'modified') {
if (this.modifiedIcon === 'arrow_upward') {
asc = true
}
}
try {
await users.update({ id: this.user.id, sorting: { by, asc } }, ['sorting'])
} catch (e) {
this.$showError(e)
}
this.$store.commit('setReload', true)
}
}
}
</script>

View File

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

View File

@@ -1,166 +0,0 @@
<template>
<div id="previewer">
<div class="bar">
<button @click="back" class="action" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close">
<i class="material-icons">close</i>
</button>
<rename-button v-if="user.perm.rename"></rename-button>
<delete-button v-if="user.perm.delete"></delete-button>
<download-button v-if="user.perm.download"></download-button>
<info-button></info-button>
</div>
<button class="action" @click="prev" v-show="hasPrevious" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')">
<i class="material-icons">chevron_left</i>
</button>
<button class="action" @click="next" v-show="hasNext" :aria-label="$t('buttons.next')" :title="$t('buttons.next')">
<i class="material-icons">chevron_right</i>
</button>
<div class="preview">
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
<audio v-else-if="req.type == 'audio'" :src="raw" autoplay controls></audio>
<video v-else-if="req.type == 'video'" :src="raw" autoplay controls>
<track
kind="captions"
v-for="(sub, index) in subtitles"
:key="index"
:src="sub"
:label="'Subtitle ' + index" :default="index === 0">
Sorry, your browser doesn't support embedded videos,
but don't worry, you can <a :href="download">download it</a>
and watch it with your favorite video player!
</video>
<object v-else-if="req.extension == '.pdf'" class="pdf" :data="raw"></object>
<a v-else-if="req.type == 'blob'" :href="download">
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2>
</a>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import { baseURL } from '@/utils/constants'
import { files as api } from '@/api'
import InfoButton from '@/components/buttons/Info'
import DeleteButton from '@/components/buttons/Delete'
import RenameButton from '@/components/buttons/Rename'
import DownloadButton from '@/components/buttons/Download'
import ExtendedImage from './ExtendedImage'
const mediaTypes = [
"image",
"video",
"audio",
"blob"
]
export default {
name: 'preview',
components: {
InfoButton,
DeleteButton,
RenameButton,
DownloadButton,
ExtendedImage
},
data: function () {
return {
previousLink: '',
nextLink: '',
listing: null,
subtitles: []
}
},
computed: {
...mapState(['req', 'user', 'oldReq', 'jwt']),
hasPrevious () {
return (this.previousLink !== '')
},
hasNext () {
return (this.nextLink !== '')
},
download () {
return `${baseURL}/api/raw${this.req.path}?auth=${this.jwt}`
},
previewUrl () {
if (this.req.type === 'image') {
return `${baseURL}/api/preview/big${this.req.path}?auth=${this.jwt}`
}
return `${baseURL}/api/raw${this.req.path}?auth=${this.jwt}`
},
raw () {
return `${this.previewUrl}&inline=true`
}
},
async mounted () {
window.addEventListener('keyup', this.key)
if (this.req.subtitles) {
this.subtitles = this.req.subtitles.map(sub => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`)
}
try {
if (this.oldReq.items) {
this.updateLinks(this.oldReq.items)
} else {
const path = url.removeLastDir(this.$route.path)
const res = await api.fetch(path)
this.updateLinks(res.items)
}
} catch (e) {
this.$showError(e)
}
},
beforeDestroy () {
window.removeEventListener('keyup', this.key)
},
methods: {
back () {
let uri = url.removeLastDir(this.$route.path) + '/'
this.$router.push({ path: uri })
},
prev () {
this.$router.push({ path: this.previousLink })
},
next () {
this.$router.push({ path: this.nextLink })
},
key (event) {
event.preventDefault()
if (event.which === 13 || event.which === 39) { // right arrow
if (this.hasNext) this.next()
} else if (event.which === 37) { // left arrow
if (this.hasPrevious) this.prev()
}
},
updateLinks (items) {
for (let i = 0; i < items.length; i++) {
if (items[i].name !== this.req.name) {
continue
}
for (let j = i - 1; j >= 0; j--) {
if (mediaTypes.includes(items[j].type)) {
this.previousLink = items[j].url
break
}
}
for (let j = i + 1; j < items.length; j++) {
if (mediaTypes.includes(items[j].type)) {
this.nextLink = items[j].url
break
}
}
return
}
}
}
}
</script>

View File

@@ -0,0 +1,32 @@
<template>
<button @click="action" :aria-label="label" :title="label" class="action">
<i class="material-icons">{{ icon }}</i>
<span>{{ label }}</span>
<span v-if="counter > 0" class="counter">{{ counter }}</span>
</button>
</template>
<script>
export default {
name: 'action',
props: [
'icon',
'label',
'counter',
'show'
],
methods: {
action: function () {
if (this.show) {
this.$store.commit('showHover', this.show)
}
this.$emit('action')
}
}
}
</script>
<style>
</style>

View File

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

View File

@@ -16,7 +16,6 @@
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="button button--flat" <button class="button button--flat"
@click="copy" @click="copy"
:disabled="$route.path === dest"
:aria-label="$t('buttons.copy')" :aria-label="$t('buttons.copy')"
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button> :title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
</div> </div>
@@ -28,6 +27,7 @@ import { mapState } from 'vuex'
import FileList from './FileList' import FileList from './FileList'
import { files as api } from '@/api' import { files as api } from '@/api'
import buttons from '@/utils/buttons' import buttons from '@/utils/buttons'
import * as upload from '@/utils/upload'
export default { export default {
name: 'copy', name: 'copy',
@@ -42,25 +42,66 @@ export default {
methods: { methods: {
copy: async function (event) { copy: async function (event) {
event.preventDefault() event.preventDefault()
buttons.loading('copy')
let items = [] let items = []
// Create a new promise for each file. // Create a new promise for each file.
for (let item of this.selected) { for (let item of this.selected) {
items.push({ items.push({
from: this.req.items[item].url, from: this.req.items[item].url,
to: this.dest + encodeURIComponent(this.req.items[item].name) to: this.dest + encodeURIComponent(this.req.items[item].name),
name: this.req.items[item].name
}) })
} }
try { let action = async (overwrite, rename) => {
await api.copy(items) buttons.loading('copy')
buttons.success('copy')
this.$router.push({ path: this.dest }) await api.copy(items, overwrite, rename).then(() => {
} catch (e) { buttons.success('copy')
buttons.done('copy')
this.$showError(e) if (this.$route.path === this.dest) {
this.$store.commit('setReload', true)
return
}
this.$router.push({ path: this.dest })
}).catch((e) => {
buttons.done('copy')
this.$showError(e)
})
} }
if (this.$route.path === this.dest) {
this.$store.commit('closeHovers')
action(false, true)
return
}
let dstItems = (await api.fetch(this.dest)).items
let conflict = upload.checkConflict(items, dstItems)
let overwrite = false
let rename = false
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace-rename',
confirm: (event, option) => {
overwrite = option == 'overwrite'
rename = option == 'rename'
event.preventDefault()
this.$store.commit('closeHovers')
action(overwrite, rename)
}
})
return
}
action(overwrite, rename)
} }
} }
} }

View File

@@ -20,29 +20,31 @@
<script> <script>
import {mapGetters, mapMutations, mapState} from 'vuex' import {mapGetters, mapMutations, mapState} from 'vuex'
import { files as api } from '@/api' import { files as api } from '@/api'
import url from '@/utils/url'
import buttons from '@/utils/buttons' import buttons from '@/utils/buttons'
export default { export default {
name: 'delete', name: 'delete',
computed: { computed: {
...mapGetters(['isListing', 'selectedCount']), ...mapGetters(['isListing', 'selectedCount']),
...mapState(['req', 'selected']) ...mapState(['req', 'selected', 'showConfirm'])
}, },
methods: { methods: {
...mapMutations(['closeHovers']), ...mapMutations(['closeHovers']),
submit: async function () { submit: async function () {
this.closeHovers()
buttons.loading('delete') buttons.loading('delete')
try { try {
if (!this.isListing) { if (!this.isListing) {
await api.remove(this.$route.path) await api.remove(this.$route.path)
buttons.success('delete') buttons.success('delete')
this.$router.push({ path: url.removeLastDir(this.$route.path) + '/' })
this.showConfirm()
this.closeHovers()
return return
} }
this.closeHovers()
if (this.selectedCount === 0) { if (this.selectedCount === 0) {
return return
} }

View File

@@ -7,43 +7,29 @@
<div class="card-content"> <div class="card-content">
<p>{{ $t('prompts.downloadMessage') }}</p> <p>{{ $t('prompts.downloadMessage') }}</p>
<button class="button button--block" @click="download('zip')" v-focus>zip</button> <button v-for="(ext, format) in formats" :key="format" class="button button--block" @click="showConfirm(format)" v-focus>{{ ext }}</button>
<button class="button button--block" @click="download('tar')" v-focus>tar</button>
<button class="button button--block" @click="download('targz')" v-focus>tar.gz</button>
<button class="button button--block" @click="download('tarbz2')" v-focus>tar.bz2</button>
<button class="button button--block" @click="download('tarxz')" v-focus>tar.xz</button>
<button class="button button--block" @click="download('tarlz4')" v-focus>tar.lz4</button>
<button class="button button--block" @click="download('tarsz')" v-focus>tar.sz</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import {mapGetters, mapState} from 'vuex' import { mapState } from 'vuex'
import { files as api } from '@/api'
export default { export default {
name: 'download', name: 'download',
computed: { data: function () {
...mapState(['selected', 'req']), return {
...mapGetters(['selectedCount']) formats: {
}, zip: 'zip',
methods: { tar: 'tar',
download: function (format) { targz: 'tar.gz',
if (this.selectedCount === 0) { tarbz2: 'tar.bz2',
api.download(format, this.$route.path) tarxz: 'tar.xz',
} else { tarlz4: 'tar.lz4',
let files = [] tarsz: 'tar.sz'
for (let i of this.selected) {
files.push(this.req.items[i].url)
}
api.download(format, ...files)
} }
this.$store.commit('closeHovers')
} }
} },
computed: mapState(['showConfirm'])
} }
</script> </script>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<ul class="file-list"> <ul class="file-list">
<li @click="select" <li @click="itemClick"
@touchstart="touchstart" @touchstart="touchstart"
@dblclick="next" @dblclick="next"
role="button" role="button"
@@ -35,25 +35,13 @@ export default {
} }
}, },
computed: { computed: {
...mapState([ 'req' ]), ...mapState([ 'req', 'user' ]),
nav () { nav () {
return decodeURIComponent(this.current) return decodeURIComponent(this.current)
} }
}, },
mounted () { mounted () {
// If we're showing this on a listing, this.fillOptions(this.req)
// we can use the current request object
// to fill the move options.
if (this.req.kind === 'listing') {
this.fillOptions(this.req)
return
}
// Otherwise, we must be on a preview or editor
// so we fetch the data from the previous directory.
files.fetch(url.removeLastDir(this.$route.path))
.then(this.fillOptions)
.catch(this.$showError)
}, },
methods: { methods: {
fillOptions (req) { fillOptions (req) {
@@ -123,6 +111,10 @@ export default {
this.next(event) this.next(event)
} }
}, },
itemClick: function (event) {
if (this.user.singleClick) this.next(event)
else this.select(event)
},
select: function (event) { select: function (event) {
// If the element is already selected, unselect it. // If the element is already selected, unselect it.
if (this.selected === event.currentTarget.dataset.url) { if (this.selected === event.currentTarget.dataset.url) {

View File

@@ -63,7 +63,7 @@ export default {
return moment(this.req.modified).fromNow() return moment(this.req.modified).fromNow()
} }
return moment(this.req.items[this.selected[0]]).fromNow() return moment(this.req.items[this.selected[0]].modified).fromNow()
}, },
name: function () { name: function () {
return this.selectedCount === 0 ? this.req.name : this.req.items[this.selected[0]].name return this.selectedCount === 0 ? this.req.name : this.req.items[this.selected[0]].name

View File

@@ -27,6 +27,7 @@ import { mapState } from 'vuex'
import FileList from './FileList' import FileList from './FileList'
import { files as api } from '@/api' import { files as api } from '@/api'
import buttons from '@/utils/buttons' import buttons from '@/utils/buttons'
import * as upload from '@/utils/upload'
export default { export default {
name: 'move', name: 'move',
@@ -41,26 +42,51 @@ export default {
methods: { methods: {
move: async function (event) { move: async function (event) {
event.preventDefault() event.preventDefault()
buttons.loading('move')
let items = [] let items = []
for (let item of this.selected) { for (let item of this.selected) {
items.push({ items.push({
from: this.req.items[item].url, from: this.req.items[item].url,
to: this.dest + encodeURIComponent(this.req.items[item].name) to: this.dest + encodeURIComponent(this.req.items[item].name),
name: this.req.items[item].name
}) })
} }
try { let action = async (overwrite, rename) => {
api.move(items) buttons.loading('move')
buttons.success('move')
this.$router.push({ path: this.dest }) await api.move(items, overwrite, rename).then(() => {
} catch (e) { buttons.success('move')
buttons.done('move') this.$router.push({ path: this.dest })
this.$showError(e) }).catch((e) => {
buttons.done('move')
this.$showError(e)
})
} }
event.preventDefault() let dstItems = (await api.fetch(this.dest)).items
let conflict = upload.checkConflict(items, dstItems)
let overwrite = false
let rename = false
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace-rename',
confirm: (event, option) => {
overwrite = option == 'overwrite'
rename = option == 'rename'
event.preventDefault()
this.$store.commit('closeHovers')
action(overwrite, rename)
}
})
return
}
action(overwrite, rename)
} }
} }
} }

View File

@@ -16,8 +16,10 @@ import Copy from './Copy'
import NewFile from './NewFile' import NewFile from './NewFile'
import NewDir from './NewDir' import NewDir from './NewDir'
import Replace from './Replace' import Replace from './Replace'
import ReplaceRename from './ReplaceRename'
import Share from './Share' import Share from './Share'
import Upload from './Upload' import Upload from './Upload'
import ShareDelete from './ShareDelete'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import buttons from '@/utils/buttons' import buttons from '@/utils/buttons'
@@ -35,7 +37,9 @@ export default {
NewDir, NewDir,
Help, Help,
Replace, Replace,
Upload ReplaceRename,
Upload,
ShareDelete
}, },
data: function () { data: function () {
return { return {
@@ -87,8 +91,10 @@ export default {
'newDir', 'newDir',
'download', 'download',
'replace', 'replace',
'replace-rename',
'share', 'share',
'upload' 'upload',
'share-delete'
].indexOf(this.show) >= 0; ].indexOf(this.show) >= 0;
return matched && this.show || null; return matched && this.show || null;

View File

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

View File

@@ -1,59 +1,85 @@
<template> <template>
<div class="card floating" id="share"> <div class="card floating share__promt__card" id="share">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('buttons.share') }}</h2> <h2>{{ $t('buttons.share') }}</h2>
</div> </div>
<div class="card-content"> <template v-if="listing">
<ul> <div class="card-content">
<li v-if="!hasPermanent"> <table>
<a @click="getPermalink" :aria-label="$t('buttons.permalink')">{{ $t('buttons.permalink') }}</a> <tr>
</li> <th>#</th>
<th>{{ $t('settings.shareDuration') }}</th>
<th></th>
<th></th>
</tr>
<li v-for="link in links" :key="link.hash"> <tr v-for="link in links" :key="link.hash">
<a :href="buildLink(link.hash)" target="_blank"> <td>{{ link.hash }}</td>
<template v-if="link.expire !== 0">{{ humanTime(link.expire) }}</template> <td>
<template v-else>{{ $t('permanent') }}</template> <template v-if="link.expire !== 0">{{ humanTime(link.expire) }}</template>
</a> <template v-else>{{ $t('permanent') }}</template>
</td>
<td class="small">
<button class="action copy-clipboard"
:data-clipboard-text="buildLink(link.hash)"
:aria-label="$t('buttons.copyToClipboard')"
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
</td>
<td class="small">
<button class="action"
@click="deleteLink($event, link)"
:aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
</td>
</tr>
</table>
</div>
<button class="action" <div class="card-action">
@click="deleteLink($event, link)" <button class="button button--flat button--grey"
:aria-label="$t('buttons.delete')" @click="$store.commit('closeHovers')"
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button> :aria-label="$t('buttons.close')"
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
<button class="button button--flat button--blue"
@click="() => switchListing()"
:aria-label="$t('buttons.new')"
:title="$t('buttons.new')">{{ $t('buttons.new') }}</button>
</div>
</template>
<button class="action copy-clipboard" <template v-else>
:data-clipboard-text="buildLink(link.hash)" <div class="card-content">
:aria-label="$t('buttons.copyToClipboard')" <p>{{ $t('settings.shareDuration') }}</p>
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button> <div class="input-group input">
</li> <input v-focus
type="number"
max="2147483647"
min="1"
@keyup.enter="submit"
v-model.trim="time">
<select class="right" v-model="unit" :aria-label="$t('time.unit')">
<option value="seconds">{{ $t('time.seconds') }}</option>
<option value="minutes">{{ $t('time.minutes') }}</option>
<option value="hours">{{ $t('time.hours') }}</option>
<option value="days">{{ $t('time.days') }}</option>
</select>
</div>
<p>{{ $t('prompts.optionalPassword') }}</p>
<input class="input input--block" type="password" v-model.trim="password">
</div>
<li> <div class="card-action">
<input v-focus <button class="button button--flat button--grey"
type="number" @click="() => switchListing()"
max="2147483647" :aria-label="$t('buttons.cancel')"
min="0" :title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
@keyup.enter="submit" <button class="button button--flat button--blue"
v-model.trim="time"> @click="submit"
<select v-model="unit" :aria-label="$t('time.unit')"> :aria-label="$t('buttons.share')"
<option value="seconds">{{ $t('time.seconds') }}</option> :title="$t('buttons.share')">{{ $t('buttons.share') }}</button>
<option value="minutes">{{ $t('time.minutes') }}</option> </div>
<option value="hours">{{ $t('time.hours') }}</option> </template>
<option value="days">{{ $t('time.days') }}</option>
</select>
<button class="action"
@click="submit"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')"><i class="material-icons">add</i></button>
</li>
</ul>
</div>
<div class="card-action">
<button class="button button--flat"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.close')"
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
</div>
</div> </div>
</template> </template>
@@ -70,9 +96,10 @@ export default {
return { return {
time: '', time: '',
unit: 'hours', unit: 'hours',
hasPermanent: false,
links: [], links: [],
clip: null clip: null,
password: '',
listing: true
} }
}, },
computed: { computed: {
@@ -97,11 +124,8 @@ export default {
this.links = links this.links = links
this.sort() this.sort()
for (let link of this.links) { if (this.links.length == 0) {
if (link.expire === 0) { this.listing = false
this.hasPermanent = true
break
}
} }
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e)
@@ -118,22 +142,25 @@ export default {
}, },
methods: { methods: {
submit: async function () { submit: async function () {
if (!this.time) return let isPermanent = !this.time || this.time == 0
try { try {
const res = await api.create(this.url, this.time, this.unit) let res = null
if (isPermanent) {
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.links.push(res)
this.sort() this.sort()
} catch (e) {
this.$showError(e) this.time = ''
} this.unit = 'hours'
}, this.password = ''
getPermalink: async function () {
try { this.listing = true
const res = await api.create(this.url)
this.links.push(res)
this.sort()
this.hasPermanent = true
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e)
} }
@@ -142,8 +169,11 @@ export default {
event.preventDefault() event.preventDefault()
try { try {
await api.remove(link.hash) await api.remove(link.hash)
if (link.expire === 0) this.hasPermanent = false
this.links = this.links.filter(item => item.hash !== link.hash) this.links = this.links.filter(item => item.hash !== link.hash)
if (this.links.length == 0) {
this.listing = false
}
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e)
} }
@@ -160,6 +190,13 @@ export default {
if (b.expire === 0) return 1 if (b.expire === 0) return 1
return new Date(a.expire) - new Date(b.expire) return new Date(a.expire) - new Date(b.expire)
}) })
},
switchListing () {
if (this.links.length == 0 && !this.listing) {
this.$store.commit('closeHovers')
}
this.listing = !this.listing
} }
} }
} }

View File

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

View File

@@ -27,9 +27,11 @@ export default {
name: 'upload', name: 'upload',
methods: { methods: {
uploadFile: function () { uploadFile: function () {
document.getElementById('upload-input').value = ''
document.getElementById('upload-input').click() document.getElementById('upload-input').click()
}, },
uploadFolder: function () { uploadFolder: function () {
document.getElementById('upload-folder-input').value = ''
document.getElementById('upload-folder-input').click() document.getElementById('upload-folder-input').click()
} }
} }

View File

@@ -16,7 +16,11 @@ export default {
return this.commands.join(' ') return this.commands.join(' ')
}, },
set (value) { set (value) {
this.$emit('update:commands', value.split(' ')) if (value !== '') {
this.$emit('update:commands', value.split(' '))
} else {
this.$emit('update:commands', [])
}
} }
} }
} }

View File

@@ -9,13 +9,14 @@
<p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p> <p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p> <p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p> <p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p> <p v-if="isExecEnabled"><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p> <p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p> <p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p>
</div> </div>
</template> </template>
<script> <script>
import { enableExec } from '@/utils/constants'
export default { export default {
name: 'permissions', name: 'permissions',
props: ['perm'], props: ['perm'],
@@ -33,7 +34,8 @@ export default {
this.perm.admin = value this.perm.admin = value
} }
} },
isExecEnabled: () => enableExec
} }
} }
</script> </script>

View File

@@ -25,7 +25,7 @@
</p> </p>
<permissions :perm.sync="user.perm" /> <permissions :perm.sync="user.perm" />
<commands :commands.sync="user.commands" /> <commands v-if="isExecEnabled" :commands.sync="user.commands" />
<div v-if="!isDefault"> <div v-if="!isDefault">
<h3>{{ $t('settings.rules') }}</h3> <h3>{{ $t('settings.rules') }}</h3>
@@ -40,6 +40,7 @@ import Languages from './Languages'
import Rules from './Rules' import Rules from './Rules'
import Permissions from './Permissions' import Permissions from './Permissions'
import Commands from './Commands' import Commands from './Commands'
import { enableExec } from '@/utils/constants'
export default { export default {
name: 'user', name: 'user',
@@ -53,7 +54,8 @@ export default {
computed: { computed: {
passwordPlaceholder () { passwordPlaceholder () {
return this.isNew ? '' : this.$t('settings.avoidChanges') return this.isNew ? '' : this.$t('settings.avoidChanges')
} },
isExecEnabled: () => enableExec
}, },
watch: { watch: {
'user.perm.admin': function () { 'user.perm.admin': function () {

View File

@@ -25,8 +25,8 @@
background: var(--red); background: var(--red);
} }
.button--red:hover { .button--blue {
background: var(--dark-red); background: var(--blue);
} }
.button--flat { .button--flat {

View File

@@ -1,29 +1,73 @@
.share__box { .share {
text-align: center; display: flex;
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px; flex-wrap: wrap;
background: #fff; justify-content: center;
display: block; align-items: flex-start;
border-radius: 0.2em;
width: 90%;
max-width: 25em;
margin: 6em auto;
} }
.share__box__download { @media (max-width: 736px) {
width: 100%; .share {
display: block;
}
}
.share__box {
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
background: #fff;
border-radius: 0.2em;
margin: 5px;
overflow: hidden;
}
.share__box__header {
padding: 1em; padding: 1em;
cursor: pointer; text-align: center;
background: #ffffff; }
color: rgba(0, 0, 0, 0.5);
border-bottom: 1px solid rgba(0, 0, 0, 0.05); .share__box__icon i {
font-size: 10em;
color: #40c4ff;
}
.share__box__center {
text-align: center;
} }
.share__box__info { .share__box__info {
padding: 2em 3em; flex: 1 1 auto;
} }
.share__box__title { .share__box__element {
margin-top: .2em; padding: 1em;
overflow: hidden; border-top: 1px solid rgba(0, 0, 0, 0.1);
text-overflow: ellipsis; word-break: break-all;
}
.share__box__items {
text-align: left;
flex: 10 0 25em;
}
.share__box__items #listing.list .item {
cursor: pointer;
border-left: 0;
border-right: 0;
border-bottom: 0;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.share__box__items #listing.list .item .name {
width: 50%;
}
.share__box__items #listing.list .item .modified {
width: 25%;
}
.share__wrong__password {
background: var(--red);
color: #fff;
padding: .5em;
text-align: center;
animation: .2s opac forwards;
} }

View File

@@ -83,29 +83,29 @@ main {
width: calc(100% - 19em); width: calc(100% - 19em);
} }
#breadcrumbs { .breadcrumbs {
height: 3em; height: 3em;
border-bottom: 1px solid rgba(0, 0, 0, 0.05); border-bottom: 1px solid rgba(0, 0, 0, 0.05);
} }
#breadcrumbs span, .breadcrumbs span,
#breadcrumbs { .breadcrumbs {
display: flex; display: flex;
align-items: center; align-items: center;
color: #6f6f6f; color: #6f6f6f;
} }
#breadcrumbs a { .breadcrumbs a {
color: inherit; color: inherit;
transition: .1s ease-in; transition: .1s ease-in;
border-radius: .125em; border-radius: .125em;
} }
#breadcrumbs a:hover { .breadcrumbs a:hover {
background-color: rgba(0,0,0, 0.05); background-color: rgba(0,0,0, 0.05);
} }
#breadcrumbs span a { .breadcrumbs span a {
padding: .2em; padding: .2em;
} }

View File

@@ -1,8 +1,29 @@
.dashboard { .dashboard {
max-width: 600px;
margin: 1em 0; margin: 1em 0;
} }
.dashboard .row {
display: flex;
margin: 0 -.5em;
flex-wrap: wrap;
}
.dashboard .row .column {
display: flex;
padding: 0 .5em;
width: 50%;
}
.dashboard .row .column .card {
flex-grow: 1;
}
@media(max-width: 1200px) {
.dashboard .row .column {
width: 100%;
}
}
a { a {
color: inherit color: inherit
} }
@@ -28,25 +49,56 @@ p code {
} }
.dashboard #nav { .dashboard #nav {
display: flex;
padding-bottom: 1em;
overflow: auto;
}
.dashboard #nav .wrapper {
display: flex;
flex-grow: 1;
border-bottom: 2px solid rgba(0, 0, 0, 0.05);
}
.dashboard #nav ul {
list-style: none; list-style: none;
display: flex; display: flex;
color: rgb(84, 110, 122); color: rgb(84, 110, 122);
font-weight: 500; font-weight: 500;
margin: 0 0 1em; padding: 0;
margin: 0 0 -2px 0;
font-size: .8em; font-size: .8em;
text-align: center; text-align: center;
justify-content: space-between; justify-content: left;
padding: 0;
} }
.dashboard #nav li { .dashboard #nav ul li {
position: relative;
padding: 1.5em 2em;
white-space: nowrap;
border-bottom: 2px solid transparent;
transition: .1s ease-in-out all;
}
.dashboard #nav ul li:hover {
background: var(--moon-grey);
}
.dashboard #nav ul li.active {
border-color: var(--blue);
color: var(--blue);
}
.dashboard #nav ul li.active::before {
width: 100%; width: 100%;
padding: 0 0 1em; height: 100%;
border-bottom: 2px solid rgba(0, 0, 0, 0.05); position: absolute;
} top: 0;
left: 0;
.dashboard #nav li.active { content: "";
border-color: var(--blue) background: var(--blue);
opacity: 0.08;
} }
.dashboard #nav i { .dashboard #nav i {
@@ -92,7 +144,7 @@ table tr>*:last-child {
.card { .card {
position: relative; position: relative;
margin: .5rem 0 1rem 0; margin: 0 0 1rem 0;
background-color: #fff; background-color: #fff;
border-radius: 2px; border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
@@ -151,6 +203,7 @@ table tr>*:last-child {
.card .card-content.full { .card .card-content.full {
padding-bottom: 0; padding-bottom: 0;
overflow: auto;
} }
.card h2 { .card h2 {
@@ -226,6 +279,18 @@ table tr>*:last-child {
opacity: 1; opacity: 1;
} }
.card#share .input-group {
display: flex;
}
.card#share .input-group * {
border: none;
}
.card#share .input-group input {
flex: 1;
}
.overlay { .overlay {
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
position: fixed; position: fixed;

View File

@@ -6,9 +6,25 @@ header {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
height: 4em;
width: 100%; width: 100%;
padding: 0; padding: 0;
display: flex; display: flex;
padding: 0.5em 0.5em 0.5em 1em;
align-items: center;
}
header > * {
flex: 0 0 auto;
}
header title {
display: block;
flex: 1 1 auto;
padding: 0 1em;
overflow: hidden;
text-overflow: ellipsis;
font-size: 1.2em;
} }
header .overlay { header .overlay {
@@ -30,17 +46,6 @@ header img {
height: 2.5em; height: 2.5em;
} }
header>div:first-child>.action {
display: none;
}
header>div {
display: flex;
width: 100%;
padding: 0.5em 0.5em 0.5em 1em;
align-items: center;
}
header .action span { header .action span {
display: none; display: none;
} }
@@ -50,19 +55,8 @@ header>div div {
position: relative; position: relative;
} }
header>div:last-child div { header .search-button,
display: flex; header .menu-button {
}
header>div:first-child {
height: 4em;
}
header>div:last-child {
justify-content: flex-end;
}
header .search-button {
display: none; display: none;
} }

View File

@@ -70,6 +70,7 @@
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px; box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
width: 95%; width: 95%;
max-width: 20em; max-width: 20em;
z-index: 1;
} }
#file-selection .action { #file-selection .action {
border-radius: 50%; border-radius: 50%;
@@ -81,6 +82,9 @@
color: #6f6f6f; color: #6f6f6f;
margin-right: auto; margin-right: auto;
} }
#file-selection .action span {
display: none;
}
nav { nav {
top: 0; top: 0;
z-index: 99999; z-index: 99999;
@@ -95,7 +99,7 @@
left: 0; left: 0;
} }
header .search-button, header .search-button,
header>div:first-child>.action { header .menu-button {
display: inherit; display: inherit;
} }
header img { header img {

View File

@@ -96,10 +96,11 @@
color: #fff; color: #fff;
border-radius: 50%; border-radius: 50%;
font-size: .75em; font-size: .75em;
width: 1.5em; width: 1.8em;
height: 1.5em; height: 1.8em;
text-align: center; text-align: center;
line-height: 1.25em; line-height: 1.55em;
font-weight: bold;
border: 2px solid white; border: 2px solid white;
} }
@@ -117,35 +118,33 @@
overflow: hidden; overflow: hidden;
} }
#previewer .bar { #previewer header {
width: 100%; background: none;
text-align: right;
display: flex;
padding: 0.5em;
height: 3.7em;
}
#previewer .action:first-of-type {
margin-right: auto;
}
#previewer .action i {
color: #fff; color: #fff;
} }
#previewer .action:hover { #previewer header > .action i {
color: #fff;
}
@media (min-width: 738px) {
#previewer header #dropdown .action i {
color: #fff;
}
}
#previewer header .action:hover {
background-color: rgba(255, 255, 255, 0.3) background-color: rgba(255, 255, 255, 0.3)
} }
#previewer .action span { #previewer header .action span {
display: none; display: none;
} }
#previewer .preview { #previewer .preview {
margin: 2em auto 4em; margin-top: 4em;
max-width: 80%;
text-align: center; text-align: center;
height: calc(100vh - 9.7em); height: calc(100vh - 4em);
} }
#previewer .preview pre { #previewer .preview pre {
@@ -160,6 +159,10 @@
margin: 0; margin: 0;
} }
#previewer .preview video {
height: 100%;
}
#previewer .pdf { #previewer .pdf {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -172,8 +175,25 @@
#previewer>button { #previewer>button {
margin: 0; margin: 0;
position: fixed; position: fixed;
top: 50%; top: calc(50% + 1.85em);
transform: translateY(-50%); transform: translateY(-50%);
background-color: rgba(80, 80, 80, .5);
color: white;
border-radius: 50%;
cursor: pointer;
border: 0;
margin: 0;
padding: 0;
transition: 0.2s ease all;
}
#previewer>button.hidden {
opacity: 0;
visibility: hidden;
}
#previewer>button>i {
padding: 0.4em;
} }
#previewer>button:first-of-type { #previewer>button:first-of-type {
@@ -184,6 +204,40 @@
right: 0.5em; right: 0.5em;
} }
/* EDITOR */
#editor-container {
background-color: #fafafa;
position: fixed;
margin-top: 4em;
top: 0;
left: 0;
width: 100%;
z-index: 9999;
overflow: hidden;
}
#previewer .loading {
height: 100%;
width: 100%;
}
#editor-container #editor {
height: calc(100vh - 8.4em);
}
#editor-container .breadcrumbs {
height: 2.3em;
padding: 0 1em;
}
#editor-container .breadcrumbs span {
font-size: 12px;
}
#editor-container .breadcrumbs i {
font-size: 16px;
}
/* * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * *
* PROMPT * * PROMPT *

View File

@@ -1,7 +1,5 @@
{ {
"permanent": "دائم",
"buttons": { "buttons": {
"shell": "Toggle shell",
"cancel": "إلغاء", "cancel": "إلغاء",
"close": "إغلاق", "close": "إغلاق",
"copy": "نسخ", "copy": "نسخ",
@@ -10,6 +8,7 @@
"create": "إنشاء", "create": "إنشاء",
"delete": "حذف", "delete": "حذف",
"download": "تحميل", "download": "تحميل",
"hideDotfiles": "",
"info": "معلومات", "info": "معلومات",
"more": "المزيد", "more": "المزيد",
"move": "نقل", "move": "نقل",
@@ -17,25 +16,28 @@
"new": "جديد", "new": "جديد",
"next": "التالي", "next": "التالي",
"ok": "موافق", "ok": "موافق",
"replace": "استبدال", "permalink": "الحصول على لنك دائم",
"previous": "السابق", "previous": "السابق",
"publish": "نشر",
"rename": "إعادة تسمية", "rename": "إعادة تسمية",
"replace": "استبدال",
"reportIssue": "إبلاغ عن مشكلة", "reportIssue": "إبلاغ عن مشكلة",
"save": "حفظ", "save": "حفظ",
"schedule": "جدولة",
"search": "بحث", "search": "بحث",
"select": "تحديد", "select": "تحديد",
"share": "مشاركة",
"publish": "نشر",
"selectMultiple": "تحديد متعدد", "selectMultiple": "تحديد متعدد",
"schedule": "جدولة", "share": "مشاركة",
"shell": "Toggle shell",
"switchView": "تغيير العرض", "switchView": "تغيير العرض",
"toggleSidebar": "تبديل الشريط الجانبي", "toggleSidebar": "تبديل الشريط الجانبي",
"update": "تحديث", "update": "تحديث",
"upload": "رفع", "upload": "رفع"
"permalink": "الحصول على لنك دائم"
}, },
"success": { "download": {
"linkCopied": "تم نسخ الملف" "downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": ""
}, },
"errors": { "errors": {
"forbidden": "You don't have permissions to access this.", "forbidden": "You don't have permissions to access this.",
@@ -43,11 +45,11 @@
"notFound": "لا يمكن الوصول لهذا المحتوى." "notFound": "لا يمكن الوصول لهذا المحتوى."
}, },
"files": { "files": {
"folders": "المجلدات",
"files": "الملفات",
"body": "الصفحة", "body": "الصفحة",
"clear": "مسح", "clear": "مسح",
"closePreview": "إغلاق العرض", "closePreview": "إغلاق العرض",
"files": "الملفات",
"folders": "المجلدات",
"home": "الصفحة الاولى", "home": "الصفحة الاولى",
"lastModified": "آخر تعديل", "lastModified": "آخر تعديل",
"loading": "جاري التحميل...", "loading": "جاري التحميل...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "التحديد المتعدد مفعل", "multipleSelectionEnabled": "التحديد المتعدد مفعل",
"name": "الإسم", "name": "الإسم",
"size": "الحجم", "size": "الحجم",
"sortByLastModified": "الترتيب بآخر تعديل",
"sortByName": "الترتيب بالإسم", "sortByName": "الترتيب بالإسم",
"sortBySize": "الترتيب بالحجم", "sortBySize": "الترتيب بالحجم"
"sortByLastModified": "الترتيب بآخر تعديل"
}, },
"help": { "help": {
"click": "حدد الملف أو المجلد", "click": "حدد الملف أو المجلد",
@@ -74,18 +76,39 @@
"f2": "إعادة تسمية الملف", "f2": "إعادة تسمية الملف",
"help": "مساعدة" "help": "مساعدة"
}, },
"languages": {
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"svSE": "",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": { "login": {
"password": "كلمة المرور",
"passwordConfirm": "Password Confirmation",
"submit": "تسجيل دخول",
"createAnAccount": "Create an account", "createAnAccount": "Create an account",
"loginInstead": "Already have an account", "loginInstead": "Already have an account",
"password": "كلمة المرور",
"passwordConfirm": "Password Confirmation",
"passwordsDontMatch": "Passwords don't match", "passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup", "signup": "Signup",
"submit": "تسجيل دخول",
"username": "إسم المستخدم", "username": "إسم المستخدم",
"usernameTaken": "Username already taken",
"wrongCredentials": "بيانات دخول خاطئة" "wrongCredentials": "بيانات دخول خاطئة"
}, },
"permanent": "دائم",
"prompts": { "prompts": {
"copy": "نسخ", "copy": "نسخ",
"copyMessage": "رجاء حدد المكان لنسخ ملفاتك فيه:", "copyMessage": "رجاء حدد المكان لنسخ ملفاتك فيه:",
@@ -102,43 +125,64 @@
"lastModified": "آخر تعديل", "lastModified": "آخر تعديل",
"move": "نقل", "move": "نقل",
"moveMessage": "إختر مكان جديد للملفات أو المجلدات المراد نقلها:", "moveMessage": "إختر مكان جديد للملفات أو المجلدات المراد نقلها:",
"newArchetype": "إنشاء منشور من المنشور الأصلي. الملف سيتم انشاءه في مجلد المحتويات.",
"newDir": "مجلد جديد", "newDir": "مجلد جديد",
"newDirMessage": "رجاء أدخل اسم المجلد الجديد.", "newDirMessage": "رجاء أدخل اسم المجلد الجديد.",
"newFile": "ملف جديد", "newFile": "ملف جديد",
"newFileMessage": "رجاء ادخل اسم الملف الجديد.", "newFileMessage": "رجاء ادخل اسم الملف الجديد.",
"numberDirs": "عدد المجلدات", "numberDirs": "عدد المجلدات",
"numberFiles": "عدد الملفات", "numberFiles": "عدد الملفات",
"replace": "إستبدال",
"replaceMessage": "أحد الملفات التي تحاول رفعها يتعارض مع ملف موجود بنفس الإسم. هل تريد إستبدال الملف الموجود؟\n",
"rename": "إعادة تسمية", "rename": "إعادة تسمية",
"renameMessage": "إدراج اسم جديد لـ", "renameMessage": "إدراج اسم جديد لـ",
"show": "عرض", "replace": "إستبدال",
"size": "الحجم", "replaceMessage": "أحد الملفات التي تحاول رفعها يتعارض مع ملف موجود بنفس الإسم. هل تريد إستبدال الملف الموجود؟\n",
"schedule": "جدولة", "schedule": "جدولة",
"scheduleMessage": "أختر الوقت والتاريخ لجدولة نشر هذا المقال.", "scheduleMessage": "أختر الوقت والتاريخ لجدولة نشر هذا المقال.",
"newArchetype": "إنشاء منشور من المنشور الأصلي. الملف سيتم انشاءه في مجلد المحتويات." "show": "عرض",
"size": "الحجم",
"upload": "",
"uploadMessage": ""
},
"search": {
"images": "الصور",
"music": "الموسيقى",
"pdf": "PDF",
"pressToSearch": "Press enter to search...",
"search": "البحث...",
"typeToSearch": "Type to search...",
"types": "الأنواع",
"video": "فيديوهات"
}, },
"settings": { "settings": {
"instanceName": "Instance name",
"brandingDirectoryPath": "Branding directory path",
"documentation": "documentation",
"branding": "Branding",
"disableExternalLinks": "Disable external links (except documentation)",
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"admin": "Admin", "admin": "Admin",
"administrator": "Administrator", "administrator": "Administrator",
"allowCommands": "تنفيذ الأوامر", "allowCommands": "تنفيذ الأوامر",
"allowEdit": "تعديل، إعادة تسمية وحذف الملفات والمجلدات", "allowEdit": "تعديل، إعادة تسمية وحذف الملفات والمجلدات",
"allowNew": "إنشاء ملفات ومجلدات جديدة", "allowNew": "إنشاء ملفات ومجلدات جديدة",
"allowPublish": "نشر مقالات وصفحات جديدة", "allowPublish": "نشر مقالات وصفحات جديدة",
"allowSignup": "Allow users to signup",
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)", "avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
"branding": "Branding",
"brandingDirectoryPath": "Branding directory path",
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"changePassword": "تغيير كلمة المرور", "changePassword": "تغيير كلمة المرور",
"commandRunner": "Command runner", "commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.", "commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "تم تحديث الأوامر", "commandsUpdated": "تم تحديث الأوامر",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "ستايل مخصص", "customStylesheet": "ستايل مخصص",
"defaultUserDescription": "This are the default settings for new users.",
"disableExternalLinks": "Disable external links (except documentation)",
"documentation": "documentation",
"examples": "أمثلة", "examples": "أمثلة",
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"globalSettings": "إعدادات عامة", "globalSettings": "إعدادات عامة",
"hideDotfiles": "",
"insertPath": "Insert the path",
"insertRegex": "Insert regex expression",
"instanceName": "Instance name",
"language": "اللغة", "language": "اللغة",
"lockPassword": "منع المستخدم من تغيير كلمة المرور", "lockPassword": "منع المستخدم من تغيير كلمة المرور",
"newPassword": "كلمة المرور الجديدة", "newPassword": "كلمة المرور الجديدة",
@@ -146,6 +190,16 @@
"newUser": "مستخدم جديد", "newUser": "مستخدم جديد",
"password": "كلمة المرور", "password": "كلمة المرور",
"passwordUpdated": "تم تغيير كلمة المرور", "passwordUpdated": "تم تغيير كلمة المرور",
"path": "",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"execute": "Execute commands",
"modify": "Edit files",
"rename": "Rename or move files and directories",
"share": "Share files"
},
"permissions": "الصلاحيات", "permissions": "الصلاحيات",
"permissionsHelp": "يمكنك تعيين المستخدم كـ \"مدير\" أو تحديد الصلاحيات بشكل منفرد.\n إذا قمت بتحديد المستخدم كـ \"مدير\"، باقي الخيارات سيتم تحديدها تلقائياً.\n إدارة المستخدمين تبقى صلاحية فريدة للـ \"مدير\" فقط.\n", "permissionsHelp": "يمكنك تعيين المستخدم كـ \"مدير\" أو تحديد الصلاحيات بشكل منفرد.\n إذا قمت بتحديد المستخدم كـ \"مدير\"، باقي الخيارات سيتم تحديدها تلقائياً.\n إدارة المستخدمين تبقى صلاحية فريدة للـ \"مدير\" فقط.\n",
"profileSettings": "إعدادات الحساب", "profileSettings": "إعدادات الحساب",
@@ -155,82 +209,46 @@
"rulesHelp": "يمكنك هنا تحديد مجموعة من شروط السماح والمنع لهذا المستخدم. الملفات الممنوعة لن تظهر ضمن القائمة لهذا المستخدم ولن يستطيع الوصول لها. هنا ندعم الـ regex والـ relative path لنطاق المستخدمين.\n", "rulesHelp": "يمكنك هنا تحديد مجموعة من شروط السماح والمنع لهذا المستخدم. الملفات الممنوعة لن تظهر ضمن القائمة لهذا المستخدم ولن يستطيع الوصول لها. هنا ندعم الـ regex والـ relative path لنطاق المستخدمين.\n",
"scope": "نطاق", "scope": "نطاق",
"settingsUpdated": "تم تعديل الإعدادات", "settingsUpdated": "تم تعديل الإعدادات",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "المستخدم", "user": "المستخدم",
"userCommands": "الأوامر", "userCommands": "الأوامر",
"userCommandsHelp": "الأوامر المتاحة لهذا المستخدم مفصولة فيما بينها بمسافة. مثال:\n", "userCommandsHelp": "الأوامر المتاحة لهذا المستخدم مفصولة فيما بينها بمسافة. مثال:\n",
"userCreated": "تم إنشاء المستخدم", "userCreated": "تم إنشاء المستخدم",
"userDefaults": "User default settings",
"userDeleted": "تم حذف المستخدم", "userDeleted": "تم حذف المستخدم",
"userManagement": "إدارة المستخدمين", "userManagement": "إدارة المستخدمين",
"username": "إسم المستخدم",
"users": "المستخدمين",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"allowSignup": "Allow users to signup",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Insert regex expression",
"insertPath": "Insert the path",
"userUpdated": "تم تعديل المستخدم", "userUpdated": "تم تعديل المستخدم",
"userDefaults": "User default settings", "username": "إسم المستخدم",
"defaultUserDescription": "This are the default settings for new users.", "users": "المستخدمين"
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"modify": "Edit files",
"execute": "Execute commands",
"rename": "Rename or move files and directories",
"share": "Share files"
}
}, },
"sidebar": { "sidebar": {
"help": "مساعدة", "help": "مساعدة",
"hugoNew": "هيوجو جديد",
"login": "Login", "login": "Login",
"signup": "Signup",
"logout": "تسجيل خروج", "logout": "تسجيل خروج",
"myFiles": "ملفاتي", "myFiles": "ملفاتي",
"newFile": "ملف جديد", "newFile": "ملف جديد",
"newFolder": "مجلد جديد", "newFolder": "مجلد جديد",
"preview": "معاينة",
"settings": "الإعدادات", "settings": "الإعدادات",
"siteSettings": "إعدادات الموقع", "signup": "Signup",
"hugoNew": "هيوجو جديد", "siteSettings": "إعدادات الموقع"
"preview": "معاينة"
}, },
"search": { "success": {
"images": "الصور", "linkCopied": "تم نسخ الملف"
"music": "الموسيقى",
"pdf": "PDF",
"types": "الأنواع",
"video": "فيديوهات",
"search": "البحث...",
"typeToSearch": "Type to search...",
"pressToSearch": "Press enter to search..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
}, },
"time": { "time": {
"unit": "وحدة الوقت", "days": "أيام",
"seconds": "ثواني",
"minutes": "دقائق",
"hours": "ساعات", "hours": "ساعات",
"days": "أيام" "minutes": "دقائق",
}, "seconds": "ثواني",
"download": { "unit": "وحدة الوقت"
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
} }
} }

View File

@@ -1,7 +1,5 @@
{ {
"permanent": "Permanent",
"buttons": { "buttons": {
"shell": "Kommandozeile ein/ausschalten",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"close": "Schließen", "close": "Schließen",
"copy": "Kopieren", "copy": "Kopieren",
@@ -10,6 +8,7 @@
"create": "Neu", "create": "Neu",
"delete": "Löschen", "delete": "Löschen",
"download": "Downloaden", "download": "Downloaden",
"hideDotfiles": "",
"info": "Info", "info": "Info",
"more": "mehr", "more": "mehr",
"move": "Verschieben", "move": "Verschieben",
@@ -17,25 +16,28 @@
"new": "Neu", "new": "Neu",
"next": "nächste", "next": "nächste",
"ok": "OK", "ok": "OK",
"replace": "Ersetzen", "permalink": "permanenten Verweis anzeigen",
"previous": "vorherige", "previous": "vorherige",
"publish": "Veröffentlichen",
"rename": "umbenennen", "rename": "umbenennen",
"replace": "Ersetzen",
"reportIssue": "Fehler melden", "reportIssue": "Fehler melden",
"save": "Speichern", "save": "Speichern",
"schedule": "Planung",
"search": "Suchen", "search": "Suchen",
"select": "Auswählen", "select": "Auswählen",
"share": "Teilen",
"publish": "Veröffentlichen",
"selectMultiple": "Mehrfachauswahl", "selectMultiple": "Mehrfachauswahl",
"schedule": "Planung", "share": "Teilen",
"shell": "Kommandozeile ein/ausschalten",
"switchView": "Ansicht wechseln", "switchView": "Ansicht wechseln",
"toggleSidebar": "Seitenleiste anzeigen", "toggleSidebar": "Seitenleiste anzeigen",
"update": "Update", "update": "Update",
"upload": "Upload", "upload": "Upload"
"permalink": "permanenten Verweis anzeigen"
}, },
"success": { "download": {
"linkCopied": "Verweis wurde kopiert!" "downloadFile": "Download Datei",
"downloadFolder": "Download Ordner",
"downloadSelected": ""
}, },
"errors": { "errors": {
"forbidden": "Sie haben keine Berechtigung dies abzurufen.", "forbidden": "Sie haben keine Berechtigung dies abzurufen.",
@@ -43,11 +45,11 @@
"notFound": "Dieser Ort kann nicht angezeigt werden." "notFound": "Dieser Ort kann nicht angezeigt werden."
}, },
"files": { "files": {
"folders": "Ordner",
"files": "Dateien",
"body": "Body", "body": "Body",
"clear": "Clear", "clear": "Clear",
"closePreview": "Vorschau schließen", "closePreview": "Vorschau schließen",
"files": "Dateien",
"folders": "Ordner",
"home": "Home", "home": "Home",
"lastModified": "zuletzt verändert", "lastModified": "zuletzt verändert",
"loading": "Lade...", "loading": "Lade...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "Mehrfachauswahl ausgewählt", "multipleSelectionEnabled": "Mehrfachauswahl ausgewählt",
"name": "Name", "name": "Name",
"size": "Größe", "size": "Größe",
"sortByLastModified": "Nach Änderungsdatum sortieren",
"sortByName": "Nach Namen sortieren", "sortByName": "Nach Namen sortieren",
"sortBySize": "Nach Größe sortieren", "sortBySize": "Nach Größe sortieren"
"sortByLastModified": "Nach Änderungsdatum sortieren"
}, },
"help": { "help": {
"click": "wähle Datei oder Ordner", "click": "wähle Datei oder Ordner",
@@ -74,18 +76,39 @@
"f2": "Datei umbenennen", "f2": "Datei umbenennen",
"help": "Hilfe" "help": "Hilfe"
}, },
"languages": {
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"svSE": "",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": { "login": {
"password": "Passwort",
"passwordConfirm": "Passwort Bestätigung",
"submit": "Login",
"createAnAccount": "Account erstellen", "createAnAccount": "Account erstellen",
"loginInstead": "Account besteht bereits", "loginInstead": "Account besteht bereits",
"password": "Passwort",
"passwordConfirm": "Passwort Bestätigung",
"passwordsDontMatch": "Passwörter stimmen nicht überein", "passwordsDontMatch": "Passwörter stimmen nicht überein",
"usernameTaken": "Benutzername ist bereits vergeben",
"signup": "Registrieren", "signup": "Registrieren",
"submit": "Login",
"username": "Benutzername", "username": "Benutzername",
"usernameTaken": "Benutzername ist bereits vergeben",
"wrongCredentials": "Falsche Zugangsdaten" "wrongCredentials": "Falsche Zugangsdaten"
}, },
"permanent": "Permanent",
"prompts": { "prompts": {
"copy": "Kopieren", "copy": "Kopieren",
"copyMessage": "Wählen Sie einen Zielort zum Kopieren:", "copyMessage": "Wählen Sie einen Zielort zum Kopieren:",
@@ -102,43 +125,64 @@
"lastModified": "Zuletzt geändert", "lastModified": "Zuletzt geändert",
"move": "Verschieben", "move": "Verschieben",
"moveMessage": "Wählen sie einen neuen Platz für ihre Datei(en)/Ordner:", "moveMessage": "Wählen sie einen neuen Platz für ihre Datei(en)/Ordner:",
"newArchetype": "Erstelle neuen Beitrag auf dem Archetyp. Ihre Datei wird im Inhalteordner erstellt.",
"newDir": "Neuer Ordner", "newDir": "Neuer Ordner",
"newDirMessage": "Geben Sie den Namen des neuen Ordners an.", "newDirMessage": "Geben Sie den Namen des neuen Ordners an.",
"newFile": "Neue Datei", "newFile": "Neue Datei",
"newFileMessage": "Geben Sie den Namen der neuen Datei an.", "newFileMessage": "Geben Sie den Namen der neuen Datei an.",
"numberDirs": "Anzahl der Ordner", "numberDirs": "Anzahl der Ordner",
"numberFiles": "Anzahl der Dateien", "numberFiles": "Anzahl der Dateien",
"replace": "Ersetzen",
"replaceMessage": "Eine der Datei mit dem gleichen Namen, wie die Sie hochladen wollen, existiert bereits. Soll die vorhandene Datei ersetzt werden ?\n",
"rename": "Umbenennen", "rename": "Umbenennen",
"renameMessage": "Fügen Sie einen Namen ein für", "renameMessage": "Fügen Sie einen Namen ein für",
"show": "Anzeigen", "replace": "Ersetzen",
"size": "Größe", "replaceMessage": "Eine der Datei mit dem gleichen Namen, wie die Sie hochladen wollen, existiert bereits. Soll die vorhandene Datei ersetzt werden ?\n",
"schedule": "Plan", "schedule": "Plan",
"scheduleMessage": "Wählen Sie ein Datum und eine Zeit für die Veröffentlichung dieses Beitrags.", "scheduleMessage": "Wählen Sie ein Datum und eine Zeit für die Veröffentlichung dieses Beitrags.",
"newArchetype": "Erstelle neuen Beitrag auf dem Archetyp. Ihre Datei wird im Inhalteordner erstellt." "show": "Anzeigen",
"size": "Größe",
"upload": "",
"uploadMessage": ""
},
"search": {
"images": "Bilder",
"music": "Musik",
"pdf": "PDF",
"pressToSearch": "Drücken sie Enter um zu suchen...",
"search": "Suche...",
"typeToSearch": "Tippe um zu suchen...",
"types": "Typen",
"video": "Video"
}, },
"settings": { "settings": {
"instanceName": "Instanzname",
"brandingDirectoryPath": "Markenverzeichnispfad",
"documentation": "Dokumentation",
"branding": "Marke",
"disableExternalLinks": "Externe Links deaktivieren (außer Dokumentation)",
"brandingHelp": "Sie können das Erscheinungsbild ihres File Browser anpassen, in dem sie den Namen ändern, das Logo austauchsen oder eigene Stile definieren und sogar externe Links zu GitHub deaktivieren.\nUm mehr Informationen zum Anpassen an ihre Marke zu bekommen, gehen sie bitte zu {0}.",
"admin": "Admin", "admin": "Admin",
"administrator": "Administrator", "administrator": "Administrator",
"allowCommands": "Befehle ausführen", "allowCommands": "Befehle ausführen",
"allowEdit": "Bearbeiten, Umbenennen und Löschen von Dateien oder Ordnern", "allowEdit": "Bearbeiten, Umbenennen und Löschen von Dateien oder Ordnern",
"allowNew": "Erstellen neuer Dateien und Ordner", "allowNew": "Erstellen neuer Dateien und Ordner",
"allowPublish": "Veröffentlichen von neuen Beiträgen und Seiten", "allowPublish": "Veröffentlichen von neuen Beiträgen und Seiten",
"allowSignup": "Erlaube Benutzern sich zu registrieren",
"avoidChanges": "(leer lassen um Änderungen zu vermeiden)", "avoidChanges": "(leer lassen um Änderungen zu vermeiden)",
"branding": "Marke",
"brandingDirectoryPath": "Markenverzeichnispfad",
"brandingHelp": "Sie können das Erscheinungsbild ihres File Browser anpassen, in dem sie den Namen ändern, das Logo austauchsen oder eigene Stile definieren und sogar externe Links zu GitHub deaktivieren.\nUm mehr Informationen zum Anpassen an ihre Marke zu bekommen, gehen sie bitte zu {0}.",
"changePassword": "Ändere das Passwort", "changePassword": "Ändere das Passwort",
"commandRunner": "Befehlseingabe", "commandRunner": "Befehlseingabe",
"commandRunnerHelp": "Hier könne sie Befehle eintragen die bei benannten Aktionen ausgeführt werden. Sie müssen pro Zeile jeweils einen eingeben. Die Umgebungsvariable {0} und {1} sind verfügbar, wobei {0} relative zu {1} ist. Für mehr Informationen über diese Funktion und die verfügbaren Umgebungsvariablen, lesen sie bitte das {2}.", "commandRunnerHelp": "Hier könne sie Befehle eintragen die bei benannten Aktionen ausgeführt werden. Sie müssen pro Zeile jeweils einen eingeben. Die Umgebungsvariable {0} und {1} sind verfügbar, wobei {0} relative zu {1} ist. Für mehr Informationen über diese Funktion und die verfügbaren Umgebungsvariablen, lesen sie bitte das {2}.",
"commandsUpdated": "Befehle aktualisiert!", "commandsUpdated": "Befehle aktualisiert!",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "Individuelles Stylesheet", "customStylesheet": "Individuelles Stylesheet",
"defaultUserDescription": "Das sind die Standard Einstellunge für Benutzer",
"disableExternalLinks": "Externe Links deaktivieren (außer Dokumentation)",
"documentation": "Dokumentation",
"examples": "Beispiele", "examples": "Beispiele",
"executeOnShell": "In shell ausführen",
"executeOnShellDescription": "Es ist voreingestellt das der File Brower Befehle ausführt in dem er die Befehlsdatein direkt auf ruft. Wenn sie wollen das sie auf einer Kommandozeile (wo Bash oder PowerShell) laufen, könne sie das hier definieren mit allen bennötigten Argumenten und Optionen. Wenn gesetzt, wird das Kommando das ausgeführt werden soll als Parameter angehängt. Das gilt für Benuzerkommandos sowie auch für Ereignisse.",
"globalRules": "Das ist ein globales Set von Regeln die erlauben oder nicht erlauben. Die sind für alle Benutzer zutreffend. Es können spezielle Regeln in den Einstellungen der Benutzer definiert werden die diese übersteuern.",
"globalSettings": "Globale Einstellungen", "globalSettings": "Globale Einstellungen",
"hideDotfiles": "",
"insertPath": "Pfad einfügen",
"insertRegex": "Regex Ausdruck einfügen",
"instanceName": "Instanzname",
"language": "Sprache", "language": "Sprache",
"lockPassword": "Verhindere, dass der Benutzer sein Passwort ändert", "lockPassword": "Verhindere, dass der Benutzer sein Passwort ändert",
"newPassword": "Ihr neues Passwort.", "newPassword": "Ihr neues Passwort.",
@@ -146,6 +190,16 @@
"newUser": "Neuer Benutzer", "newUser": "Neuer Benutzer",
"password": "Passwort", "password": "Passwort",
"passwordUpdated": "Passwort aktualisiert!", "passwordUpdated": "Passwort aktualisiert!",
"path": "",
"perm": {
"create": "Dateien und Ordner erstellen",
"delete": "Dateien und Ordner löschen",
"download": "Download",
"execute": "Befehle ausführen",
"modify": "Dateien editieren",
"rename": "Umbenennen oder Verschieben von Dateien oder Ordnern",
"share": "Datei teilen"
},
"permissions": "Berechtigungen", "permissions": "Berechtigungen",
"permissionsHelp": "Sie können einem Benutzer Administratorrechte einräumen oder die Berechtigunen individuell festlegen. Wenn Sie \"Administrator\" auswählen, werden alle anderen Rechte automatisch vergeben. Die Nutzerverwaltung kann nur durch einen Administrator erfolgen.\n", "permissionsHelp": "Sie können einem Benutzer Administratorrechte einräumen oder die Berechtigunen individuell festlegen. Wenn Sie \"Administrator\" auswählen, werden alle anderen Rechte automatisch vergeben. Die Nutzerverwaltung kann nur durch einen Administrator erfolgen.\n",
"profileSettings": "Profileinstellungen", "profileSettings": "Profileinstellungen",
@@ -155,82 +209,46 @@
"rulesHelp": "Hier können Sie erlaubte und verbotene Aktionen für einen einzelnen Benutzer festlegen. Bockierte Dateien werden nicht im Listing angezeigt und sind nicht erreichbar für den Nutzer. Wir unterstützen reguläre Ausdrücke (Regex) und Pfade die relativ zum Benutzerordner sind. \n", "rulesHelp": "Hier können Sie erlaubte und verbotene Aktionen für einen einzelnen Benutzer festlegen. Bockierte Dateien werden nicht im Listing angezeigt und sind nicht erreichbar für den Nutzer. Wir unterstützen reguläre Ausdrücke (Regex) und Pfade die relativ zum Benutzerordner sind. \n",
"scope": "Scope", "scope": "Scope",
"settingsUpdated": "Einstellungen aktualisiert!", "settingsUpdated": "Einstellungen aktualisiert!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "Benutzer", "user": "Benutzer",
"userCommands": "Befehle", "userCommands": "Befehle",
"userCommandsHelp": "Eine Liste, mit einem Leerzeichen als Trennung, mit den für diesen Nutzer verfügbaren Befehlen. Example:\n", "userCommandsHelp": "Eine Liste, mit einem Leerzeichen als Trennung, mit den für diesen Nutzer verfügbaren Befehlen. Example:\n",
"userCreated": "Benutzer angelegt!", "userCreated": "Benutzer angelegt!",
"userDefaults": "Benutzer Standard Einstellungen",
"userDeleted": "Benutzer gelöscht!", "userDeleted": "Benutzer gelöscht!",
"userManagement": "Benutzerverwaltung", "userManagement": "Benutzerverwaltung",
"username": "Nutzername",
"users": "Nutzer",
"globalRules": "Das ist ein globales Set von Regeln die erlauben oder nicht erlauben. Die sind für alle Benutzer zutreffend. Es können spezielle Regeln in den Einstellungen der Benutzer definiert werden die diese übersteuern.",
"allowSignup": "Erlaube Benutzern sich zu registrieren",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Regex Ausdruck einfügen",
"insertPath": "Pfad einfügen",
"userUpdated": "Benutzer aktualisiert!", "userUpdated": "Benutzer aktualisiert!",
"userDefaults": "Benutzer Standard Einstellungen", "username": "Nutzername",
"defaultUserDescription": "Das sind die Standard Einstellunge für Benutzer", "users": "Nutzer"
"executeOnShell": "In shell ausführen",
"executeOnShellDescription": "Es ist voreingestellt das der File Brower Befehle ausführt in dem er die Befehlsdatein direkt auf ruft. Wenn sie wollen das sie auf einer Kommandozeile (wo Bash oder PowerShell) laufen, könne sie das hier definieren mit allen bennötigten Argumenten und Optionen. Wenn gesetzt, wird das Kommando das ausgeführt werden soll als Parameter angehängt. Das gilt für Benuzerkommandos sowie auch für Ereignisse.",
"perm": {
"create": "Dateien und Ordner erstellen",
"delete": "Dateien und Ordner löschen",
"download": "Download",
"modify": "Dateien editieren",
"execute": "Befehle ausführen",
"rename": "Umbenennen oder Verschieben von Dateien oder Ordnern",
"share": "Datei teilen"
}
}, },
"sidebar": { "sidebar": {
"help": "Hilfe", "help": "Hilfe",
"hugoNew": "Hugo Neu",
"login": "Anmelden", "login": "Anmelden",
"signup": "Registrieren",
"logout": "Logout", "logout": "Logout",
"myFiles": "Meine Dateien", "myFiles": "Meine Dateien",
"newFile": "Neue Datei", "newFile": "Neue Datei",
"newFolder": "Neuer Ordner", "newFolder": "Neuer Ordner",
"preview": "Vorschau",
"settings": "Einstellungen", "settings": "Einstellungen",
"siteSettings": "Seiteneinstellungen", "signup": "Registrieren",
"hugoNew": "Hugo Neu", "siteSettings": "Seiteneinstellungen"
"preview": "Vorschau"
}, },
"search": { "success": {
"images": "Bilder", "linkCopied": "Verweis wurde kopiert!"
"music": "Musik",
"pdf": "PDF",
"types": "Typen",
"video": "Video",
"search": "Suche...",
"typeToSearch": "Tippe um zu suchen...",
"pressToSearch": "Drücken sie Enter um zu suchen..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
}, },
"time": { "time": {
"unit": "Zeiteinheit", "days": "Tage",
"seconds": "Sekunden",
"minutes": "Minuten",
"hours": "Stunden", "hours": "Stunden",
"days": "Tage" "minutes": "Minuten",
}, "seconds": "Sekunden",
"download": { "unit": "Zeiteinheit"
"downloadFile": "Download Datei",
"downloadFolder": "Download Ordner"
} }
} }

View File

@@ -1,7 +1,5 @@
{ {
"permanent": "Permanent",
"buttons": { "buttons": {
"shell": "Toggle shell",
"cancel": "Cancel", "cancel": "Cancel",
"close": "Close", "close": "Close",
"copy": "Copy", "copy": "Copy",
@@ -10,6 +8,7 @@
"create": "Create", "create": "Create",
"delete": "Delete", "delete": "Delete",
"download": "Download", "download": "Download",
"hideDotfiles": "Hide dotfiles",
"info": "Info", "info": "Info",
"more": "More", "more": "More",
"move": "Move", "move": "Move",
@@ -17,25 +16,29 @@
"new": "New", "new": "New",
"next": "Next", "next": "Next",
"ok": "OK", "ok": "OK",
"replace": "Replace", "permalink": "Get Permanent Link",
"previous": "Previous", "previous": "Previous",
"publish": "Publish",
"rename": "Rename", "rename": "Rename",
"replace": "Replace",
"reportIssue": "Report Issue", "reportIssue": "Report Issue",
"save": "Save", "save": "Save",
"schedule": "Schedule",
"search": "Search", "search": "Search",
"select": "Select", "select": "Select",
"share": "Share",
"publish": "Publish",
"selectMultiple": "Select multiple", "selectMultiple": "Select multiple",
"schedule": "Schedule", "share": "Share",
"shell": "Toggle shell",
"submit": "Submit",
"switchView": "Switch view", "switchView": "Switch view",
"toggleSidebar": "Toggle sidebar", "toggleSidebar": "Toggle sidebar",
"update": "Update", "update": "Update",
"upload": "Upload", "upload": "Upload"
"permalink": "Get Permanent Link"
}, },
"success": { "download": {
"linkCopied": "Link copied!" "downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": "Download Selected"
}, },
"errors": { "errors": {
"forbidden": "You don't have permissions to access this.", "forbidden": "You don't have permissions to access this.",
@@ -43,11 +46,11 @@
"notFound": "This location can't be reached." "notFound": "This location can't be reached."
}, },
"files": { "files": {
"folders": "Folders",
"files": "Files",
"body": "Body", "body": "Body",
"clear": "Clear", "clear": "Clear",
"closePreview": "Close preview", "closePreview": "Close preview",
"files": "Files",
"folders": "Folders",
"home": "Home", "home": "Home",
"lastModified": "Last modified", "lastModified": "Last modified",
"loading": "Loading...", "loading": "Loading...",
@@ -56,9 +59,9 @@
"multipleSelectionEnabled": "Multiple selection enabled", "multipleSelectionEnabled": "Multiple selection enabled",
"name": "Name", "name": "Name",
"size": "Size", "size": "Size",
"sortByLastModified": "Sort by last modified",
"sortByName": "Sort by name", "sortByName": "Sort by name",
"sortBySize": "Sort by size", "sortBySize": "Sort by size"
"sortByLastModified": "Sort by last modified"
}, },
"help": { "help": {
"click": "select file or directory", "click": "select file or directory",
@@ -74,24 +77,46 @@
"f2": "rename file", "f2": "rename file",
"help": "Help" "help": "Help"
}, },
"languages": {
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "Icelandic",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "Dutch (Belgium)",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "Romanian",
"ru": "Русский",
"svSE": "Swedish (Sweden)",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": { "login": {
"password": "Password",
"passwordConfirm": "Password Confirmation",
"submit": "Login",
"createAnAccount": "Create an account", "createAnAccount": "Create an account",
"loginInstead": "Already have an account", "loginInstead": "Already have an account",
"password": "Password",
"passwordConfirm": "Password Confirmation",
"passwordsDontMatch": "Passwords don't match", "passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup", "signup": "Signup",
"submit": "Login",
"username": "Username", "username": "Username",
"usernameTaken": "Username already taken",
"wrongCredentials": "Wrong credentials" "wrongCredentials": "Wrong credentials"
}, },
"permanent": "Permanent",
"prompts": { "prompts": {
"copy": "Copy", "copy": "Copy",
"copyMessage": "Choose the place to copy your files:", "copyMessage": "Choose the place to copy your files:",
"currentlyNavigating": "Currently navigating on:", "currentlyNavigating": "Currently navigating on:",
"deleteMessageMultiple": "Are you sure you want to delete {count} file(s)?", "deleteMessageMultiple": "Are you sure you want to delete {count} file(s)?",
"deleteMessageSingle": "Are you sure you want to delete this file/folder?", "deleteMessageSingle": "Are you sure you want to delete this file/folder?",
"deleteMessageShare": "Are you sure you want to delete this share({path})?",
"deleteTitle": "Delete files", "deleteTitle": "Delete files",
"displayName": "Display Name:", "displayName": "Display Name:",
"download": "Download files", "download": "Download files",
@@ -102,50 +127,65 @@
"lastModified": "Last Modified", "lastModified": "Last Modified",
"move": "Move", "move": "Move",
"moveMessage": "Choose new house for your file(s)/folder(s):", "moveMessage": "Choose new house for your file(s)/folder(s):",
"newArchetype": "Create a new post based on an archetype. Your file will be created on content folder.",
"newDir": "New directory", "newDir": "New directory",
"newDirMessage": "Write the name of the new directory.", "newDirMessage": "Write the name of the new directory.",
"newFile": "New file", "newFile": "New file",
"newFileMessage": "Write the name of the new file.", "newFileMessage": "Write the name of the new file.",
"numberDirs": "Number of directories", "numberDirs": "Number of directories",
"numberFiles": "Number of files", "numberFiles": "Number of files",
"replace": "Replace",
"replaceMessage": "One of the files you're trying to upload is conflicting because of its name. Do you wish to replace the existing one?\n",
"rename": "Rename", "rename": "Rename",
"renameMessage": "Insert a new name for", "renameMessage": "Insert a new name for",
"show": "Show", "replace": "Replace",
"size": "Size", "replaceMessage": "One of the files you're trying to upload is conflicting because of its name. Do you wish to replace the existing one?\n",
"schedule": "Schedule", "schedule": "Schedule",
"scheduleMessage": "Pick a date and time to schedule the publication of this post.", "scheduleMessage": "Pick a date and time to schedule the publication of this post.",
"newArchetype": "Create a new post based on an archetype. Your file will be created on content folder.", "show": "Show",
"size": "Size",
"upload": "Upload", "upload": "Upload",
"uploadMessage": "Select an option to upload." "uploadMessage": "Select an option to upload.",
"optionalPassword": "Optional password"
},
"search": {
"images": "Images",
"music": "Music",
"pdf": "PDF",
"pressToSearch": "Press enter to search...",
"search": "Search...",
"typeToSearch": "Type to search...",
"types": "Types",
"video": "Video"
}, },
"settings": { "settings": {
"themes": {
"title": "Theme",
"light": "Light",
"dark": "Dark"
},
"instanceName": "Instance name",
"brandingDirectoryPath": "Branding directory path",
"documentation": "documentation",
"branding": "Branding",
"disableExternalLinks": "Disable external links (except documentation)",
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"admin": "Admin", "admin": "Admin",
"administrator": "Administrator", "administrator": "Administrator",
"allowCommands": "Execute commands", "allowCommands": "Execute commands",
"allowEdit": "Edit, rename and delete files or directories", "allowEdit": "Edit, rename and delete files or directories",
"allowNew": "Create new files and directories", "allowNew": "Create new files and directories",
"allowPublish": "Publish new posts and pages", "allowPublish": "Publish new posts and pages",
"allowSignup": "Allow users to signup",
"avoidChanges": "(leave blank to avoid changes)", "avoidChanges": "(leave blank to avoid changes)",
"branding": "Branding",
"brandingDirectoryPath": "Branding directory path",
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"changePassword": "Change Password", "changePassword": "Change Password",
"commandRunner": "Command runner", "commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.", "commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "Commands updated!", "commandsUpdated": "Commands updated!",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "Custom Stylesheet", "customStylesheet": "Custom Stylesheet",
"defaultUserDescription": "This are the default settings for new users.",
"disableExternalLinks": "Disable external links (except documentation)",
"documentation": "documentation",
"examples": "Examples", "examples": "Examples",
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"globalSettings": "Global Settings", "globalSettings": "Global Settings",
"hideDotfiles": "Hide dotfiles",
"insertPath": "Insert the path",
"insertRegex": "Insert regex expression",
"instanceName": "Instance name",
"language": "Language", "language": "Language",
"lockPassword": "Prevent the user from changing the password", "lockPassword": "Prevent the user from changing the password",
"newPassword": "Your new password", "newPassword": "Your new password",
@@ -153,6 +193,16 @@
"newUser": "New User", "newUser": "New User",
"password": "Password", "password": "Password",
"passwordUpdated": "Password updated!", "passwordUpdated": "Password updated!",
"path": "Path",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"execute": "Execute commands",
"modify": "Edit files",
"rename": "Rename or move files and directories",
"share": "Share files"
},
"permissions": "Permissions", "permissions": "Permissions",
"permissionsHelp": "You can set the user to be an administrator or choose the permissions individually. If you select \"Administrator\", all of the other options will be automatically checked. The management of users remains a privilege of an administrator.\n", "permissionsHelp": "You can set the user to be an administrator or choose the permissions individually. If you select \"Administrator\", all of the other options will be automatically checked. The management of users remains a privilege of an administrator.\n",
"profileSettings": "Profile Settings", "profileSettings": "Profile Settings",
@@ -162,86 +212,46 @@
"rulesHelp": "Here you can define a set of allow and disallow rules for this specific user. The blocked files won't show up in the listings and they wont be accessible to the user. We support regex and paths relative to the users scope.\n", "rulesHelp": "Here you can define a set of allow and disallow rules for this specific user. The blocked files won't show up in the listings and they wont be accessible to the user. We support regex and paths relative to the users scope.\n",
"scope": "Scope", "scope": "Scope",
"settingsUpdated": "Settings updated!", "settingsUpdated": "Settings updated!",
"shareDuration": "Share Duration",
"shareManagement": "Share Management",
"singleClick": "Use single clicks to open files and directories",
"themes": {
"dark": "Dark",
"light": "Light",
"title": "Theme"
},
"user": "User", "user": "User",
"userCommands": "Commands", "userCommands": "Commands",
"userCommandsHelp": "A space separated list with the available commands for this user. Example:\n", "userCommandsHelp": "A space separated list with the available commands for this user. Example:\n",
"userCreated": "User created!", "userCreated": "User created!",
"userDefaults": "User default settings",
"userDeleted": "User deleted!", "userDeleted": "User deleted!",
"userManagement": "User Management", "userManagement": "User Management",
"username": "Username",
"users": "Users",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"allowSignup": "Allow users to signup",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Insert regex expression",
"insertPath": "Insert the path",
"userUpdated": "User updated!", "userUpdated": "User updated!",
"userDefaults": "User default settings", "username": "Username",
"defaultUserDescription": "This are the default settings for new users.", "users": "Users"
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"modify": "Edit files",
"execute": "Execute commands",
"rename": "Rename or move files and directories",
"share": "Share files"
}
}, },
"sidebar": { "sidebar": {
"help": "Help", "help": "Help",
"hugoNew": "Hugo New",
"login": "Login", "login": "Login",
"signup": "Signup",
"logout": "Logout", "logout": "Logout",
"myFiles": "My files", "myFiles": "My files",
"newFile": "New file", "newFile": "New file",
"newFolder": "New folder", "newFolder": "New folder",
"preview": "Preview",
"settings": "Settings", "settings": "Settings",
"siteSettings": "Site Settings", "signup": "Signup",
"hugoNew": "Hugo New", "siteSettings": "Site Settings"
"preview": "Preview"
}, },
"search": { "success": {
"images": "Images", "linkCopied": "Link copied!"
"music": "Music",
"pdf": "PDF",
"types": "Types",
"video": "Video",
"search": "Search...",
"typeToSearch": "Type to search...",
"pressToSearch": "Press enter to search..."
},
"languages": {
"ar": "العربية",
"en": "English",
"is": "Icelandic",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어",
"nlBE": "Dutch (Belgium)",
"ro": "Romanian",
"svSE": "Swedish (Sweden)"
}, },
"time": { "time": {
"unit": "Time Unit", "days": "Days",
"seconds": "Seconds",
"minutes": "Minutes",
"hours": "Hours", "hours": "Hours",
"days": "Days" "minutes": "Minutes",
}, "seconds": "Seconds",
"download": { "unit": "Time Unit"
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
} }
} }

View File

@@ -1,7 +1,5 @@
{ {
"permanent": "Permanente",
"buttons": { "buttons": {
"shell": "Presiona Enter para buscar...",
"cancel": "Cancelar", "cancel": "Cancelar",
"close": "Cerrar", "close": "Cerrar",
"copy": "Copiar", "copy": "Copiar",
@@ -10,6 +8,7 @@
"create": "Crear", "create": "Crear",
"delete": "Borrar", "delete": "Borrar",
"download": "Descargar", "download": "Descargar",
"hideDotfiles": "",
"info": "Info", "info": "Info",
"more": "Más", "more": "Más",
"move": "Mover", "move": "Mover",
@@ -17,25 +16,28 @@
"new": "Nuevo", "new": "Nuevo",
"next": "Siguiente", "next": "Siguiente",
"ok": "OK", "ok": "OK",
"replace": "Reemplazar", "permalink": "Link permanente",
"previous": "Anterior", "previous": "Anterior",
"publish": "Publicar",
"rename": "Renombrar", "rename": "Renombrar",
"replace": "Reemplazar",
"reportIssue": "Reportar problema", "reportIssue": "Reportar problema",
"save": "Guardar", "save": "Guardar",
"schedule": "Programar",
"search": "Buscar", "search": "Buscar",
"select": "Seleccionar", "select": "Seleccionar",
"share": "Compartir",
"publish": "Publicar",
"selectMultiple": "Selección múltiple", "selectMultiple": "Selección múltiple",
"schedule": "Programar", "share": "Compartir",
"shell": "Presiona Enter para buscar...",
"switchView": "Cambiar vista", "switchView": "Cambiar vista",
"toggleSidebar": "Mostrar/Ocultar menú", "toggleSidebar": "Mostrar/Ocultar menú",
"update": "Actualizar", "update": "Actualizar",
"upload": "Subir", "upload": "Subir"
"permalink": "Link permanente"
}, },
"success": { "download": {
"linkCopied": "¡Link copiado!" "downloadFile": "Descargar fichero",
"downloadFolder": "Descargar directorio",
"downloadSelected": ""
}, },
"errors": { "errors": {
"forbidden": "No tienes los permisos necesarios para acceder.", "forbidden": "No tienes los permisos necesarios para acceder.",
@@ -43,11 +45,11 @@
"notFound": "No se puede acceder a este lugar." "notFound": "No se puede acceder a este lugar."
}, },
"files": { "files": {
"folders": "Carpetas",
"files": "Archivos",
"body": "Cuerpo", "body": "Cuerpo",
"clear": "Limpiar", "clear": "Limpiar",
"closePreview": "Cerrar vista previa", "closePreview": "Cerrar vista previa",
"files": "Archivos",
"folders": "Carpetas",
"home": "Inicio", "home": "Inicio",
"lastModified": "Última modificación", "lastModified": "Última modificación",
"loading": "Cargando...", "loading": "Cargando...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "Selección múltiple activada", "multipleSelectionEnabled": "Selección múltiple activada",
"name": "Nombre", "name": "Nombre",
"size": "Tamaño", "size": "Tamaño",
"sortByLastModified": "Ordenar por última modificación",
"sortByName": "Ordenar por nombre", "sortByName": "Ordenar por nombre",
"sortBySize": "Ordenar por tamaño", "sortBySize": "Ordenar por tamaño"
"sortByLastModified": "Ordenar por última modificación"
}, },
"help": { "help": {
"click": "seleccionar archivo o carpeta", "click": "seleccionar archivo o carpeta",
@@ -74,18 +76,39 @@
"f2": "renombrar archivo", "f2": "renombrar archivo",
"help": "Ayuda" "help": "Ayuda"
}, },
"languages": {
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"svSE": "",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": { "login": {
"password": "Contraseña",
"passwordConfirm": "Confirmación de contraseña",
"submit": "Iniciar sesión",
"createAnAccount": "Crear una cuenta", "createAnAccount": "Crear una cuenta",
"loginInstead": "Usuario ya existente", "loginInstead": "Usuario ya existente",
"password": "Contraseña",
"passwordConfirm": "Confirmación de contraseña",
"passwordsDontMatch": "Las contraseñas no coinciden", "passwordsDontMatch": "Las contraseñas no coinciden",
"usernameTaken": "Nombre usuario no disponible",
"signup": "Registrate", "signup": "Registrate",
"submit": "Iniciar sesión",
"username": "Usuario", "username": "Usuario",
"usernameTaken": "Nombre usuario no disponible",
"wrongCredentials": "Usuario y/o contraseña incorrectos" "wrongCredentials": "Usuario y/o contraseña incorrectos"
}, },
"permanent": "Permanente",
"prompts": { "prompts": {
"copy": "Copiar", "copy": "Copiar",
"copyMessage": "Elige el lugar donde quieres copiar tus archivos:", "copyMessage": "Elige el lugar donde quieres copiar tus archivos:",
@@ -102,43 +125,64 @@
"lastModified": "Última modificación", "lastModified": "Última modificación",
"move": "Mover", "move": "Mover",
"moveMessage": "Elige una nueva casa para tus archivo(s)/carpeta(s):", "moveMessage": "Elige una nueva casa para tus archivo(s)/carpeta(s):",
"newArchetype": "Crea un nuevo post basado en un arquetipo. Tu archivo será creado en la carpeta de contenido.",
"newDir": "Nueva carpeta", "newDir": "Nueva carpeta",
"newDirMessage": "Escribe el nombre de la nueva carpeta.", "newDirMessage": "Escribe el nombre de la nueva carpeta.",
"newFile": "Nuevo archivo", "newFile": "Nuevo archivo",
"newFileMessage": "Escribe el nombre del nuevo archivo.", "newFileMessage": "Escribe el nombre del nuevo archivo.",
"numberDirs": "Número de carpetas", "numberDirs": "Número de carpetas",
"numberFiles": "Número de archivos", "numberFiles": "Número de archivos",
"replace": "Reemplazar",
"replaceMessage": "Uno de los archivos ue intentas subir está creando conflicto por su nombre. ¿Quieres cambiar el nombre del ya existente?\n",
"rename": "Renombrar", "rename": "Renombrar",
"renameMessage": "Escribe el nuevo nombre para", "renameMessage": "Escribe el nuevo nombre para",
"show": "Mostrar", "replace": "Reemplazar",
"size": "Tamaño", "replaceMessage": "Uno de los archivos ue intentas subir está creando conflicto por su nombre. ¿Quieres cambiar el nombre del ya existente?\n",
"schedule": "Programar", "schedule": "Programar",
"scheduleMessage": "Elige una hora y fecha para programar la publicación de este post.", "scheduleMessage": "Elige una hora y fecha para programar la publicación de este post.",
"newArchetype": "Crea un nuevo post basado en un arquetipo. Tu archivo será creado en la carpeta de contenido." "show": "Mostrar",
"size": "Tamaño",
"upload": "",
"uploadMessage": ""
},
"search": {
"images": "Images",
"music": "Música",
"pdf": "PDF",
"pressToSearch": "Presiona enter para buscar...",
"search": "Buscar...",
"typeToSearch": "Escribe para realizar una busqueda...",
"types": "Tipos",
"video": "Vídeo"
}, },
"settings": { "settings": {
"instanceName": "Nombre de la instancia",
"brandingDirectoryPath": "Ruta de la carpeta de personalizacion de marca",
"documentation": "documentación",
"branding": "Marca",
"disableExternalLinks": "Deshabilitar enlaces externos (excepto documentación)",
"brandingHelp": "Tú puedes personalizar como se ve tu instancia de FileBrowser cambiándole el nombre, reemplazando ellogo, agregar estilos personalizados e incluso deshabilitando loslinks externos que apuntan hacia GitHub. \nPara mayor información acerca de personalización de marca, por favor revisa el {0}.",
"admin": "Admin", "admin": "Admin",
"administrator": "Administrador", "administrator": "Administrador",
"allowCommands": "Ejecutar comandos", "allowCommands": "Ejecutar comandos",
"allowEdit": "Editar, renombrar y borrar archivos o carpetas", "allowEdit": "Editar, renombrar y borrar archivos o carpetas",
"allowNew": "Crear nuevos archivos y carpetas", "allowNew": "Crear nuevos archivos y carpetas",
"allowPublish": "Publicar nuevos posts y páginas", "allowPublish": "Publicar nuevos posts y páginas",
"allowSignup": "Permitir registro de usuarios",
"avoidChanges": "(dejar en blanco para evitar cambios)", "avoidChanges": "(dejar en blanco para evitar cambios)",
"branding": "Marca",
"brandingDirectoryPath": "Ruta de la carpeta de personalizacion de marca",
"brandingHelp": "Tú puedes personalizar como se ve tu instancia de FileBrowser cambiándole el nombre, reemplazando ellogo, agregar estilos personalizados e incluso deshabilitando loslinks externos que apuntan hacia GitHub. \nPara mayor información acerca de personalización de marca, por favor revisa el {0}.",
"changePassword": "Cambiar contraseña", "changePassword": "Cambiar contraseña",
"commandRunner": "Executor de comandos", "commandRunner": "Executor de comandos",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.", "commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "¡Comandos actualizados!", "commandsUpdated": "¡Comandos actualizados!",
"createUserDir": "Crea automaticamente una carpeta de inicio cuando se agrega un usuario",
"customStylesheet": "Modificar hoja de estilos", "customStylesheet": "Modificar hoja de estilos",
"defaultUserDescription": "Estas son las configuraciones por defecto para nuevos usuarios.",
"disableExternalLinks": "Deshabilitar enlaces externos (excepto documentación)",
"documentation": "documentación",
"examples": "Ejemplos", "examples": "Ejemplos",
"executeOnShell": "Ejecutar en la shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"globalSettings": "Ajustes globales", "globalSettings": "Ajustes globales",
"hideDotfiles": "",
"insertPath": "Introduce la ruta",
"insertRegex": "Introducir expresión regular",
"instanceName": "Nombre de la instancia",
"language": "Idioma", "language": "Idioma",
"lockPassword": "Evitar que el usuario cambie la contraseña", "lockPassword": "Evitar que el usuario cambie la contraseña",
"newPassword": "Tu nueva contraseña", "newPassword": "Tu nueva contraseña",
@@ -146,6 +190,16 @@
"newUser": "Nuevo usuario", "newUser": "Nuevo usuario",
"password": "Contraseña", "password": "Contraseña",
"passwordUpdated": "¡Contraseña actualizada!", "passwordUpdated": "¡Contraseña actualizada!",
"path": "",
"perm": {
"create": "Crear ficheros y directorios",
"delete": "Eliminar ficheros y directorios",
"download": "Descargar",
"execute": "Executar comandos",
"modify": "Editar ficheros",
"rename": "Renombrar o mover ficheros y directorios",
"share": "Compartir ficheros"
},
"permissions": "Permisos", "permissions": "Permisos",
"permissionsHelp": "Puedes nombrar al usuario como administrador o elegir los permisos individualmente. Si seleccionas \"Administrador\", todas las otras opciones serán activadas automáticamente. La administración de usuarios es un privilegio de administrador.\n", "permissionsHelp": "Puedes nombrar al usuario como administrador o elegir los permisos individualmente. Si seleccionas \"Administrador\", todas las otras opciones serán activadas automáticamente. La administración de usuarios es un privilegio de administrador.\n",
"profileSettings": "Ajustes del perfil", "profileSettings": "Ajustes del perfil",
@@ -155,82 +209,46 @@
"rulesHelp": "Aquí puedes definir un conjunto de reglas de permisos para este usuario específico. Los archivos bloqueados no se mostrarán en las listas y no serán accesibles por el usuario. Puedes utilizar regex y rutas relativas a la raíz del usuario.\n", "rulesHelp": "Aquí puedes definir un conjunto de reglas de permisos para este usuario específico. Los archivos bloqueados no se mostrarán en las listas y no serán accesibles por el usuario. Puedes utilizar regex y rutas relativas a la raíz del usuario.\n",
"scope": "Raíz", "scope": "Raíz",
"settingsUpdated": "¡Ajustes actualizados!", "settingsUpdated": "¡Ajustes actualizados!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "Usuario", "user": "Usuario",
"userCommands": "Comandos", "userCommands": "Comandos",
"userCommandsHelp": "Una lista separada por espacios con los comandos permitidos para este usuario. Ejemplo:\n", "userCommandsHelp": "Una lista separada por espacios con los comandos permitidos para este usuario. Ejemplo:\n",
"userCreated": "¡Usuario creado!", "userCreated": "¡Usuario creado!",
"userDefaults": "Configuración de usuario por defecto",
"userDeleted": "¡Usuario eliminado!", "userDeleted": "¡Usuario eliminado!",
"userManagement": "Administración de usuarios", "userManagement": "Administración de usuarios",
"username": "Usuario",
"users": "Usuarios",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"allowSignup": "Permitir registro de usuarios",
"createUserDir": "Crea automaticamente una carpeta de inicio cuando se agrega un usuario",
"insertRegex": "Introducir expresión regular",
"insertPath": "Introduce la ruta",
"userUpdated": "¡Usuario actualizado!", "userUpdated": "¡Usuario actualizado!",
"userDefaults": "Configuración de usuario por defecto", "username": "Usuario",
"defaultUserDescription": "Estas son las configuraciones por defecto para nuevos usuarios.", "users": "Usuarios"
"executeOnShell": "Ejecutar en la shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"perm": {
"create": "Crear ficheros y directorios",
"delete": "Eliminar ficheros y directorios",
"download": "Descargar",
"modify": "Editar ficheros",
"execute": "Executar comandos",
"rename": "Renombrar o mover ficheros y directorios",
"share": "Compartir ficheros"
}
}, },
"sidebar": { "sidebar": {
"help": "Ayuda", "help": "Ayuda",
"hugoNew": "Nuevo Hugo",
"login": "Iniciar sesión", "login": "Iniciar sesión",
"signup": "Registrate",
"logout": "Cerrar sesión", "logout": "Cerrar sesión",
"myFiles": "Mis archivos", "myFiles": "Mis archivos",
"newFile": "Nuevo archivo", "newFile": "Nuevo archivo",
"newFolder": "Nueva carpeta", "newFolder": "Nueva carpeta",
"preview": "Vista previa",
"settings": "Ajustes", "settings": "Ajustes",
"siteSettings": "Ajustes del sitio", "signup": "Registrate",
"hugoNew": "Nuevo Hugo", "siteSettings": "Ajustes del sitio"
"preview": "Vista previa"
}, },
"search": { "success": {
"images": "Images", "linkCopied": "¡Link copiado!"
"music": "Música",
"pdf": "PDF",
"types": "Tipos",
"video": "Vídeo",
"search": "Buscar...",
"typeToSearch": "Escribe para realizar una busqueda...",
"pressToSearch": "Presiona enter para buscar..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
}, },
"time": { "time": {
"unit": "Unidad", "days": "Días",
"seconds": "Segundos",
"minutes": "Minutos",
"hours": "Horas", "hours": "Horas",
"days": "Días" "minutes": "Minutos",
}, "seconds": "Segundos",
"download": { "unit": "Unidad"
"downloadFile": "Descargar fichero",
"downloadFolder": "Descargar directorio"
} }
} }

View File

@@ -1,7 +1,5 @@
{ {
"permanent": "Permanent",
"buttons": { "buttons": {
"shell": "Toggle shell",
"cancel": "Annuler", "cancel": "Annuler",
"close": "Fermer", "close": "Fermer",
"copy": "Copier", "copy": "Copier",
@@ -10,6 +8,7 @@
"create": "Créer", "create": "Créer",
"delete": "Supprimer", "delete": "Supprimer",
"download": "Télécharger", "download": "Télécharger",
"hideDotfiles": "",
"info": "Info", "info": "Info",
"more": "Plus", "more": "Plus",
"move": "Déplacer", "move": "Déplacer",
@@ -17,25 +16,28 @@
"new": "Nouveau", "new": "Nouveau",
"next": "Suivant", "next": "Suivant",
"ok": "OK", "ok": "OK",
"replace": "Remplacer", "permalink": "Obtenir un lien permanent",
"previous": "Précédent", "previous": "Précédent",
"publish": "Publier",
"rename": "Renommer", "rename": "Renommer",
"replace": "Remplacer",
"reportIssue": "Rapport d'erreur", "reportIssue": "Rapport d'erreur",
"save": "Enregistrer", "save": "Enregistrer",
"schedule": "Fixer la date",
"search": "Chercher", "search": "Chercher",
"select": "Sélectionner", "select": "Sélectionner",
"share": "Partager",
"publish": "Publier",
"selectMultiple": "Sélection multiple", "selectMultiple": "Sélection multiple",
"schedule": "Fixer la date", "share": "Partager",
"shell": "Toggle shell",
"switchView": "Changer le mode d'affichage", "switchView": "Changer le mode d'affichage",
"toggleSidebar": "Afficher/Masquer la barre latérale", "toggleSidebar": "Afficher/Masquer la barre latérale",
"update": "Mettre à jour", "update": "Mettre à jour",
"upload": "Importer", "upload": "Importer"
"permalink": "Obtenir un lien permanent"
}, },
"success": { "download": {
"linkCopied": "Link copied!" "downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": ""
}, },
"errors": { "errors": {
"forbidden": "You don't have permissions to access this.", "forbidden": "You don't have permissions to access this.",
@@ -43,22 +45,22 @@
"notFound": "Impossible d'accéder à cet emplacement." "notFound": "Impossible d'accéder à cet emplacement."
}, },
"files": { "files": {
"folders": "Dossiers",
"files": "Fichiers",
"body": "Corps", "body": "Corps",
"clear": "Fermer", "clear": "Fermer",
"closePreview": "Fermer la prévisualisation", "closePreview": "Fermer la prévisualisation",
"files": "Fichiers",
"folders": "Dossiers",
"home": "Accueil", "home": "Accueil",
"lastModified": "Dernière modification", "lastModified": "Dernière modification",
"loading": "Chargement...", "loading": "Chargement...",
"lonely": "Il semble qu'il n'y ai rien par ici...", "lonely": "Il semble qu'il n'y ait rien par ici...",
"metadata": "Metadonnées", "metadata": "Metadonnées",
"multipleSelectionEnabled": "Sélection multiple activée", "multipleSelectionEnabled": "Sélection multiple activée",
"name": "Nom", "name": "Nom",
"size": "Taille", "size": "Taille",
"sortByLastModified": "Trier par date de dernière modification",
"sortByName": "Trier par nom", "sortByName": "Trier par nom",
"sortBySize": "Trier par taille", "sortBySize": "Trier par taille"
"sortByLastModified": "Trier par date de dernière modification"
}, },
"help": { "help": {
"click": "Sélectionner un élément", "click": "Sélectionner un élément",
@@ -74,18 +76,39 @@
"f2": "Renommer le fichier", "f2": "Renommer le fichier",
"help": "Aide" "help": "Aide"
}, },
"languages": {
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"svSE": "",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": { "login": {
"password": "Mot de passe",
"passwordConfirm": "Password Confirmation",
"submit": "Se connecter",
"createAnAccount": "Create an account", "createAnAccount": "Create an account",
"loginInstead": "Already have an account", "loginInstead": "Already have an account",
"password": "Mot de passe",
"passwordConfirm": "Password Confirmation",
"passwordsDontMatch": "Passwords don't match", "passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup", "signup": "Signup",
"submit": "Se connecter",
"username": "Utilisateur", "username": "Utilisateur",
"usernameTaken": "Username already taken",
"wrongCredentials": "Identifiants incorrects !" "wrongCredentials": "Identifiants incorrects !"
}, },
"permanent": "Permanent",
"prompts": { "prompts": {
"copy": "Copier", "copy": "Copier",
"copyMessage": "Choisissez l'emplacement où copier la sélection :", "copyMessage": "Choisissez l'emplacement où copier la sélection :",
@@ -102,43 +125,64 @@
"lastModified": "Dernière modification", "lastModified": "Dernière modification",
"move": "Déplacer", "move": "Déplacer",
"moveMessage": "Choisissez l'emplacement où déplacer la sélection :", "moveMessage": "Choisissez l'emplacement où déplacer la sélection :",
"newArchetype": "Créer un nouveau post basé sur un archétype. Votre fichier sera créé dans le dossier de contenu.",
"newDir": "Nouveau dossier", "newDir": "Nouveau dossier",
"newDirMessage": "Nom du nouveau dossier :", "newDirMessage": "Nom du nouveau dossier :",
"newFile": "Nouveau fichier", "newFile": "Nouveau fichier",
"newFileMessage": "Nom du nouveau fichier :", "newFileMessage": "Nom du nouveau fichier :",
"numberDirs": "Nombre de dossiers", "numberDirs": "Nombre de dossiers",
"numberFiles": "Nombre de fichiers", "numberFiles": "Nombre de fichiers",
"replace": "Remplacer",
"replaceMessage": "Un des fichiers que vous êtes en train d'importer a le même nom qu'un autre déjà présent. Voulez-vous remplacer le fichier actuel par le nouveau ?\n",
"rename": "Renommer", "rename": "Renommer",
"renameMessage": "Nouveau nom pour", "renameMessage": "Nouveau nom pour",
"show": "Montrer", "replace": "Remplacer",
"size": "Taille", "replaceMessage": "Un des fichiers que vous êtes en train d'importer a le même nom qu'un autre déjà présent. Voulez-vous remplacer le fichier actuel par le nouveau ?\n",
"schedule": "Fixer la date", "schedule": "Fixer la date",
"scheduleMessage": "Choisissez une date pour planifier la publication de ce post", "scheduleMessage": "Choisissez une date pour planifier la publication de ce post",
"newArchetype": "Créer un nouveau post basé sur un archétype. Votre fichier sera créé dans le dossier de contenu." "show": "Montrer",
"size": "Taille",
"upload": "",
"uploadMessage": ""
},
"search": {
"images": "Images",
"music": "Musique",
"pdf": "PDF",
"pressToSearch": "Press enter to search...",
"search": "Recherche en cours...",
"typeToSearch": "Type to search...",
"types": "Types",
"video": "Video"
}, },
"settings": { "settings": {
"instanceName": "Instance name",
"brandingDirectoryPath": "Branding directory path",
"documentation": "documentation",
"branding": "Branding",
"disableExternalLinks": "Disable external links (except documentation)",
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"admin": "Admin", "admin": "Admin",
"administrator": "Administrateur", "administrator": "Administrateur",
"allowCommands": "Exécuter des commandes", "allowCommands": "Exécuter des commandes",
"allowEdit": "Editer, renommer et supprimer des fichiers ou des dossiers", "allowEdit": "Editer, renommer et supprimer des fichiers ou des dossiers",
"allowNew": "Créer de nouveaux fichiers et dossiers", "allowNew": "Créer de nouveaux fichiers et dossiers",
"allowPublish": "Publier de nouveaux posts et pages", "allowPublish": "Publier de nouveaux posts et pages",
"allowSignup": "Allow users to signup",
"avoidChanges": "(Laisser vide pour conserver l'actuel)", "avoidChanges": "(Laisser vide pour conserver l'actuel)",
"branding": "Branding",
"brandingDirectoryPath": "Branding directory path",
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"changePassword": "Modifier le mot de passe", "changePassword": "Modifier le mot de passe",
"commandRunner": "Command runner", "commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.", "commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "Commandes mises à jour !", "commandsUpdated": "Commandes mises à jour !",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "Feuille de style personnalisée", "customStylesheet": "Feuille de style personnalisée",
"defaultUserDescription": "This are the default settings for new users.",
"disableExternalLinks": "Disable external links (except documentation)",
"documentation": "documentation",
"examples": "Exemples", "examples": "Exemples",
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"globalSettings": "Paramètres généraux", "globalSettings": "Paramètres généraux",
"hideDotfiles": "",
"insertPath": "Insert the path",
"insertRegex": "Insert regex expression",
"instanceName": "Instance name",
"language": "Langue", "language": "Langue",
"lockPassword": "Prevent the user from changing the password", "lockPassword": "Prevent the user from changing the password",
"newPassword": "Votre nouveau mot de passe", "newPassword": "Votre nouveau mot de passe",
@@ -146,6 +190,16 @@
"newUser": "Nouvel Utilisateur", "newUser": "Nouvel Utilisateur",
"password": "Mot de passe", "password": "Mot de passe",
"passwordUpdated": "Mot de passe mis à jour !", "passwordUpdated": "Mot de passe mis à jour !",
"path": "",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"execute": "Execute commands",
"modify": "Edit files",
"rename": "Rename or move files and directories",
"share": "Share files"
},
"permissions": "Permissions", "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 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",
"profileSettings": "Paramètres du profil", "profileSettings": "Paramètres du profil",
@@ -155,82 +209,46 @@
"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", "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", "scope": "Portée du dossier utilisateur",
"settingsUpdated": "Les paramètres ont été mis à jour !", "settingsUpdated": "Les paramètres ont été mis à jour !",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "Utilisateur", "user": "Utilisateur",
"userCommands": "Commandes", "userCommands": "Commandes",
"userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur. Exemple :", "userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur. Exemple :",
"userCreated": "Utilisateur créé !", "userCreated": "Utilisateur créé !",
"userDefaults": "User default settings",
"userDeleted": "Utilisateur supprimé !", "userDeleted": "Utilisateur supprimé !",
"userManagement": "Gestion des utilisateurs", "userManagement": "Gestion des utilisateurs",
"username": "Nom d'utilisateur",
"users": "Utilisateurs",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"allowSignup": "Allow users to signup",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Insert regex expression",
"insertPath": "Insert the path",
"userUpdated": "Utilisateur mis à jour !", "userUpdated": "Utilisateur mis à jour !",
"userDefaults": "User default settings", "username": "Nom d'utilisateur",
"defaultUserDescription": "This are the default settings for new users.", "users": "Utilisateurs"
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"modify": "Edit files",
"execute": "Execute commands",
"rename": "Rename or move files and directories",
"share": "Share files"
}
}, },
"sidebar": { "sidebar": {
"help": "Aide", "help": "Aide",
"hugoNew": "Nouveau Hugo",
"login": "Login", "login": "Login",
"signup": "Signup",
"logout": "Se déconnecter", "logout": "Se déconnecter",
"myFiles": "Mes fichiers", "myFiles": "Mes fichiers",
"newFile": "Nouveau fichier", "newFile": "Nouveau fichier",
"newFolder": "Nouveau dossier", "newFolder": "Nouveau dossier",
"preview": "Prévisualiser",
"settings": "Paramètres", "settings": "Paramètres",
"siteSettings": "Paramètres du site", "signup": "Signup",
"hugoNew": "Nouveau Hugo", "siteSettings": "Paramètres du site"
"preview": "Prévisualiser"
}, },
"search": { "success": {
"images": "Images", "linkCopied": "Link copied!"
"music": "Musique",
"pdf": "PDF",
"types": "Types",
"video": "Video",
"search": "Recherche en cours...",
"typeToSearch": "Type to search...",
"pressToSearch": "Press enter to search..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
}, },
"time": { "time": {
"unit": "Unité de temps", "days": "Jours",
"seconds": "Secondes",
"minutes": "Minutes",
"hours": "Heures", "hours": "Heures",
"days": "Jours" "minutes": "Minutes",
}, "seconds": "Secondes",
"download": { "unit": "Unité de temps"
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
} }
} }

View File

@@ -77,28 +77,39 @@ export function detectLocale () {
return locale return locale
} }
const removeEmpty = (obj) =>
Object.keys(obj)
.filter((k) => obj[k] !== null && obj[k] !== undefined && obj[k] !== '') // Remove undef. and null and empty.string.
.reduce(
(newObj, k) =>
typeof obj[k] === 'object'
? Object.assign(newObj, { [k]: removeEmpty(obj[k]) }) // Recurse.
: Object.assign(newObj, { [k]: obj[k] }), // Copy value.
{},
);
const i18n = new VueI18n({ const i18n = new VueI18n({
locale: detectLocale(), locale: detectLocale(),
fallbackLocale: 'en', fallbackLocale: 'en',
messages: { messages: {
'ar': ar, 'ar': removeEmpty(ar),
'de': de, 'de': removeEmpty(de),
'en': en, 'en': en,
'es': es, 'es': removeEmpty(es),
'fr': fr, 'fr': removeEmpty(fr),
'is': is, 'is': removeEmpty(is),
'it': it, 'it': removeEmpty(it),
'ja': ja, 'ja': removeEmpty(ja),
'ko': ko, 'ko': removeEmpty(ko),
'nl-be': nlBE, 'nl-be': removeEmpty(nlBE),
'pl': pl, 'pl': removeEmpty(pl),
'pt-br': ptBR, 'pt-br': removeEmpty(ptBR),
'pt': pt, 'pt': removeEmpty(pt),
'ru': ru, 'ru': removeEmpty(ru),
'ro': ro, 'ro': removeEmpty(ro),
'sv-se': svSE, 'sv-se': removeEmpty(svSE),
'zh-cn': zhCN, 'zh-cn': removeEmpty(zhCN),
'zh-tw': zhTW 'zh-tw': removeEmpty(zhTW)
} }
}) })

View File

@@ -1,7 +1,5 @@
{ {
"permanent": "Varanlegt",
"buttons": { "buttons": {
"shell": "Sýna skipanaglugga",
"cancel": "Hætta við", "cancel": "Hætta við",
"close": "Loka", "close": "Loka",
"copy": "Afrita", "copy": "Afrita",
@@ -10,6 +8,7 @@
"create": "Búa til", "create": "Búa til",
"delete": "Eyða", "delete": "Eyða",
"download": "Sækja", "download": "Sækja",
"hideDotfiles": "",
"info": "Upplýsingar", "info": "Upplýsingar",
"more": "Meira", "more": "Meira",
"move": "Færa", "move": "Færa",
@@ -17,25 +16,28 @@
"new": "Nýtt", "new": "Nýtt",
"next": "Næsta", "next": "Næsta",
"ok": "OK", "ok": "OK",
"replace": "Skipta út", "permalink": "Sækja fastan hlekk",
"previous": "Fyrri", "previous": "Fyrri",
"publish": "Gefa út",
"rename": "Endurnefna", "rename": "Endurnefna",
"replace": "Skipta út",
"reportIssue": "Tilkynna vandamál", "reportIssue": "Tilkynna vandamál",
"save": "Vista", "save": "Vista",
"schedule": "Áætlun",
"search": "Leita", "search": "Leita",
"select": "Velja", "select": "Velja",
"share": "Deila",
"publish": "Gefa út",
"selectMultiple": "Velja mörg", "selectMultiple": "Velja mörg",
"schedule": "Áætlun", "share": "Deila",
"shell": "Sýna skipanaglugga",
"switchView": "Skipta um útlit", "switchView": "Skipta um útlit",
"toggleSidebar": "Sýna hliðarstiku", "toggleSidebar": "Sýna hliðarstiku",
"update": "Vista", "update": "Vista",
"upload": "Hlaða upp", "upload": "Hlaða upp"
"permalink": "Sækja fastan hlekk"
}, },
"success": { "download": {
"linkCopied": "Hlekkur afritaður!" "downloadFile": "Sækja skjal",
"downloadFolder": "Sækja möppu",
"downloadSelected": ""
}, },
"errors": { "errors": {
"forbidden": "Þú hefur ekki aðgang að þessari síðu.", "forbidden": "Þú hefur ekki aðgang að þessari síðu.",
@@ -43,11 +45,11 @@
"notFound": "Ekki er hægt að opna þessa síðu." "notFound": "Ekki er hægt að opna þessa síðu."
}, },
"files": { "files": {
"folders": "Möppur",
"files": "Skjöl",
"body": "Meginmál", "body": "Meginmál",
"clear": "Hreinsa", "clear": "Hreinsa",
"closePreview": "Loka forskoðun", "closePreview": "Loka forskoðun",
"files": "Skjöl",
"folders": "Möppur",
"home": "Heim", "home": "Heim",
"lastModified": "Seinast breytt", "lastModified": "Seinast breytt",
"loading": "Hleð...", "loading": "Hleð...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "Hægt að velja mörg skjöl/möppur", "multipleSelectionEnabled": "Hægt að velja mörg skjöl/möppur",
"name": "Nafn", "name": "Nafn",
"size": "Stærð", "size": "Stærð",
"sortByLastModified": "Flokka eftir Seinast breytt",
"sortByName": "Flokka eftir nafni", "sortByName": "Flokka eftir nafni",
"sortBySize": "Flokka eftir stærð", "sortBySize": "Flokka eftir stærð"
"sortByLastModified": "Flokka eftir Seinast breytt"
}, },
"help": { "help": {
"click": "velja skjal eða möppu", "click": "velja skjal eða möppu",
@@ -74,18 +76,39 @@
"f2": "endurnefna skjal", "f2": "endurnefna skjal",
"help": "Hjálp" "help": "Hjálp"
}, },
"languages": {
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"svSE": "",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": { "login": {
"password": "Lykilorð",
"passwordConfirm": "Staðfesting lykilorðs",
"submit": "Innskráning",
"createAnAccount": "Búa til nýjan aðgang", "createAnAccount": "Búa til nýjan aðgang",
"loginInstead": "Þú ert þegar með aðgang", "loginInstead": "Þú ert þegar með aðgang",
"password": "Lykilorð",
"passwordConfirm": "Staðfesting lykilorðs",
"passwordsDontMatch": "Lykilorð eru mismunandi", "passwordsDontMatch": "Lykilorð eru mismunandi",
"usernameTaken": "Þetta norendanafn er þegar í notkun",
"signup": "Nýskráning", "signup": "Nýskráning",
"submit": "Innskráning",
"username": "Notendanafn", "username": "Notendanafn",
"usernameTaken": "Þetta norendanafn er þegar í notkun",
"wrongCredentials": "Rangar notendaupplýsingar" "wrongCredentials": "Rangar notendaupplýsingar"
}, },
"permanent": "Varanlegt",
"prompts": { "prompts": {
"copy": "Afrita", "copy": "Afrita",
"copyMessage": "Veldu staðsetningu til að afrita gögn: ", "copyMessage": "Veldu staðsetningu til að afrita gögn: ",
@@ -102,43 +125,64 @@
"lastModified": "Seinast breytt", "lastModified": "Seinast breytt",
"move": "Færa", "move": "Færa",
"moveMessage": "Velja nýtt hús fyrir skjöl/möppur:", "moveMessage": "Velja nýtt hús fyrir skjöl/möppur:",
"newArchetype": "Búðu til nýja færslu sem byggir á frumgerð. Skjalið verður búið til í content möppu. ",
"newDir": "Ný mappa", "newDir": "Ný mappa",
"newDirMessage": "Hvað á mappan að heita?", "newDirMessage": "Hvað á mappan að heita?",
"newFile": "Nýtt skjal", "newFile": "Nýtt skjal",
"newFileMessage": "Hvað á skjalið að heita?", "newFileMessage": "Hvað á skjalið að heita?",
"numberDirs": "Fjöldi mappa", "numberDirs": "Fjöldi mappa",
"numberFiles": "Fjöldi skjala", "numberFiles": "Fjöldi skjala",
"replace": "Skipta út",
"replaceMessage": "Eitt af skjölunum sem þú ert að reyna að hlaða upp hefur sama nafn og annað skjal. Viltu skipta nýja skjalinu út fyrir það gamla?\n",
"rename": "Endurnefna", "rename": "Endurnefna",
"renameMessage": "Settu inn nýtt nafn fyrir", "renameMessage": "Settu inn nýtt nafn fyrir",
"show": "Sýna", "replace": "Skipta út",
"size": "Stærð", "replaceMessage": "Eitt af skjölunum sem þú ert að reyna að hlaða upp hefur sama nafn og annað skjal. Viltu skipta nýja skjalinu út fyrir það gamla?\n",
"schedule": "Áætlun", "schedule": "Áætlun",
"scheduleMessage": "Veldu dagsetningu og tíma fyrir áætlaða útgáfu. ", "scheduleMessage": "Veldu dagsetningu og tíma fyrir áætlaða útgáfu. ",
"newArchetype": "Búðu til nýja færslu sem byggir á frumgerð. Skjalið verður búið til í content möppu. " "show": "Sýna",
"size": "Stærð",
"upload": "",
"uploadMessage": ""
},
"search": {
"images": "Myndir",
"music": "Tónlist",
"pdf": "PDF",
"pressToSearch": "Ýttu á Enter til að leita...",
"search": "Leita...",
"typeToSearch": "Skrifaðu til að leita...",
"types": "Skrárgerðir",
"video": "Myndbönd"
}, },
"settings": { "settings": {
"instanceName": "Nafn tilviks",
"brandingDirectoryPath": "Mappa fyrir branding-skjöl",
"documentation": "leiðbeiningar",
"branding": "Útlit",
"disableExternalLinks": "Sýna ytri-hlekki (fyrir utan leiðbeiningar)",
"brandingHelp": "Þú getur breytt því hvernig File Browser lítur út með því að breyta nafninu, setja inn nýtt lógó, búa til þína eigin stíla og tekið út GitHub-hlekki. \nTil að lesa meira um custom-branding, kíktu á {0}.",
"admin": "Stjórnandi", "admin": "Stjórnandi",
"administrator": "Stjórnandi", "administrator": "Stjórnandi",
"allowCommands": "Senda skipanir", "allowCommands": "Senda skipanir",
"allowEdit": "Breyta, endurnefna og eyða skjölum eða möppum", "allowEdit": "Breyta, endurnefna og eyða skjölum eða möppum",
"allowNew": "Búa til ný skjöl og möppur", "allowNew": "Búa til ný skjöl og möppur",
"allowPublish": "Gefa út nýjar færslur og síður", "allowPublish": "Gefa út nýjar færslur og síður",
"allowSignup": "Leyfa nýjum notendum að skrá sig",
"avoidChanges": "(engar breytingar ef ekkert er skrifað)", "avoidChanges": "(engar breytingar ef ekkert er skrifað)",
"branding": "Útlit",
"brandingDirectoryPath": "Mappa fyrir branding-skjöl",
"brandingHelp": "Þú getur breytt því hvernig File Browser lítur út með því að breyta nafninu, setja inn nýtt lógó, búa til þína eigin stíla og tekið út GitHub-hlekki. \nTil að lesa meira um custom-branding, kíktu á {0}.",
"changePassword": "Breyta lykilorði", "changePassword": "Breyta lykilorði",
"commandRunner": "Skipanagluggi", "commandRunner": "Skipanagluggi",
"commandRunnerHelp": "Hér geturðu sett inn skipanir sem eru keyrðar eftir því sem þú tilgreinir. Skrifaðu eina skipun í hverja línu. Umhverfisbreyturnar {0} og {1} verða aðgengilegar ({0} miðast við {1}). Til að lesa meira og sjá lista yfir þær skipanir sem eru í boði, vinsamlegast lestu {2}. ", "commandRunnerHelp": "Hér geturðu sett inn skipanir sem eru keyrðar eftir því sem þú tilgreinir. Skrifaðu eina skipun í hverja línu. Umhverfisbreyturnar {0} og {1} verða aðgengilegar ({0} miðast við {1}). Til að lesa meira og sjá lista yfir þær skipanir sem eru í boði, vinsamlegast lestu {2}. ",
"commandsUpdated": "Skipanastillingar vistaðar!", "commandsUpdated": "Skipanastillingar vistaðar!",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "Custom Stylesheet", "customStylesheet": "Custom Stylesheet",
"defaultUserDescription": "Þetta eru sjálfgefnar stillingar fyrir nýja notendur.",
"disableExternalLinks": "Sýna ytri-hlekki (fyrir utan leiðbeiningar)",
"documentation": "leiðbeiningar",
"examples": "Dæmi", "examples": "Dæmi",
"executeOnShell": "Keyra í skel",
"executeOnShellDescription": "Sjálfgefnar stillingar File Browser eru að keyra skipanir beint með því að sækja binaries. Ef þú villt keyra skipanir í skel (t.d. í Bash eða PowerShell), þá geturðu skilgreint það hér með nauðsynlegum arguments og flags. Ef þetta er stillt, þá verður skipuninni bætt fyrir aftan sem argument. Þetta gildir bæði um skipanir notenda og event hooks.",
"globalRules": "Þetta eru sjálfgegnar aðgangsreglur. Þær gilda um alla notendur. Þú getur tilgreint sérstakar reglur í stillingum fyrir hvern notenda til að ógilda þessar reglur. ",
"globalSettings": "Global stillingar", "globalSettings": "Global stillingar",
"hideDotfiles": "",
"insertPath": "Settu inn slóð",
"insertRegex": "Setja inn reglulega segð",
"instanceName": "Nafn tilviks",
"language": "Tungumál", "language": "Tungumál",
"lockPassword": "Koma í veg fyrir að notandi breyti lykilorðinu", "lockPassword": "Koma í veg fyrir að notandi breyti lykilorðinu",
"newPassword": "Nýja lykilorðið þitt", "newPassword": "Nýja lykilorðið þitt",
@@ -146,6 +190,16 @@
"newUser": "Nýr notandi", "newUser": "Nýr notandi",
"password": "Lykilorð", "password": "Lykilorð",
"passwordUpdated": "Lykilorð vistað!", "passwordUpdated": "Lykilorð vistað!",
"path": "",
"perm": {
"create": "Búa til sköl og möppur",
"delete": "Eyða skjölum og möppum",
"download": "Sækja",
"execute": "Keyra skipanir",
"modify": "Breyta skjölum",
"rename": "Endurnefna eða færa skjöl og möppur",
"share": "Deila skjölum"
},
"permissions": "Heimildir", "permissions": "Heimildir",
"permissionsHelp": "Þú getur stillt notenda sem stjórnanda eða valið einstaklingsbundnar heimildir. Ef þú velur \"Stjórnandi\", þá verða allir aðrir valmöguleikar valdir sjálfrafa. Aðgangstýring notenda er á hendi stjórnenda. \n", "permissionsHelp": "Þú getur stillt notenda sem stjórnanda eða valið einstaklingsbundnar heimildir. Ef þú velur \"Stjórnandi\", þá verða allir aðrir valmöguleikar valdir sjálfrafa. Aðgangstýring notenda er á hendi stjórnenda. \n",
"profileSettings": "Stilla prófíl", "profileSettings": "Stilla prófíl",
@@ -155,82 +209,46 @@
"rulesHelp": "Hér getur þú skilgreint hvaða reglur gilda um notandann. Skjölin sem hann hefur ekki aðgang að eru óaðgengileg og hann sér þau ekki. Stuðst er við reglulegar segðir og slóðir sem miðast við sýn notandans. ", "rulesHelp": "Hér getur þú skilgreint hvaða reglur gilda um notandann. Skjölin sem hann hefur ekki aðgang að eru óaðgengileg og hann sér þau ekki. Stuðst er við reglulegar segðir og slóðir sem miðast við sýn notandans. ",
"scope": "Sýn notandans", "scope": "Sýn notandans",
"settingsUpdated": "Stillingar vistaðar!", "settingsUpdated": "Stillingar vistaðar!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "Notandi", "user": "Notandi",
"userCommands": "Skipanir", "userCommands": "Skipanir",
"userCommandsHelp": "Listi þar sem gildum er skipt upp með bili og inniheldur tiltækar skipanir fyrir þennan notanda. Til dæmis:\n", "userCommandsHelp": "Listi þar sem gildum er skipt upp með bili og inniheldur tiltækar skipanir fyrir þennan notanda. Til dæmis:\n",
"userCreated": "Notandi stofnaður!", "userCreated": "Notandi stofnaður!",
"userDefaults": "Sjálfgefnar notendastillingar",
"userDeleted": "Notanda eytt!", "userDeleted": "Notanda eytt!",
"userManagement": "Notendastýring", "userManagement": "Notendastýring",
"username": "Notendanafn",
"users": "Notendur",
"globalRules": "Þetta eru sjálfgegnar aðgangsreglur. Þær gilda um alla notendur. Þú getur tilgreint sérstakar reglur í stillingum fyrir hvern notenda til að ógilda þessar reglur. ",
"allowSignup": "Leyfa nýjum notendum að skrá sig",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Setja inn reglulega segð",
"insertPath": "Settu inn slóð",
"userUpdated": "Notandastillingar vistaðar!", "userUpdated": "Notandastillingar vistaðar!",
"userDefaults": "Sjálfgefnar notendastillingar", "username": "Notendanafn",
"defaultUserDescription": "Þetta eru sjálfgefnar stillingar fyrir nýja notendur.", "users": "Notendur"
"executeOnShell": "Keyra í skel",
"executeOnShellDescription": "Sjálfgefnar stillingar File Browser eru að keyra skipanir beint með því að sækja binaries. Ef þú villt keyra skipanir í skel (t.d. í Bash eða PowerShell), þá geturðu skilgreint það hér með nauðsynlegum arguments og flags. Ef þetta er stillt, þá verður skipuninni bætt fyrir aftan sem argument. Þetta gildir bæði um skipanir notenda og event hooks.",
"perm": {
"create": "Búa til sköl og möppur",
"delete": "Eyða skjölum og möppum",
"download": "Sækja",
"modify": "Breyta skjölum",
"execute": "Keyra skipanir",
"rename": "Endurnefna eða færa skjöl og möppur",
"share": "Deila skjölum"
}
}, },
"sidebar": { "sidebar": {
"help": "Hjálp", "help": "Hjálp",
"hugoNew": "Hugo New",
"login": "Innskráning", "login": "Innskráning",
"signup": "Nýskráning",
"logout": "Útskráning", "logout": "Útskráning",
"myFiles": "Gögnin mín", "myFiles": "Gögnin mín",
"newFile": "Nýtt skjal", "newFile": "Nýtt skjal",
"newFolder": "Ný mappa", "newFolder": "Ný mappa",
"preview": "Sýnishorn",
"settings": "Stillingar", "settings": "Stillingar",
"siteSettings": "Stillingar síðu", "signup": "Nýskráning",
"hugoNew": "Hugo New", "siteSettings": "Stillingar síðu"
"preview": "Sýnishorn"
}, },
"search": { "success": {
"images": "Myndir", "linkCopied": "Hlekkur afritaður!"
"music": "Tónlist",
"pdf": "PDF",
"types": "Skrárgerðir",
"video": "Myndbönd",
"search": "Leita...",
"typeToSearch": "Skrifaðu til að leita...",
"pressToSearch": "Ýttu á Enter til að leita..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
}, },
"time": { "time": {
"unit": "Tímastilling", "days": "Dagar",
"seconds": "Sekúndur",
"minutes": "Mínútur",
"hours": "Klukkutímar", "hours": "Klukkutímar",
"days": "Dagar" "minutes": "Mínútur",
}, "seconds": "Sekúndur",
"download": { "unit": "Tímastilling"
"downloadFile": "Sækja skjal",
"downloadFolder": "Sækja möppu"
} }
} }

View File

@@ -1,7 +1,5 @@
{ {
"permanent": "Permanente",
"buttons": { "buttons": {
"shell": "Toggle shell",
"cancel": "Annulla", "cancel": "Annulla",
"close": "Chiudi", "close": "Chiudi",
"copy": "Copia", "copy": "Copia",
@@ -10,6 +8,7 @@
"create": "Crea", "create": "Crea",
"delete": "Elimina", "delete": "Elimina",
"download": "Scarica", "download": "Scarica",
"hideDotfiles": "",
"info": "Informazioni", "info": "Informazioni",
"more": "Altro", "more": "Altro",
"move": "Sposta", "move": "Sposta",
@@ -17,25 +16,28 @@
"new": "Nuovo", "new": "Nuovo",
"next": "Successivo", "next": "Successivo",
"ok": "OK", "ok": "OK",
"replace": "Sostituisci", "permalink": "Ottieni link permanente",
"previous": "Precedente", "previous": "Precedente",
"publish": "Publica",
"rename": "Rinomina", "rename": "Rinomina",
"replace": "Sostituisci",
"reportIssue": "Segnala un problema", "reportIssue": "Segnala un problema",
"save": "Salva", "save": "Salva",
"schedule": "Programma",
"search": "Cerca", "search": "Cerca",
"select": "Seleziona", "select": "Seleziona",
"share": "Condividi",
"publish": "Publica",
"selectMultiple": "Seleziona molteplici", "selectMultiple": "Seleziona molteplici",
"schedule": "Programma", "share": "Condividi",
"shell": "Toggle shell",
"switchView": "Cambia vista", "switchView": "Cambia vista",
"toggleSidebar": "Mostra/nascondi la barra laterale", "toggleSidebar": "Mostra/nascondi la barra laterale",
"update": "Aggiorna", "update": "Aggiorna",
"upload": "Carica", "upload": "Carica"
"permalink": "Ottieni link permanente"
}, },
"success": { "download": {
"linkCopied": "Link copiato!" "downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": ""
}, },
"errors": { "errors": {
"forbidden": "You don't have permissions to access this.", "forbidden": "You don't have permissions to access this.",
@@ -43,11 +45,11 @@
"notFound": "Questo percorso non può essere raggiunto." "notFound": "Questo percorso non può essere raggiunto."
}, },
"files": { "files": {
"folders": "Cartelle",
"files": "Files",
"body": "Contenuto", "body": "Contenuto",
"clear": "Cancella", "clear": "Cancella",
"closePreview": "Chiudi anteprima", "closePreview": "Chiudi anteprima",
"files": "Files",
"folders": "Cartelle",
"home": "Home", "home": "Home",
"lastModified": "Ultima modifica", "lastModified": "Ultima modifica",
"loading": "Caricamento...", "loading": "Caricamento...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "Selezione multipla attivata", "multipleSelectionEnabled": "Selezione multipla attivata",
"name": "Nome", "name": "Nome",
"size": "Grandezza", "size": "Grandezza",
"sortByLastModified": "Ordina per ultima modifica",
"sortByName": "Ordina per nome", "sortByName": "Ordina per nome",
"sortBySize": "Ordina per dimensione", "sortBySize": "Ordina per dimensione"
"sortByLastModified": "Ordina per ultima modifica"
}, },
"help": { "help": {
"click": "seleziona un file o una cartella", "click": "seleziona un file o una cartella",
@@ -74,18 +76,39 @@
"f2": "rinomina un file", "f2": "rinomina un file",
"help": "Aiuto" "help": "Aiuto"
}, },
"languages": {
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "",
"ru": "Русский",
"svSE": "",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": { "login": {
"password": "Password",
"passwordConfirm": "Password Confirmation",
"submit": "Entra",
"createAnAccount": "Create an account", "createAnAccount": "Create an account",
"loginInstead": "Already have an account", "loginInstead": "Already have an account",
"password": "Password",
"passwordConfirm": "Password Confirmation",
"passwordsDontMatch": "Passwords don't match", "passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup", "signup": "Signup",
"submit": "Entra",
"username": "Nome utente", "username": "Nome utente",
"usernameTaken": "Username already taken",
"wrongCredentials": "Credenziali errate" "wrongCredentials": "Credenziali errate"
}, },
"permanent": "Permanente",
"prompts": { "prompts": {
"copy": "Copia", "copy": "Copia",
"copyMessage": "Seleziona la cartella in cui copiare i file:", "copyMessage": "Seleziona la cartella in cui copiare i file:",
@@ -102,43 +125,64 @@
"lastModified": "Ultima modifica", "lastModified": "Ultima modifica",
"move": "Sposta", "move": "Sposta",
"moveMessage": "Seleziona la nuova posizione per i tuoi file e/o cartella/e:", "moveMessage": "Seleziona la nuova posizione per i tuoi file e/o cartella/e:",
"newArchetype": "Crea un nuovo post basato su un modello. Il tuo file verrà creato nella cartella.",
"newDir": "Nuova cartella", "newDir": "Nuova cartella",
"newDirMessage": "Scrivi il nome della nuova cartella.", "newDirMessage": "Scrivi il nome della nuova cartella.",
"newFile": "Nuovo file", "newFile": "Nuovo file",
"newFileMessage": "Scrivi il nome del nuovo file.", "newFileMessage": "Scrivi il nome del nuovo file.",
"numberDirs": "Numero di cartelle", "numberDirs": "Numero di cartelle",
"numberFiles": "Numero di files", "numberFiles": "Numero di files",
"replace": "Sostituisci",
"replaceMessage": "Uno dei file che stai cercando di caricare sta generando un conflitto per via del suo nome. Desideri sostituire il file già esistente?\n",
"rename": "Rinomina", "rename": "Rinomina",
"renameMessage": "Inserisci un nuovo nome per", "renameMessage": "Inserisci un nuovo nome per",
"show": "Mostra", "replace": "Sostituisci",
"size": "Grandezza", "replaceMessage": "Uno dei file che stai cercando di caricare sta generando un conflitto per via del suo nome. Desideri sostituire il file già esistente?\n",
"schedule": "Pianifica", "schedule": "Pianifica",
"scheduleMessage": "Seleziona data e ora per programmare la pubbilicazione di questo post", "scheduleMessage": "Seleziona data e ora per programmare la pubbilicazione di questo post",
"newArchetype": "Crea un nuovo post basato su un modello. Il tuo file verrà creato nella cartella." "show": "Mostra",
"size": "Grandezza",
"upload": "",
"uploadMessage": ""
},
"search": {
"images": "Immagini",
"music": "Musica",
"pdf": "PDF",
"pressToSearch": "Press enter to search...",
"search": "Cerca...",
"typeToSearch": "Type to search...",
"types": "Tipi",
"video": "Video"
}, },
"settings": { "settings": {
"instanceName": "Instance name",
"brandingDirectoryPath": "Branding directory path",
"documentation": "documentation",
"branding": "Branding",
"disableExternalLinks": "Disable external links (except documentation)",
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"admin": "Admin", "admin": "Admin",
"administrator": "Amministratore", "administrator": "Amministratore",
"allowCommands": "Esegui comandi", "allowCommands": "Esegui comandi",
"allowEdit": "Modifica, rinomina ed elimina file o cartelle", "allowEdit": "Modifica, rinomina ed elimina file o cartelle",
"allowNew": "Crea nuovi files o cartelle", "allowNew": "Crea nuovi files o cartelle",
"allowPublish": "Pubblica nuovi post e pagine", "allowPublish": "Pubblica nuovi post e pagine",
"allowSignup": "Allow users to signup",
"avoidChanges": "(lascia vuoto per evitare cambiamenti)", "avoidChanges": "(lascia vuoto per evitare cambiamenti)",
"branding": "Branding",
"brandingDirectoryPath": "Branding directory path",
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"changePassword": "Modifica password", "changePassword": "Modifica password",
"commandRunner": "Command runner", "commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.", "commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "Comandi aggiornati!", "commandsUpdated": "Comandi aggiornati!",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "Folgio di stile personalizzato", "customStylesheet": "Folgio di stile personalizzato",
"defaultUserDescription": "This are the default settings for new users.",
"disableExternalLinks": "Disable external links (except documentation)",
"documentation": "documentation",
"examples": "Esempi", "examples": "Esempi",
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"globalSettings": "Impostazioni Globali", "globalSettings": "Impostazioni Globali",
"hideDotfiles": "",
"insertPath": "Insert the path",
"insertRegex": "Insert regex expression",
"instanceName": "Instance name",
"language": "Lingua", "language": "Lingua",
"lockPassword": "Impedisci all'utente di modificare la password", "lockPassword": "Impedisci all'utente di modificare la password",
"newPassword": "La tua nuova password", "newPassword": "La tua nuova password",
@@ -146,6 +190,16 @@
"newUser": "Nuovo utente", "newUser": "Nuovo utente",
"password": "Password", "password": "Password",
"passwordUpdated": "Password aggiornata!", "passwordUpdated": "Password aggiornata!",
"path": "",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"execute": "Execute commands",
"modify": "Edit files",
"rename": "Rename or move files and directories",
"share": "Share files"
},
"permissions": "Permessi", "permissions": "Permessi",
"permissionsHelp": "È possibile impostare l'utente come amministratore o scegliere i permessi singolarmente. Se si seleziona \"Amministratore\", tutte le altre opzioni saranno automaticamente assegnate. La gestione degli utenti rimane un privilegio di un amministratore.\n", "permissionsHelp": "È possibile impostare l'utente come amministratore o scegliere i permessi singolarmente. Se si seleziona \"Amministratore\", tutte le altre opzioni saranno automaticamente assegnate. La gestione degli utenti rimane un privilegio di un amministratore.\n",
"profileSettings": "Impostazioni del profilo", "profileSettings": "Impostazioni del profilo",
@@ -155,82 +209,46 @@
"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", "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": "Scopo", "scope": "Scopo",
"settingsUpdated": "Impostazioni aggiornate!", "settingsUpdated": "Impostazioni aggiornate!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "Utente", "user": "Utente",
"userCommands": "Comandi", "userCommands": "Comandi",
"userCommandsHelp": "Una lista separata dal spazi con i comandi disponibili per questo utente. Example:\n", "userCommandsHelp": "Una lista separata dal spazi con i comandi disponibili per questo utente. Example:\n",
"userCreated": "Utente creato!", "userCreated": "Utente creato!",
"userDefaults": "User default settings",
"userDeleted": "Utente eliminato!", "userDeleted": "Utente eliminato!",
"userManagement": "Gestione degli utenti", "userManagement": "Gestione degli utenti",
"username": "Nome utente",
"users": "Utenti",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"allowSignup": "Allow users to signup",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Insert regex expression",
"insertPath": "Insert the path",
"userUpdated": "Utente aggiornato!", "userUpdated": "Utente aggiornato!",
"userDefaults": "User default settings", "username": "Nome utente",
"defaultUserDescription": "This are the default settings for new users.", "users": "Utenti"
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"modify": "Edit files",
"execute": "Execute commands",
"rename": "Rename or move files and directories",
"share": "Share files"
}
}, },
"sidebar": { "sidebar": {
"help": "Aiuto", "help": "Aiuto",
"hugoNew": "Hugo New",
"login": "Login", "login": "Login",
"signup": "Signup",
"logout": "Esci", "logout": "Esci",
"myFiles": "I miei file", "myFiles": "I miei file",
"newFile": "Nuovo file", "newFile": "Nuovo file",
"newFolder": "Nuova cartella", "newFolder": "Nuova cartella",
"preview": "Anteprima",
"settings": "Impostazioni", "settings": "Impostazioni",
"siteSettings": "Impostaizoni del sito", "signup": "Signup",
"hugoNew": "Hugo New", "siteSettings": "Impostaizoni del sito"
"preview": "Anteprima"
}, },
"search": { "success": {
"images": "Immagini", "linkCopied": "Link copiato!"
"music": "Musica",
"pdf": "PDF",
"types": "Tipi",
"video": "Video",
"search": "Cerca...",
"typeToSearch": "Type to search...",
"pressToSearch": "Press enter to search..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
}, },
"time": { "time": {
"unit": "Unità di tempo", "days": "Giorni",
"seconds": "Secondi",
"minutes": "Minuti",
"hours": "Ore", "hours": "Ore",
"days": "Giorni" "minutes": "Minuti",
}, "seconds": "Secondi",
"download": { "unit": "Unità di tempo"
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
} }
} }

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