Compare commits

...

190 Commits

Author SHA1 Message Date
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
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
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
Alvaro Aleman
977ec33918 chore: close preview on esc key press (#1288) 2021-02-16 17:15:04 +01: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
Oleg Lobanov
de0b8bb7b2 chore(release): 2.3.0 2020-06-26 12:14:44 +02:00
Thomas Queste
84da110085 fix: typo in image_templates (apline -> alpine) (#1005) 2020-06-25 09:37:55 +02:00
monkeyWie
6b0d49b1fc feat: add image thumbnails support (#980)
* set max image preview size to 1080x1080px
2020-06-25 09:37:13 +02:00
Oleg Lobanov
4c20772e11 chore(release): 2.2.0 2020-06-22 19:12:12 +02:00
Oleg Lobanov
68f8348dde fix: apply all fs user rulles 2020-06-22 18:46:22 +02:00
Oleg Lobanov
5023e77296 Merge pull request #995 from ramiresviana/key-shortcuts 2020-06-22 13:48:56 +02:00
Ramires Viana
95316cbe8c feat: add key shortcuts
- 'Ctrl + a' selects all files in listing.
- 'Enter' to confirm a prompt.
2020-06-21 21:54:23 +00:00
Ramires Viana
cd454bae51 feat: upload progress based on total size (#993) 2020-06-19 09:46:33 +02:00
Oleg Lobanov
241201657c fix: add a workaround to fix window freezing when viewing a large file #992 2020-06-18 19:21:02 +02:00
Hampton
9eefaddd9b chore: fix documentation links on README (#987) 2020-06-18 17:54:37 +02:00
Oleg Lobanov
d6d47bbd6b Merge pull request #991 from ramiresviana/small-fixes 2020-06-18 09:59:27 +02:00
Ramires Viana
82c883f95e fix: save event hook
fix filebrowser/filebrowser#696
2020-06-17 22:57:13 +00:00
Ramires Viana
dd40b0d9b9 fix: frontend token validation
fix filebrowser/filebrowser#638
2020-06-17 22:57:07 +00:00
Ramires Viana
963837ef1d fix: multiple selection count
- Only add files to selected list that arent on it.
- Only shift key select when there are selected files.
2020-06-17 22:56:55 +00:00
Oleg Lobanov
66863b72f7 feat: add alpine and debian docker images 2020-06-16 23:18:22 +02:00
Ramires Viana
89773447a5 feat: add folder upload (#981)
* feat: folder upload
fix filebrowser/filebrowser#741

* fix: apply gofmt formater

* feat: upload button prompt

* feat: empty folder upload
2020-06-16 21:56:44 +02:00
Oleg Lobanov
6d899a6335 chore: version v2.1.2 2020-06-06 17:49:14 +02:00
Oleg Lobanov
28672c0114 fix(security): check user permission to rename files 2020-06-06 17:45:51 +02:00
Oleg Lobanov
b8300b7121 chore: add dist folder to gitignore 2020-06-02 10:50:14 +02:00
Oleg Lobanov
584ef4d4bd chore: version v2.1.1 2020-06-01 02:53:15 +02:00
Oleg Lobanov
e8295a944a fix(build): fix openbsd build
bump golang.org/x deps:
* golang.org/x/crypto
* golang.org/x/net
* golang.org/x/sys
2020-06-01 02:52:26 +02:00
Oleg Lobanov
f8f5698ad0 build(docker): add arm 5 docker image for raspberry pi 2020-06-01 02:14:11 +02:00
Oleg Lobanov
700f32718e refactor: add more go linters (#970) 2020-06-01 01:12:36 +02:00
Oleg Lobanov
54d92a2708 chore: bump go to 1.14.3 (#969) 2020-05-31 23:17:32 +02:00
Oleg Lobanov
ba47e3b2fe fix: fix static assets url generation (#965) 2020-05-31 22:26:10 +02:00
Oleg Lobanov
6e5405eeed Update README.md 2020-05-27 14:23:12 +02:00
Henrique Dias
45326e664f Update README.md 2020-04-16 13:25:03 +01:00
Henrique Dias
6ce44f7092 chore: version v2.1.0 2020-01-09 18:05:20 +00:00
Henrique Dias
b320419088 tidy 2020-01-09 18:03:18 +00:00
Henrique Dias
ca183a4fb8 Update README.md 2020-01-09 17:49:25 +00:00
Henrique Dias
895bb755cd Disable the logout method for authentication methods other than… (#934)
Disable the logout method for authentication methods other than 'json' (currently 'proxy' and 'none'.) Resolves #870.
2020-01-09 17:27:56 +00:00
Henrique Dias
a9e715dc50 Merge branch 'master' into remove-logout-button 2020-01-09 17:27:49 +00:00
Henrique Dias
7cb046c542 Merge pull request #938 from filebrowser/dependabot/go_modules/github.com/spf13/viper-1.6.1
chore(deps): bump github.com/spf13/viper from 1.5.0 to 1.6.1
2020-01-09 17:27:18 +00:00
dependabot-preview[bot]
cd03faf0fc chore(deps): bump vue-i18n from 8.15.1 to 8.15.3 in /frontend
Bumps [vue-i18n](https://github.com/kazupon/vue-i18n) from 8.15.1 to 8.15.3.
- [Release notes](https://github.com/kazupon/vue-i18n/releases)
- [Changelog](https://github.com/kazupon/vue-i18n/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/kazupon/vue-i18n/compare/v8.15.1...v8.15.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 17:27:09 +00:00
dependabot-preview[bot]
87ba03b224 chore(deps-dev): bump @vue/cli-service from 4.0.5 to 4.1.2 in /frontend
Bumps [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service) from 4.0.5 to 4.1.2.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.1.2/packages/@vue/cli-service)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 17:26:58 +00:00
dependabot-preview[bot]
6458f91e1c chore(deps-dev): bump @vue/cli-plugin-babel in /frontend
Bumps [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.1.2/packages/@vue/cli-plugin-babel)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 17:26:49 +00:00
dependabot-preview[bot]
312ebbbcc0 chore(deps-dev): bump eslint-plugin-vue from 6.0.1 to 6.1.2 in /frontend
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 6.0.1 to 6.1.2.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v6.0.1...v6.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 17:26:42 +00:00
freedomlang
060a7ad80c refactor: Load Editor as need to reduce bundle size 2020-01-09 17:26:17 +00:00
freedomlang
ae893abc5f refactor: Remove useless react data and destory ace editor 2020-01-09 17:26:17 +00:00
freedomlang
12d6415f7f refactor: Freeze property to improve performance 2020-01-09 17:26:17 +00:00
freedomlang
897ac75281 refactor: Use v-for for language option 2020-01-09 17:26:17 +00:00
Hadrien Dorio
cec551c3de fix(docker): Add mime.types file
Uses the package mailcap from alpine as a source for /etc/mime.types
which is required by golang.org/pkg/mime on unix systems.
2020-01-09 17:26:06 +00:00
Alexey Larkov
cb98c913d4 Allow request manifest through reverse proxy with authentication 2020-01-09 17:25:30 +00:00
Ramires Viana
55a9d945cc Add dark theme 2020-01-09 17:24:59 +00:00
Ramires Viana
cc7ec4f0c5 Fix multiple selection 2020-01-09 17:24:30 +00:00
dependabot-preview[bot]
265b81a52b chore(deps): bump github.com/spf13/viper from 1.5.0 to 1.6.1
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.5.0 to 1.6.1.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.5.0...v1.6.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-09 11:14:41 +00:00
Ovidiu Predescu
b42b09ccbe Disable the logout method for authentication methods other than 'json' (currently 'proxy' and 'none'.) 2019-12-03 17:31:11 -08:00
dependabot-preview[bot]
118071ba4b chore(deps-dev): bump @vue/cli-plugin-babel from 4.0.5 to 4.1.1… (#929)
Bumps [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel) from 4.0.5 to 4.1.1.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.1.1/packages/@vue/cli-plugin-babel)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:52:26 +00:00
dependabot-preview[bot]
73b85eced4 chore(deps-dev): bump eslint from 6.6.0 to 6.7.2 in /frontend (#932)
Bumps [eslint](https://github.com/eslint/eslint) from 6.6.0 to 6.7.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v6.6.0...v6.7.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:52:20 +00:00
Henrique Dias
997a0a433f fix: disable eslint on one line
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-12-02 15:48:55 +00:00
dependabot-preview[bot]
0d7e344ca3 chore(deps-dev): bump @vue/cli-plugin-eslint from 4.0.5 to 4.1.1… (#927)
Bumps [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-eslint) from 4.0.5 to 4.1.1.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.1.1/packages/@vue/cli-plugin-eslint)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:36:38 +00:00
dependabot-preview[bot]
1884d50c3b chore(deps): bump vue-i18n from 8.15.0 to 8.15.1 in /frontend (#925)
Bumps [vue-i18n](https://github.com/kazupon/vue-i18n) from 8.15.0 to 8.15.1.
- [Release notes](https://github.com/kazupon/vue-i18n/releases)
- [Changelog](https://github.com/kazupon/vue-i18n/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/kazupon/vue-i18n/compare/v8.15.0...v8.15.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:36:30 +00:00
dependabot-preview[bot]
f5fad7a01d chore(deps): bump gopkg.in/yaml.v2 from 2.2.5 to 2.2.7 (#921)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.5 to 2.2.7.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.5...v2.2.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:36:22 +00:00
dependabot-preview[bot]
5c2ed2b2f9 chore(deps-dev): bump eslint-plugin-vue from 6.0.0 to 6.0.1 in /… (#916)
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v6.0.0...v6.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:36:13 +00:00
dependabot-preview[bot]
05475eb4fc chore(deps): bump vuex from 3.1.1 to 3.1.2 in /frontend (#914)
Bumps [vuex](https://github.com/vuejs/vuex) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/vuejs/vuex/releases)
- [Commits](https://github.com/vuejs/vuex/compare/v3.1.1...v3.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:36:01 +00:00
dependabot-preview[bot]
9e6cc302c0 chore(deps): bump qrcode.vue from 1.6.3 to 1.7.0 in /frontend (#913)
Bumps [qrcode.vue](https://github.com/scopewu/qrcode.vue) from 1.6.3 to 1.7.0.
- [Release notes](https://github.com/scopewu/qrcode.vue/releases)
- [Changelog](https://github.com/scopewu/qrcode.vue/blob/master/CHANGELOG.md)
- [Commits](https://github.com/scopewu/qrcode.vue/compare/v1.6.3...v1.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 15:35:53 +00:00
Henrique Dias
d422421cf9 Merge pull request #918 from spacebat/real-ip
Determine the real IP address of the client for logging
2019-12-02 15:31:56 +00:00
freedomlang
23a3ef069e refactor: Optimize prompts component 2019-12-02 15:31:06 +00:00
blackywkl
2a81ea90db feat: add animation for disable multiple selection and break word for filename in info panel (#922) 2019-12-02 15:30:18 +00:00
A Kirkpatrick
5fb7207d65 Determine the real IP address of the client for logging
When running behind a reverse proxy such as nginx, the remote IP as
logged is always that of the proxy. Figuring out the correct address
in this context is a little tricky, hence the following module is
used:

https://github.com/tomasen/realip
2019-11-17 14:14:15 +10:30
Henrique Dias
d79d864825 chore(deps-dev): bump eslint from 5.16.0 to 6.6.0 in /frontend (#915)
chore(deps-dev): bump eslint from 5.16.0 to 6.6.0 in /frontend
2019-11-12 07:50:15 +00:00
dependabot-preview[bot]
d249b8b202 chore(deps-dev): bump eslint from 5.16.0 to 6.6.0 in /frontend
Bumps [eslint](https://github.com/eslint/eslint) from 5.16.0 to 6.6.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.16.0...v6.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-11 12:23:46 +00:00
Henrique Dias
e9bd68f3b0 docs: make warning more visivble 2019-11-10 09:13:32 +00:00
Henrique Dias
506e088236 chore(deps): bump github.com/spf13/viper from 1.4.0 to 1.5.0 (#908)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.4.0...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-10 09:08:05 +00:00
dependabot-preview[bot]
c906d296be chore(deps): bump gopkg.in/yaml.v2 from 2.2.4 to 2.2.5 (#910)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.4 to 2.2.5.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.4...v2.2.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-10 09:07:55 +00:00
dependabot-preview[bot]
3b7f6ccf8e chore(deps-dev): bump eslint-plugin-vue from 5.2.3 to 6.0.0 in /… (#911)
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 5.2.3 to 6.0.0.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v5.2.3...v6.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-10 09:07:41 +00:00
dependabot-preview[bot]
f1a7d2f8d0 chore(deps): bump github.com/spf13/viper from 1.4.0 to 1.5.0
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.4.0...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-04 14:01:17 +00:00
176 changed files with 10360 additions and 4302 deletions

View File

@@ -1,79 +0,0 @@
version: 2
jobs:
lint:
docker:
- image: golangci/golangci-lint:v1.16
steps:
- checkout
- run: golangci-lint run -v -D errcheck
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.12
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.12
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,8 +0,0 @@
{
"port": 80,
"baseURL": "",
"address": "",
"log": "stdout",
"database": "/database.db",
"root": "/srv"
}

View File

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

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

3
.gitignore vendored
View File

@@ -5,6 +5,7 @@ _old
rice-box.go
.idea/
filebrowser
dist/
.DS_Store
node_modules
@@ -27,3 +28,5 @@ yarn-error.log*
*.njsproj
*.sln
*.sw*
bin/
build/

131
.golangci.yml Normal file
View File

@@ -0,0 +1,131 @@
linters-settings:
dupl:
threshold: 100
exhaustive:
default-signifies-exhaustive: false
funlen:
lines: 100
statements: 50
goconst:
min-len: 2
min-occurrences: 2
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
disabled-checks:
- dupImport # https://github.com/go-critic/go-critic/issues/845
- ifElseChain
- octalLiteral
- whyNoLint
- wrapperFunc
gocyclo:
min-complexity: 15
goimports:
local-prefixes: github.com/filebrowser/filebrowser
golint:
min-confidence: 0
gomnd:
settings:
mnd:
# don't include the "operation" and "assign"
checks: argument,case,condition,return
govet:
check-shadowing: true
lll:
line-length: 140
maligned:
suggest-new: true
misspell:
locale: US
nolintlint:
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
allow-unused: false # report any unused nolint directives
require-explanation: false # don't require an explanation for nolint directives
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
linters:
# please, do not use `enable-all`: it's deprecated and will be removed soon.
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- funlen
- gochecknoinits
- goconst
- gocritic
- gocyclo
- goimports
- golint
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- nolintlint
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
- prealloc
# don't enable:
# - asciicheck
# - exhaustive (TODO: enable after next release; current release at time of writing is v1.27)
# - gochecknoglobals
# - gocognit
# - godot
# - godox
# - goerr113
# - maligned
# - nestif
# - testpackage
# - wsl
issues:
exclude-rules:
- path: cmd/.*.go
linters:
- gochecknoinits
- path: .*_test.go
linters:
- lll
- gochecknoinits
- gocyclo
- funlen
- dupl
- scopelint
- text: "Auther"
linters:
- misspell
run:
skip-dirs:
- frontend/
skip-files:
- http/rice-box.go
# golangci.com configuration
# https://github.com/golangci/golangci/wiki/Configuration
service:
golangci-lint-version: 1.27.x # use the fixed version to not introduce new linters unexpectedly

View File

@@ -5,7 +5,7 @@ env:
before:
hooks:
- go mod download
- make bundle-frontend
build:
env:
@@ -19,10 +19,6 @@ build:
- linux
- windows
- freebsd
- netbsd
- openbsd
- dragonfly
- solaris
goarch:
- amd64
- 386
@@ -35,14 +31,8 @@ build:
ignore:
- goos: darwin
goarch: 386
- goos: openbsd
goarch: arm
- goos: freebsd
goarch: arm
- goos: netbsd
goarch: arm
- goos: solaris
goarch: arm
archives:
-
@@ -54,12 +44,99 @@ archives:
dockers:
-
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/amd64"
goos: linux
goarch: amd64
goarm: ''
image_templates:
- "filebrowser/filebrowser:latest"
- "filebrowser/filebrowser:{{ .Tag }}"
- "filebrowser/filebrowser:v{{ .Major }}"
extra_files:
- .docker.json
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
-
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/arm64"
goos: linux
goarch: arm64
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
-
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
goarch: arm
goarm: '6'
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-armv6"
- "filebrowser/filebrowser:v{{ .Major }}-armv6"
-
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/v7"
goos: linux
goarch: arm
goarm: '7'
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
- "filebrowser/filebrowser:v{{ .Major }}-armv7"
docker_manifests:
- name_template: "filebrowser/filebrowser:latest"
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
- "filebrowser/filebrowser:{{ .Tag }}-armv6"
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
- 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 }
]
}

199
CHANGELOG.md Normal file
View File

@@ -0,0 +1,199 @@
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [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)
### Features
* add image thumbnails support ([#980](https://github.com/filebrowser/filebrowser/issues/980)) ([6b0d49b](https://github.com/filebrowser/filebrowser/commit/6b0d49b1fc8bdce89576ba91cc0b8ec594fcd625))
### Bug Fixes
* typo in image_templates (apline -> alpine) ([#1005](https://github.com/filebrowser/filebrowser/issues/1005)) ([84da110](https://github.com/filebrowser/filebrowser/commit/84da11008516a371fc0446d97863dc14d337aa25))
## [2.2.0](https://github.com/filebrowser/filebrowser/compare/v2.1.2...v2.2.0) (2020-06-22)
### Features
* add alpine and debian docker images ([66863b7](https://github.com/filebrowser/filebrowser/commit/66863b72f7664e6cb9417f7da542a92fa77ca635))
* add folder upload ([#981](https://github.com/filebrowser/filebrowser/issues/981)) ([8977344](https://github.com/filebrowser/filebrowser/commit/89773447a56675b298394149d7a05c5df4039f14)), closes [filebrowser/filebrowser#741](https://github.com/filebrowser/filebrowser/issues/741)
* add key shortcuts ([95316cb](https://github.com/filebrowser/filebrowser/commit/95316cbe8c8ac3dbb28310bc11ec347c0caf699b))
* upload progress based on total size ([#993](https://github.com/filebrowser/filebrowser/issues/993)) ([cd454ba](https://github.com/filebrowser/filebrowser/commit/cd454bae51f40b1249e6fa6133c2949970eb3018))
### Bug Fixes
* add a workaround to fix window freezing when viewing a large file [#992](https://github.com/filebrowser/filebrowser/issues/992) ([2412016](https://github.com/filebrowser/filebrowser/commit/241201657c2bf01806d02a297eb846b26102a479))
* apply all fs user rulles ([68f8348](https://github.com/filebrowser/filebrowser/commit/68f8348ddeecba570a361e7aba4546052cc3e356))
* frontend token validation ([dd40b0d](https://github.com/filebrowser/filebrowser/commit/dd40b0d9b9cc6268a611306ac4684a1af852b79d)), closes [filebrowser/filebrowser#638](https://github.com/filebrowser/filebrowser/issues/638)
* multiple selection count ([963837e](https://github.com/filebrowser/filebrowser/commit/963837ef1dc6e2e84fcf924606ce388ac30f3891))
* save event hook ([82c883f](https://github.com/filebrowser/filebrowser/commit/82c883f95eead9eebe215e230f74773c945f864a)), closes [filebrowser/filebrowser#696](https://github.com/filebrowser/filebrowser/issues/696)

View File

@@ -1,13 +1,10 @@
FROM alpine:latest as certs
FROM alpine:latest
RUN apk --update add ca-certificates
FROM scratch
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
RUN apk --update add mailcap
VOLUME /srv
EXPOSE 80
COPY .docker.json /.filebrowser.json
COPY filebrowser /filebrowser
ENTRYPOINT [ "/filebrowser" ]
ENTRYPOINT [ "/filebrowser" ]

100
Makefile Normal file
View File

@@ -0,0 +1,100 @@
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
RICE = $(BIN)/rice
$(BIN)/rice: PACKAGE=github.com/GeertJohan/go.rice/rice@v1.0.2
## 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: bundle-frontend | ; $(info $(M) building backend)
$Q $(GO) build -ldflags '$(LDFLAGS)' -o filebrowser
bundle-frontend: | $(RICE) ; $(info $(M) building backend)
$Q cd ./http && rm -rf rice-box.go && $(RICE) embed-go
## 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

@@ -2,11 +2,9 @@
<img src="https://raw.githubusercontent.com/filebrowser/logo/master/banner.png" width="550"/>
</p>
> ⚠️ WARN: **This project is not under active development ATM. A small group of developers keeps the project alive, but due to lack of time, we can't continue adding new features or doing deep changes. Please read [#532](https://github.com/filebrowser/filebrowser/issues/532) for more info!**
![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)
[![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)
@@ -16,16 +14,20 @@ filebrowser provides a file managing interface within a specified directory and
## Features
Please refer to our docs at [filebrowser.xyz/features](https://filebrowser.xyz/features)
Please refer to our docs at [https://filebrowser.org/features](https://filebrowser.org/features)
## Install
Please refer to our docs at [filebrowser.xyz](https://filebrowser.xyz/).
For installation instructions please refer to our docs at [https://filebrowser.org/installation](https://filebrowser.org/installation).
## Usage
## Configuration
Please refer to our docs at [filebrowser.xyz/usage](https://filebrowser.xyz/usage).
[Authentication Method](https://filebrowser.org/configuration/authentication-method) - You can change the way the user authenticates with the filebrowser server
[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.
## Contributing
Please refer to our docs at [filebrowser.xyz/contributing](https://filebrowser.xyz/contributing).
If you're interested in contributing to this project, our docs are best places to start [https://filebrowser.org/contributing](https://filebrowser.org/contributing).

View File

@@ -9,7 +9,7 @@ import (
// Auther is the authentication interface.
type Auther interface {
// 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() bool
}

View File

@@ -20,13 +20,13 @@ type jsonCred struct {
ReCaptcha string `json:"recaptcha"`
}
// JSONAuth is a json implementaion of an Auther.
// JSONAuth is a json implementation of an Auther.
type JSONAuth struct {
ReCaptcha *ReCaptcha `json:"recaptcha" yaml:"recaptcha"`
}
// 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
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 a.ReCaptcha != nil && len(a.ReCaptcha.Secret) > 0 {
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha)
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:govet
if err != nil {
return nil, err
@@ -66,7 +66,7 @@ func (a JSONAuth) LoginPage() bool {
const reCaptchaAPI = "/recaptcha/api/siteverify"
// ReCaptcha identifies a recaptcha conenction.
// ReCaptcha identifies a recaptcha connection.
type ReCaptcha struct {
Host string `json:"host"`
Key string `json:"key"`
@@ -89,6 +89,7 @@ func (r *ReCaptcha) Ok(response string) (bool, error) {
if err != nil {
return false, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, nil

View File

@@ -14,7 +14,7 @@ const MethodNoAuth settings.AuthMethod = "noauth"
type NoAuth struct{}
// 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))
}

View File

@@ -18,7 +18,7 @@ type ProxyAuth struct {
}
// 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)
user, err := sto.Get(root, username)
if err == errors.ErrNotExist {

View File

@@ -18,8 +18,8 @@ type Storage struct {
}
// NewStorage creates a auth storage from a backend.
func NewStorage(back StorageBackend, users *users.Storage) *Storage {
return &Storage{back: back, users: users}
func NewStorage(back StorageBackend, userStore *users.Storage) *Storage {
return &Storage{back: back, users: userStore}
}
// Get wraps a StorageBackend.Get.

View File

@@ -14,7 +14,7 @@ var cmdsAddCmd = &cobra.Command{
Use: "add <event> <command>",
Short: "Add a command to run on a specific event",
Long: `Add a command to run on a specific event.`,
Args: cobra.MinimumNArgs(2),
Args: cobra.MinimumNArgs(2), //nolint:gomnd
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
s, err := d.store.Settings.Get()
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',
including 'index_end'.`,
Args: func(cmd *cobra.Command, args []string) error {
if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil {
if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil { //nolint:gomnd
return err
}
@@ -43,7 +43,7 @@ including 'index_end'.`,
i, err := strconv.Atoi(args[1])
checkErr(err)
f := i
if len(args) == 3 {
if len(args) == 3 { //nolint:gomnd
f, err = strconv.Atoi(args[2])
checkErr(err)
}

View File

@@ -8,11 +8,12 @@ import (
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func init() {
@@ -44,6 +45,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
}
//nolint:gocyclo
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther) {
method := settings.AuthMethod(mustGetString(flags, "auth.method"))
@@ -53,11 +55,12 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
for _, arg := range defaults {
switch def := arg.(type) {
case *settings.Settings:
method = settings.AuthMethod(def.AuthMethod)
method = def.AuthMethod
case auth.Auther:
ms, err := json.Marshal(def)
checkErr(err)
json.Unmarshal(ms, &defaultAuther)
err = json.Unmarshal(ms, &defaultAuther)
checkErr(err)
}
}
}
@@ -137,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, "\tTLS Cert:\t%s\n", ser.TLSCert)
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.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
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, "\tSorting:\n")
fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)

View File

@@ -6,9 +6,10 @@ import (
"path/filepath"
"reflect"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/spf13/cobra"
)
func init() {
@@ -55,7 +56,7 @@ The path must be for a json or yaml file.`,
checkErr(err)
var rawAuther interface{}
if filepath.Ext(args[0]) != ".json" {
if filepath.Ext(args[0]) != ".json" { //nolint:goconst
rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{}))
} else {
rawAuther = file.Auther

View File

@@ -2,10 +2,10 @@ package cmd
import (
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/spf13/cobra"
)
func init() {
@@ -31,7 +31,7 @@ override the options.`,
s := &settings.Settings{
Key: generateKey(),
Signup: mustGetBool(flags, "signup"),
Shell: strings.Split(strings.TrimSpace(mustGetString(flags, "shell")), " "),
Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
AuthMethod: authMethod,
Defaults: defaults,
Branding: settings.Branding{
@@ -61,7 +61,7 @@ override the options.`,
fmt.Printf(`
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.
`)
printSettings(ser, s, auther)

View File

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

View File

@@ -88,7 +88,7 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) {
short := cmd.Short
long := cmd.Long
if len(long) == 0 {
if long == "" {
long = short
}
@@ -106,21 +106,21 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) {
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example))
}
printOptions(buf, cmd, name)
printOptions(buf, cmd)
_, err := buf.WriteTo(w)
checkErr(err)
}
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
buf.WriteString("| Name | Shorthand | Usage |\n")
buf.WriteString("|------|-----------|-------|\n")
_, _ = buf.WriteString("| Name | Shorthand | Usage |\n")
_, _ = buf.WriteString("|------|-----------|-------|\n")
fs.VisitAll(func(f *pflag.Flag) {
buf.WriteString("|" + f.Name + "|" + f.Shorthand + "|" + f.Usage + "|\n")
_, _ = buf.WriteString("|" + f.Name + "|" + f.Shorthand + "|" + f.Usage + "|\n")
})
}
func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) {
func printOptions(buf *bytes.Buffer, cmd *cobra.Command) {
flags := cmd.NonInheritedFlags()
flags.SetOutput(buf)
if flags.HasAvailableFlags() {

View File

@@ -3,8 +3,9 @@ package cmd
import (
"fmt"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/users"
)
func init() {

View File

@@ -13,16 +13,20 @@ import (
"strings"
"syscall"
"github.com/filebrowser/filebrowser/v2/auth"
fbhttp "github.com/filebrowser/filebrowser/v2/http"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
v "github.com/spf13/viper"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/diskcache"
fbhttp "github.com/filebrowser/filebrowser/v2/http"
"github.com/filebrowser/filebrowser/v2/img"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
)
var (
@@ -31,6 +35,7 @@ var (
func init() {
cobra.OnInitialize(initConfig)
cobra.MousetrapHelpText = ""
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
@@ -54,7 +59,14 @@ func addServerFlags(flags *pflag.FlagSet) {
flags.StringP("key", "k", "", "tls key")
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.Uint32("socket-perm", 0666, "unix socket file permissions")
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{
@@ -102,6 +114,24 @@ user created with the credentials from options "username" and "password".`,
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)
setupLog(server.Log)
@@ -113,15 +143,23 @@ user created with the credentials from options "username" and "password".`,
var listener net.Listener
if server.Socket != "" {
switch {
case server.Socket != "":
listener, err = net.Listen("unix", server.Socket)
checkErr(err)
} else if server.TLSKey != "" && server.TLSCert != "" {
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey)
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
checkErr(err)
listener, err = tls.Listen("tcp", adr, &tls.Config{Certificates: []tls.Certificate{cer}})
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
checkErr(err)
} else {
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)
default:
listener, err = net.Listen("tcp", adr)
checkErr(err)
}
@@ -130,7 +168,7 @@ user created with the credentials from options "username" and "password".`,
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
go cleanupHandler(listener, sigc)
handler, err := fbhttp.NewHandler(d.store, server)
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server)
checkErr(err)
defer listener.Close()
@@ -142,13 +180,14 @@ user created with the credentials from options "username" and "password".`,
}, pythonConfig{allowNoDB: true}),
}
func cleanupHandler(listener net.Listener, c chan os.Signal) {
func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfacer
sig := <-c
log.Printf("Caught signal %s: shutting down.", sig)
listener.Close()
os.Exit(0)
}
//nolint:gocyclo
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
server, err := st.Settings.GetServer()
checkErr(err)
@@ -202,6 +241,18 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
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
}
@@ -258,8 +309,9 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
Signup: false,
CreateUserDir: false,
Defaults: settings.UserDefaults{
Scope: ".",
Locale: "en",
Scope: ".",
Locale: "en",
SingleClick: false,
Perm: users.Permissions{
Admin: false,
Execute: true,
@@ -348,5 +400,4 @@ func initConfig() {
} else {
cfgFile = "Using config file: " + v.ConfigFileUsed()
}
}

View File

@@ -3,15 +3,16 @@ package cmd
import (
"strconv"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
)
func init() {
rulesCmd.AddCommand(rulesRmCommand)
rulesRmCommand.Flags().Uint("index", 0, "index of rule to remove")
rulesRmCommand.MarkFlagRequired("index")
_ = rulesRmCommand.MarkFlagRequired("index")
}
var rulesRmCommand = &cobra.Command{
@@ -43,7 +44,7 @@ including 'index_end'.`,
i, err := strconv.Atoi(args[0])
checkErr(err)
f := i
if len(args) == 2 {
if len(args) == 2 { //nolint:gomnd
f, err = strconv.Atoi(args[1])
checkErr(err)
}

View File

@@ -3,12 +3,13 @@ package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/filebrowser/filebrowser/v2/rules"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func init() {
@@ -18,8 +19,8 @@ func init() {
}
var rulesCmd = &cobra.Command{
Use: "rules",
Short: "Rules management utility",
Use: "rules",
Short: "Rules management utility",
Long: `On each subcommand you'll have available at least two flags:
"username" and "id". You must either set only one of them
or none. If you set one of them, the command will apply to
@@ -28,14 +29,14 @@ rules.`,
Args: cobra.NoArgs,
}
func runRules(st *storage.Storage, cmd *cobra.Command, users func(*users.User), global func(*settings.Settings)) {
func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User), globalFn func(*settings.Settings)) {
id := getUserIdentifier(cmd.Flags())
if id != nil {
user, err := st.Users.Get("", id)
checkErr(err)
if users != nil {
users(user)
if usersFn != nil {
usersFn(user)
}
printRules(user.Rules, id)
@@ -45,8 +46,8 @@ func runRules(st *storage.Storage, cmd *cobra.Command, users func(*users.User),
s, err := st.Settings.Get()
checkErr(err)
if global != nil {
global(s)
if globalFn != nil {
globalFn(s)
}
printRules(s.Rules, id)
@@ -65,14 +66,14 @@ func getUserIdentifier(flags *pflag.FlagSet) interface{} {
return nil
}
func printRules(rules []rules.Rule, id interface{}) {
func printRules(rulez []rules.Rule, id interface{}) {
if id == nil {
fmt.Printf("Global Rules:\n\n")
} else {
fmt.Printf("Rules for user %v:\n\n", id)
}
for id, rule := range rules {
for id, rule := range rulez {
fmt.Printf("(%d) ", id)
if rule.Regex {
if rule.Allow {

View File

@@ -3,10 +3,11 @@ package cmd
import (
"regexp"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/rules"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
)
func init() {

View File

@@ -1,8 +1,9 @@
package cmd
import (
"github.com/filebrowser/filebrowser/v2/storage/bolt/importer"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/storage/bolt/importer"
)
func init() {
@@ -10,7 +11,7 @@ func init() {
upgradeCmd.Flags().String("old.database", "", "")
upgradeCmd.Flags().String("old.config", "", "")
upgradeCmd.MarkFlagRequired("old.database")
_ = upgradeCmd.MarkFlagRequired("old.database")
}
var upgradeCmd = &cobra.Command{

View File

@@ -7,10 +7,11 @@ import (
"strconv"
"text/tabwriter"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
)
func init() {
@@ -24,38 +25,39 @@ var usersCmd = &cobra.Command{
Args: cobra.NoArgs,
}
func printUsers(users []*users.User) {
func printUsers(usrs []*users.User) {
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 _, user := range users {
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",
user.ID,
user.Username,
user.Scope,
user.Locale,
user.ViewMode,
user.Perm.Admin,
user.Perm.Execute,
user.Perm.Create,
user.Perm.Rename,
user.Perm.Modify,
user.Perm.Delete,
user.Perm.Share,
user.Perm.Download,
user.LockPassword,
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%t\t\n",
u.ID,
u.Username,
u.Scope,
u.Locale,
u.ViewMode,
u.SingleClick,
u.Perm.Admin,
u.Perm.Execute,
u.Perm.Create,
u.Perm.Rename,
u.Perm.Modify,
u.Perm.Delete,
u.Perm.Share,
u.Perm.Download,
u.LockPassword,
)
}
w.Flush()
}
func parseUsernameOrID(arg string) (string, uint) {
id, err := strconv.ParseUint(arg, 10, 0)
func parseUsernameOrID(arg string) (username string, id uint) {
id64, err := strconv.ParseUint(arg, 10, 0)
if err != nil {
return arg, 0
}
return "", uint(id)
return "", uint(id64)
}
func addUserFlags(flags *pflag.FlagSet) {
@@ -74,6 +76,7 @@ func addUserFlags(flags *pflag.FlagSet) {
flags.String("scope", ".", "scope for users")
flags.String("locale", "en", "locale for users")
flags.String("viewMode", string(users.ListViewMode), "view mode for users")
flags.Bool("singleClick", false, "use single clicks only")
}
func getViewMode(flags *pflag.FlagSet) users.ViewMode {
@@ -84,6 +87,7 @@ func getViewMode(flags *pflag.FlagSet) users.ViewMode {
return viewMode
}
//nolint:gocyclo
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) {
visit := func(flag *pflag.Flag) {
switch flag.Name {
@@ -93,6 +97,8 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
defaults.Locale = mustGetString(flags, flag.Name)
case "viewMode":
defaults.ViewMode = getViewMode(flags)
case "singleClick":
defaults.SingleClick = mustGetBool(flags, flag.Name)
case "perm.admin":
defaults.Perm.Admin = mustGetBool(flags, flag.Name)
case "perm.execute":

View File

@@ -1,8 +1,9 @@
package cmd
import (
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/users"
)
func init() {
@@ -14,7 +15,7 @@ var usersAddCmd = &cobra.Command{
Use: "add <username> <password>",
Short: "Create a new user",
Long: `Create a new user and add it to the database.`,
Args: cobra.ExactArgs(2),
Args: cobra.ExactArgs(2), //nolint:gomnd
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
s, err := d.store.Settings.Get()
checkErr(err)
@@ -33,9 +34,9 @@ var usersAddCmd = &cobra.Command{
servSettings, err := d.store.Settings.GetServer()
checkErr(err)
//since getUserDefaults() polluted s.Defaults.Scope
//which makes the Scope not the one saved in the db
//we need the right s.Defaults.Scope here
// since getUserDefaults() polluted s.Defaults.Scope
// which makes the Scope not the one saved in the db
// we need the right s.Defaults.Scope here
s2, err := d.store.Settings.Get()
checkErr(err)

View File

@@ -1,8 +1,9 @@
package cmd
import (
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/users"
)
func init() {

View File

@@ -2,11 +2,13 @@ package cmd
import (
"errors"
"fmt"
"os"
"strconv"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/users"
)
func init() {
@@ -65,8 +67,7 @@ list or set it to 0.`,
// with the new username. If there is, print an error and cancel the
// operation
if user.Username != onDB.Username {
conflictuous, err := d.store.Users.Get("", user.Username)
if err == nil {
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
}
}
@@ -82,6 +83,7 @@ list or set it to 0.`,
}, pythonConfig{}),
}
func usernameConflictError(username string, original, new uint) error {
return errors.New("can't import user with ID " + strconv.Itoa(int(new)) + " and username \"" + username + "\" because the username is already registred with the user " + strconv.Itoa(int(original)))
func usernameConflictError(username string, originalID, newID uint) error {
return fmt.Errorf(`can't import user with ID %d and username "%s" because the username is already registred with the user %d`,
newID, username, originalID)
}

View File

@@ -1,9 +1,10 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
)
func init() {
@@ -40,17 +41,19 @@ options you want to change.`,
checkErr(err)
defaults := settings.UserDefaults{
Scope: user.Scope,
Locale: user.Locale,
ViewMode: user.ViewMode,
Perm: user.Perm,
Sorting: user.Sorting,
Commands: user.Commands,
Scope: user.Scope,
Locale: user.Locale,
ViewMode: user.ViewMode,
SingleClick: user.SingleClick,
Perm: user.Perm,
Sorting: user.Sorting,
Commands: user.Commands,
}
getUserDefaults(flags, &defaults, false)
user.Scope = defaults.Scope
user.Locale = defaults.Locale
user.ViewMode = defaults.ViewMode
user.SingleClick = defaults.SingleClick
user.Perm = defaults.Perm
user.Commands = defaults.Commands
user.Sorting = defaults.Sorting

View File

@@ -7,14 +7,16 @@ import (
"log"
"os"
"path/filepath"
"strings"
"github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/storage/bolt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
yaml "gopkg.in/yaml.v2"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/storage/bolt"
)
func checkErr(err error) {
@@ -70,7 +72,9 @@ func dbExists(path string) (bool, error) {
d := filepath.Dir(path)
_, err = os.Stat(d)
if os.IsNotExist(err) {
os.MkdirAll(d, 0700)
if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet
return false, err
}
return false, nil
}
}
@@ -113,7 +117,7 @@ func marshal(filename string, data interface{}) error {
encoder := json.NewEncoder(fd)
encoder.SetIndent("", " ")
return encoder.Encode(data)
case ".yml", ".yaml":
case ".yml", ".yaml": //nolint:goconst
encoder := yaml.NewEncoder(fd)
return encoder.Encode(data)
default:
@@ -175,3 +179,15 @@ func cleanUpMapValue(v interface{}) interface{} {
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
}

View File

@@ -3,8 +3,9 @@ package cmd
import (
"fmt"
"github.com/filebrowser/filebrowser/v2/version"
"github.com/spf13/cobra"
"github.com/filebrowser/filebrowser/v2/version"
)
func init() {

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

@@ -3,15 +3,19 @@ package errors
import "errors"
var (
ErrEmptyKey = errors.New("empty key")
ErrExist = errors.New("the resource already exists")
ErrNotExist = errors.New("the resource does not exist")
ErrEmptyPassword = errors.New("password is empty")
ErrEmptyUsername = errors.New("username is empty")
ErrEmptyRequest = errors.New("empty request")
ErrScopeIsRelative = errors.New("scope is a relative path")
ErrInvalidDataType = errors.New("invalid data type")
ErrIsDirectory = errors.New("file is directory")
ErrInvalidOption = errors.New("invalid option")
ErrInvalidAuthMethod = errors.New("invalid auth method")
ErrEmptyKey = errors.New("empty key")
ErrExist = errors.New("the resource already exists")
ErrNotExist = errors.New("the resource does not exist")
ErrEmptyPassword = errors.New("password is empty")
ErrEmptyUsername = errors.New("username is empty")
ErrEmptyRequest = errors.New("empty request")
ErrScopeIsRelative = errors.New("scope is a relative path")
ErrInvalidDataType = errors.New("invalid data type")
ErrIsDirectory = errors.New("file is directory")
ErrInvalidOption = errors.New("invalid option")
ErrInvalidAuthMethod = errors.New("invalid auth method")
ErrPermissionDenied = errors.New("permission denied")
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

@@ -1,8 +1,8 @@
package files
import (
"crypto/md5"
"crypto/sha1"
"crypto/md5" //nolint:gosec
"crypto/sha1" //nolint:gosec
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
@@ -17,9 +17,10 @@ import (
"strings"
"time"
"github.com/spf13/afero"
"github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/rules"
"github.com/spf13/afero"
)
// FileInfo describes a file.
@@ -37,15 +38,18 @@ type FileInfo struct {
Subtitles []string `json:"subtitles,omitempty"`
Content string `json:"content,omitempty"`
Checksums map[string]string `json:"checksums,omitempty"`
Token string `json:"token,omitempty"`
}
// FileOptions are the options when getting a file info.
type FileOptions struct {
Fs afero.Fs
Path string
Modify bool
Expand bool
Checker rules.Checker
Fs afero.Fs
Path string
Modify bool
Expand bool
ReadHeader bool
Token string
Checker rules.Checker
}
// NewFileInfo creates a File object from a path and a given user. This File
@@ -70,14 +74,18 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
IsDir: info.IsDir(),
Size: info.Size(),
Extension: filepath.Ext(info.Name()),
Token: opts.Token,
}
if opts.Expand {
if file.IsDir {
return file, file.readListing(opts.Checker)
if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
return nil, err
}
return file, nil
}
err = file.detectType(opts.Modify, true)
err = file.detectType(opts.Modify, true, true)
if err != nil {
return nil, err
}
@@ -105,6 +113,7 @@ func (i *FileInfo) Checksum(algo string) error {
var h hash.Hash
//nolint:gosec
switch algo {
case "md5":
h = md5.New()
@@ -127,11 +136,62 @@ func (i *FileInfo) Checksum(algo string) error {
return nil
}
func (i *FileInfo) detectType(modify, saveContent bool) error {
//nolint:goconst
//TODO: use constants
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.
// imagine the situation where a file in a dir with thousands
// of files couldn't be opened: we'd have immediately
// 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)
if err != nil {
log.Print(err)
@@ -148,44 +208,7 @@ func (i *FileInfo) detectType(modify, saveContent bool) error {
return nil
}
mimetype := mime.TypeByExtension(i.Extension)
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
return buffer[:n]
}
func (i *FileInfo) detectSubtitles() {
@@ -198,13 +221,13 @@ func (i *FileInfo) detectSubtitles() {
// TODO: detect multiple languages. Base.Lang.vtt
path := strings.TrimSuffix(i.Path, ext) + ".vtt"
if _, err := i.Fs.Stat(path); err == nil {
i.Subtitles = append(i.Subtitles, path)
fPath := strings.TrimSuffix(i.Path, ext) + ".vtt"
if _, err := i.Fs.Stat(fPath); err == nil {
i.Subtitles = append(i.Subtitles, fPath)
}
}
func (i *FileInfo) readListing(checker rules.Checker) error {
func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
afs := &afero.Afero{Fs: i.Fs}
dir, err := afs.ReadDir(i.Path)
if err != nil {
@@ -219,16 +242,16 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
for _, f := range dir {
name := f.Name()
path := path.Join(i.Path, name)
fPath := path.Join(i.Path, name)
if !checker.Check(path) {
if !checker.Check(fPath) {
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,
// we stay with the link information instead if the target's.
info, err := i.Fs.Stat(path)
// we stay with the link information instead of the target's.
info, err := i.Fs.Stat(fPath)
if err == nil {
f = info
}
@@ -242,7 +265,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
Mode: f.Mode(),
IsDir: f.IsDir(),
Extension: filepath.Ext(name),
Path: path,
Path: fPath,
}
if file.IsDir {
@@ -250,7 +273,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
} else {
listing.NumFiles++
err := file.detectType(true, false)
err := file.detectType(true, false, readHeader)
if err != nil {
return err
}

View File

@@ -16,8 +16,10 @@ type Listing struct {
}
// ApplySort applies the sort order using .Order and .Sort
//nolint:goconst
func (l Listing) ApplySort() {
// Check '.Order' to know how to sort
// TODO: use enum
if !l.Sorting.Asc {
switch l.Sorting.By {
case "name":
@@ -62,11 +64,11 @@ func (l byName) Swap(i, j int) {
// Treat upper and lower case equally
func (l byName) Less(i, j int) bool {
if l.Items[i].IsDir && !l.Items[j].IsDir {
return true
return l.Sorting.Asc
}
if !l.Items[i].IsDir && l.Items[j].IsDir {
return false
return !l.Sorting.Asc
}
return natural.Less(strings.ToLower(l.Items[j].Name), strings.ToLower(l.Items[i].Name))

View File

@@ -1,46 +1,59 @@
package files
import (
"os"
"unicode/utf8"
)
func isBinary(content []byte, n int) bool {
func isBinary(content []byte) bool {
maybeStr := string(content)
runeCnt := utf8.RuneCount(content)
runeIndex := 0
gotRuneErrCnt := 0
firstRuneErrIndex := -1
for _, b := range maybeStr {
const (
// 8 and below are control chars (e.g. backspace, null, eof, etc)
if b <= 8 {
maxControlCharsCode = 8
// 0xFFFD(65533) is the "error" Rune or "Unicode replacement character"
// see https://golang.org/pkg/unicode/utf8/#pkg-constants
unicodeReplacementChar = 0xFFFD
)
for _, b := range maybeStr {
if b <= maxControlCharsCode {
return true
}
// 0xFFFD(65533) is the "error" Rune or "Unicode replacement character"
// see https://golang.org/pkg/unicode/utf8/#pkg-constants
if b == 0xFFFD {
//if it is not the last (utf8.UTFMax - x) rune
if b == unicodeReplacementChar {
// if it is not the last (utf8.UTFMax - x) rune
if runeCnt > utf8.UTFMax && runeIndex < runeCnt-utf8.UTFMax {
return true
} else {
//else it is the last (utf8.UTFMax - x) rune
//there maybe Vxxx, VVxx, VVVx, thus, we may got max 3 0xFFFD rune (asume V is the byte we got)
//for Chinese, it can only be Vxx, VVx, we may got max 2 0xFFFD rune
gotRuneErrCnt++
}
// else it is the last (utf8.UTFMax - x) rune
// there maybe Vxxx, VVxx, VVVx, thus, we may got max 3 0xFFFD rune (assume V is the byte we got)
// for Chinese, it can only be Vxx, VVx, we may got max 2 0xFFFD rune
gotRuneErrCnt++
//mark the first time
if firstRuneErrIndex == -1 {
firstRuneErrIndex = runeIndex
}
// mark the first time
if firstRuneErrIndex == -1 {
firstRuneErrIndex = runeIndex
}
}
runeIndex++
}
//if last (utf8.UTFMax - x ) rune has the "error" Rune, but not all
// if last (utf8.UTFMax - x ) rune has the "error" Rune, but not all
if firstRuneErrIndex != -1 && gotRuneErrCnt != runeCnt-firstRuneErrIndex {
return true
}
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

@@ -9,7 +9,7 @@ import (
// CopyDir copies a directory from source to dest and all
// of its sub-directories. It doesn't stop if it finds an error
// during the copy. Returns an error if any.
func CopyDir(fs afero.Fs, source string, dest string) error {
func CopyDir(fs afero.Fs, source, dest string) error {
// Get properties of source.
srcinfo, err := fs.Stat(source)
if err != nil {

View File

@@ -2,14 +2,35 @@ package fileutils
import (
"io"
"os"
"path"
"path/filepath"
"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
// an error if any.
func CopyFile(fs afero.Fs, source string, dest string) error {
func CopyFile(fs afero.Fs, source, dest string) error {
// Open the source file.
src, err := fs.Open(source)
if err != nil {
@@ -25,7 +46,7 @@ func CopyFile(fs afero.Fs, source string, dest string) error {
}
// 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 {
return err
}
@@ -37,15 +58,71 @@ func CopyFile(fs afero.Fs, source string, dest string) error {
return err
}
// Copy the mode if the user can't
// open the file.
// Copy the mode
info, err := fs.Stat(source)
if err != nil {
err = fs.Chmod(dest, info.Mode())
if err != nil {
return err
}
return err
}
err = fs.Chmod(dest, info.Mode())
if err != nil {
return err
}
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)
}
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,24 +13,27 @@
"clipboard": "^2.0.4",
"js-base64": "^2.5.1",
"lodash.clonedeep": "^4.5.0",
"lodash.throttle": "^4.1.1",
"material-design-icons": "^3.0.1",
"moment": "^2.24.0",
"normalize.css": "^8.0.1",
"noty": "^3.2.0-beta",
"qrcode.vue": "^1.6.3",
"qrcode.vue": "^1.7.0",
"utif": "^3.1.0",
"vue": "^2.6.10",
"vue-i18n": "^8.15.0",
"vue-i18n": "^8.15.3",
"vue-lazyload": "^1.3.3",
"vue-router": "^3.1.3",
"vuex": "^3.1.1",
"vuex": "^3.1.2",
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.0.5",
"@vue/cli-plugin-eslint": "^4.0.5",
"@vue/cli-service": "^4.0.5",
"@vue/cli-plugin-babel": "^4.1.2",
"@vue/cli-plugin-eslint": "^4.1.1",
"@vue/cli-service": "^4.1.2",
"babel-eslint": "^10.0.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.2.3",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.1.2",
"vue-template-compiler": "^2.6.10"
},
"eslintConfig": {

View File

@@ -11,27 +11,28 @@
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
<link rel="icon" type="image/png" sizes="32x32" href="/[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
<!-- Add to home screen for Android and modern mobile browsers -->
<link rel="manifest" id="manifestPlaceholder">
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
<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-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="assets">
<link rel="apple-touch-icon" href="/[{[ .StaticURL ]}]/img/icons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png">
<!-- Add to home screen for Windows -->
<meta name="msapplication-TileImage" content="/[{[ .StaticURL ]}]/img/icons/msapplication-icon-144x144.png">
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png">
<meta name="msapplication-TileColor" content="#2979ff">
<!-- Inject Some Variables and generate the manifest json -->
<script>
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
var fullStaticURL = window.location.origin + "/" + window.FileBrowser.StaticURL;
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
var dynamicManifest = {
"name": window.FileBrowser.Name || 'File Browser',
"short_name": window.FileBrowser.Name || 'File Browser',
@@ -133,8 +134,11 @@
</div>
</div>
[{[ if .Theme -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css" />
[{[ end ]}]
[{[ if .CSS -]}]
<link rel="stylesheet" href="/[{[ .StaticURL ]}]/custom.css" />
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
[{[ end ]}]
</body>
</html>

View File

@@ -0,0 +1,201 @@
:root {
--background: #141D24;
--surfacePrimary: #20292F;
--surfaceSecondary: #3A4147;
--divider: rgba(255, 255, 255, 0.12);
--icon: #ffffff;
--textPrimary: rgba(255, 255, 255, 0.87);
--textSecondary: rgba(255, 255, 255, 0.6);
}
body {
background: var(--background);
color: var(--textPrimary);
}
#loading {
background: var(--background);
}
#loading .spinner div, #previewer .loading .spinner div {
background: var(--icon);
}
#login {
background: var(--background);
}
header {
background: var(--surfacePrimary);
}
#search #input {
background: var(--surfaceSecondary);
border-color: var(--surfacePrimary);
}
#search #input input::placeholder {
color: var(--textSecondary);
}
#search.active #input {
background: var(--surfacePrimary);
}
#search.active input {
color: var(--textPrimary);
}
#search #result {
background: var(--background);
color: var(--textPrimary);
}
#search .boxes {
background: var(--surfaceSecondary);
}
#search .boxes h3 {
color: var(--textPrimary);
}
.action {
color: var(--textPrimary) !important;
}
.action:hover {
background-color: rgba(255, 255, 255, .1);
}
.action i {
color: var(--icon) !important;
}
.action .counter {
border-color: var(--surfacePrimary);
}
nav > div {
border-color: var(--divider);
}
#breadcrumbs {
border-color: var(--divider);
color: var(--textPrimary) !important;
}
#breadcrumbs span {
color: var(--textPrimary) !important;
}
#listing .item {
background: var(--surfacePrimary);
color: var(--textPrimary);
border-color: var(--divider) !important;
}
#listing .item i {
color: var(--icon);
}
#listing .item .modified {
color: var(--textSecondary);
}
#listing h2,
#listing.list .header span {
color: var(--textPrimary) !important;
}
#listing.list .header span {
color: var(--textPrimary);
}
#listing.list .header i {
color: var(--icon);
}
#listing.list .item.header {
background: var(--background);
}
.message {
color: var(--textPrimary);
}
.card {
background: var(--surfacePrimary);
color: var(--textPrimary);
}
.button--flat:hover {
background: var(--surfaceSecondary);
}
.card h3,
.dashboard #nav,
.dashboard p label {
color: var(--textPrimary);
}
.card#share ul li input,
.card#share ul li select,
.input {
background: var(--surfaceSecondary);
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;
}
.input--green {
background: #147A41;
}
.dashboard #nav li,
.collapsible {
border-color: var(--divider);
}
.collapsible > label * {
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 {
background: var(--surfacePrimary);
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) {
#file-selection {
background: var(--surfaceSecondary) !important;
}
#file-selection span {
color: var(--textPrimary) !important;
}
nav {
background: var(--surfaceSecondary) !important;
}
#dropdown {
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)
if (res.status !== 200) {
throw new Error(res.responseText)
throw new Error(await res.text())
} else {
return res
}
@@ -58,7 +58,7 @@ export async function put (url, content = '') {
}
export function download (format, ...files) {
let url = `${baseURL}/api/raw`
let url = store.getters['isSharing'] ? `${baseURL}/api/public/dl/${store.state.hash}` : `${baseURL}/api/raw`
if (files.length === 1) {
url += removePrefix(files[0]) + '?'
@@ -77,14 +77,24 @@ export function download (format, ...files) {
if (format !== null) {
url += `algo=${format}&`
}
if (store.state.jwt !== ''){
url += `auth=${store.state.jwt}&`
}
if (store.state.token !== ''){
url += `token=${store.state.token}`
}
url += `auth=${store.state.jwt}`
window.open(url)
}
export async function post (url, content = '', overwrite = false, onupload) {
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) => {
let request = new XMLHttpRequest()
request.open('POST', `${baseURL}/api/resources${url}?override=${overwrite}`, true)
@@ -94,9 +104,6 @@ export async function post (url, content = '', overwrite = false, 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 = () => {
if (request.status === 200) {
resolve(request.responseText)
@@ -111,30 +118,29 @@ export async function post (url, content = '', overwrite = false, onupload) {
reject(error)
}
request.send(content)
// Upload is done no more message before closing the tab
}).finally(() => { window.onbeforeunload = null })
request.send(bufferContent || content)
})
}
function moveCopy (items, copy = false) {
function moveCopy (items, copy = false, overwrite = false, rename = false) {
let promises = []
for (let item of items) {
const from = removePrefix(item.from)
const from = item.from
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'))
}
return Promise.all(promises)
}
export function move (items) {
return moveCopy(items)
export function move (items, overwrite = false, rename = false) {
return moveCopy(items, false, overwrite, rename)
}
export function copy (items) {
return moveCopy(items, true)
export function copy (items, overwrite = false, rename = false) {
return moveCopy(items, true, overwrite, rename)
}
export async function checksum (url, algo) {

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) {
url = removePrefix(url)
export default async function search (base, query) {
base = removePrefix(base)
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,13 @@
import { fetchURL, fetchJSON, removePrefix } from './utils'
export async function getHash(hash) {
return fetchJSON(`/api/public/share/${hash}`)
export async function list() {
return fetchJSON('/api/shares')
}
export async function getHash(hash, password = "") {
return fetchJSON(`/api/public/share/${hash}`, {
headers: {'X-SHARE-PASSWORD': password},
})
}
export async function get(url) {
@@ -19,14 +25,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 = `/api/share${url}`
if (expires !== '') {
url += `?expires=${expires}&unit=${unit}`
}
let body = '{}';
if (password != '' || expires !== '' || unit !== 'hours') {
body = JSON.stringify({password: password, expires: expires, unit: unit})
}
return fetchJSON(url, {
method: 'POST'
method: 'POST',
body: body,
})
}

View File

@@ -36,6 +36,8 @@ export async function fetchJSON (url, opts) {
export function removePrefix (url) {
if (url.startsWith('/files')) {
url = url.slice(6)
} else if (store.getters['isSharing']) {
url = url.slice(7 + store.state.hash.length)
}
if (url === '') url = '/'

View File

@@ -1,5 +1,5 @@
<template>
<header>
<header v-if="!isEditor && !isPreview">
<div>
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action">
<i class="material-icons">menu</i>
@@ -8,21 +8,17 @@
<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">
<template v-if="isLogged || isSharing">
<button v-show="!isSharing" @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">
<div id="file-selection" v-if="isMobile && isListing && !isSharing">
<span v-if="selectedCount > 0">{{ selectedCount }} selected</span>
<share-button v-show="showShareButton"></share-button>
<rename-button v-show="showRenameButton"></rename-button>
@@ -41,13 +37,13 @@
<delete-button v-show="showDeleteButton"></delete-button>
</div>
<shell-button v-show="user.perm.execute" />
<shell-button v-if="isExecEnabled && !isSharing && 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="openSelect" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action">
<button v-show="isListing || (isSharing && req.isDir)" @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>
@@ -72,7 +68,7 @@ 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 { logoURL, enableExec } from '@/utils/constants'
import * as api from '@/api'
import buttons from '@/utils/buttons'
@@ -112,8 +108,10 @@ export default {
'selectedCount',
'isFiles',
'isEditor',
'isPreview',
'isListing',
'isLogged'
'isLogged',
'isSharing'
]),
...mapState([
'req',
@@ -123,17 +121,15 @@ export default {
'multiple'
]),
logoURL: () => logoURL,
isExecEnabled: () => enableExec,
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
return (this.isFiles && this.user.perm.download) || (this.isSharing && this.selectedCount > 0)
},
showDeleteButton () {
return this.isFiles && (this.isListing
@@ -161,7 +157,7 @@ export default {
: this.user.perm.create)
},
showMore () {
return this.isFiles && this.$store.state.show === 'more'
return (this.isFiles || this.isSharing) && this.$store.state.show === 'more'
},
showOverlay () {
return this.showMore
@@ -177,8 +173,8 @@ export default {
openSearch () {
this.$store.commit('showHover', 'search')
},
openSelect () {
this.$store.commit('multiple', true)
toggleMultipleSelection () {
this.$store.commit('multiple', !this.multiple)
this.resetPrompts()
},
resetPrompts () {

View File

@@ -49,7 +49,7 @@
</template>
<ul v-show="results.length > 0">
<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-else class="material-icons">insert_drive_file</i>
<span>./{{ s.path }}</span>
@@ -136,12 +136,6 @@ export default {
}
},
mounted() {
window.addEventListener("keydown", event => {
if (event.keyCode === 27) {
this.closeHovers()
}
})
this.$refs.result.addEventListener('scroll', event => {
if (event.target.offsetHeight + event.target.scrollTop >= event.target.scrollHeight - 100) {
this.resultsCount += 50
@@ -189,8 +183,12 @@ export default {
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
}
}

View File

@@ -24,7 +24,7 @@
<span>{{ $t('sidebar.settings') }}</span>
</router-link>
<button v-if="!noAuth" @click="logout" class="action" id="logout" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')">
<button v-if="authMethod == 'json'" @click="logout" class="action" id="logout" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')">
<i class="material-icons">exit_to_app</i>
<span>{{ $t('sidebar.logout') }}</span>
</button>
@@ -56,7 +56,7 @@
<script>
import { mapState, mapGetters } from 'vuex'
import * as auth from '@/utils/auth'
import { version, signup, disableExternal, noAuth } from '@/utils/constants'
import { version, signup, disableExternal, noAuth, authMethod } from '@/utils/constants'
export default {
name: 'sidebar',
@@ -69,7 +69,8 @@ export default {
signup: () => signup,
version: () => version,
disableExternal: () => disableExternal,
noAuth: () => noAuth
noAuth: () => noAuth,
authMethod: () => authMethod
},
methods: {
help () {

View File

@@ -14,11 +14,11 @@ export default {
name: 'download-button',
computed: {
...mapState(['req', 'selected']),
...mapGetters(['isListing', 'selectedCount'])
...mapGetters(['isListing', 'selectedCount', 'isSharing'])
},
methods: {
download: function () {
if (!this.isListing) {
if (!this.isListing && !this.isSharing) {
api.download(null, this.$route.path)
return
}

View File

@@ -0,0 +1,22 @@
<template>
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="$emit('change-size')">
<i class="material-icons">{{ this.icon }}</i>
<span>{{ $t('buttons.info') }}</span>
</button>
</template>
<script>
export default {
name: 'preview-size-button',
props: [ 'size' ],
computed: {
icon () {
if (this.size) {
return 'photo_size_select_large'
}
return 'hd'
}
}
}
</script>

View File

@@ -10,7 +10,11 @@ export default {
name: 'upload-button',
methods: {
upload: function () {
document.getElementById('upload-input').click()
if (typeof(DataTransferItem.prototype.webkitGetAsEntry) !== 'undefined') {
this.$store.commit('showHover', 'upload')
} else {
document.getElementById('upload-input').click();
}
}
}
}

View File

@@ -1,52 +1,108 @@
<template>
<form id="editor"></form>
<div id="editor-container">
<div class="bar">
<button @click="back" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close" class="action">
<i class="material-icons">close</i>
</button>
<div class="title">
<span>{{ req.name }}</span>
</div>
<button @click="save" v-show="user.perm.modify" :aria-label="$t('buttons.save')" :title="$t('buttons.save')" id="save-button" class="action">
<i class="material-icons">save</i>
</button>
</div>
<div id="breadcrumbs">
<span><i class="material-icons">home</i></span>
<span v-for="(link, index) in breadcrumbs" :key="index">
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
<span>{{ link.name }}</span>
</span>
</div>
<form id="editor"></form>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { files as api } from '@/api'
import buttons from '@/utils/buttons'
import url from '@/utils/url'
import ace from 'ace-builds/src-min-noconflict/ace.js'
import modelist from 'ace-builds/src-min-noconflict/ext-modelist.js'
import 'ace-builds/webpack-resolver'
import { theme } from '@/utils/constants'
export default {
name: 'editor',
computed: {
...mapState(['req'])
},
data: function () {
return {
content: null,
editor: null
return {}
},
computed: {
...mapState(['req', 'user']),
breadcrumbs () {
let parts = this.$route.path.split('/')
if (parts[0] === '') {
parts.shift()
}
if (parts[parts.length - 1] === '') {
parts.pop()
}
let breadcrumbs = []
for (let i = 0; i < parts.length; i++) {
breadcrumbs.push({ name: decodeURIComponent(parts[i]) })
}
breadcrumbs.shift()
if (breadcrumbs.length > 3) {
while (breadcrumbs.length !== 4) {
breadcrumbs.shift()
}
breadcrumbs[0].name = '...'
}
return breadcrumbs
}
},
created () {
window.addEventListener('keydown', this.keyEvent)
document.getElementById('save-button').addEventListener('click', this.save)
},
beforeDestroy () {
window.removeEventListener('keydown', this.keyEvent)
document.getElementById('save-button').removeEventListener('click', this.save)
this.editor.destroy();
},
mounted: function () {
if (this.req.content === undefined || this.req.content === null) {
this.req.content = ''
}
mounted: function () {
const fileContent = this.req.content || '';
this.editor = ace.edit('editor', {
maxLines: Infinity,
minLines: 20,
value: this.req.content,
value: fileContent,
showPrintMargin: false,
readOnly: this.req.type === 'textImmutable',
theme: 'ace/theme/chrome',
mode: modelist.getModeForPath(this.req.name).mode,
wrap: true
})
if (theme == 'dark') {
this.editor.setTheme("ace/theme/twilight");
}
},
methods: {
back () {
let uri = url.removeLastDir(this.$route.path) + '/'
this.$router.push({ path: uri })
},
keyEvent (event) {
if (!event.ctrlKey && !event.metaKey) {
return

View File

@@ -10,10 +10,13 @@
@mouseup="mouseUp"
@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>
</template>
<script>
import throttle from 'lodash.throttle'
import UTIF from 'utif'
export default {
props: {
src: String,
@@ -50,10 +53,18 @@ export default {
inDrag: false,
lastTouchDistance: 0,
moveDisabled: false,
disabledTimer: null
disabledTimer: null,
imageLoaded: false,
position: {
center: { x: 0, y: 0 },
relative: { x: 0, y: 0 }
}
}
},
mounted() {
if (!this.decodeUTIF()) {
this.$refs.imgex.src = this.src
}
let container = this.$refs.container
this.classList.forEach(className => container.classList.add(className))
// set width and height if they are zero
@@ -63,24 +74,68 @@ export default {
if (getComputedStyle(container).height === "0px") {
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: {
// 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() {
let container = this.$refs.container
let img = this.$refs.imgex
let rate = Math.min(
container.clientWidth / img.clientWidth,
container.clientHeight / img.clientHeight
)
if (!this.autofill && rate > 1) {
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 )
this.position.center.x = Math.floor((container.clientWidth - img.clientWidth) / 2)
this.position.center.y = Math.floor((container.clientHeight - img.clientHeight) / 2)
img.style.left = this.position.center.x + 'px'
img.style.top = this.position.center.y + 'px'
},
mousedownStart(event) {
this.lastX = null
@@ -101,6 +156,15 @@ export default {
this.lastX = null
this.lastY = 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()
},
zoomAuto(event) {
@@ -114,6 +178,7 @@ export default {
default:
case 4:
this.scale = 1
this.setCenter()
break
}
this.setZoom()
@@ -159,8 +224,22 @@ export default {
},
doMove(x, y) {
let style = this.$refs.imgex.style
style.left = `${this.pxStringToNumber(style.left) + x}px`
style.top = `${this.pxStringToNumber(style.top) + y}px`
let posX = this.pxStringToNumber(style.left) + x
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) {
this.scale += (event.wheelDeltaY / 100) * this.zoomStep
@@ -185,9 +264,20 @@ export default {
}
.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;
top: 0;
position: absolute;
transition: transform 0.1s ease;
}
</style>

View File

@@ -5,11 +5,10 @@
<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">
:class="user.viewMode">
<div>
<div class="item header">
<div></div>
@@ -75,6 +74,7 @@
</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>
@@ -90,19 +90,19 @@ import { mapState, mapMutations } from 'vuex'
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'
import * as upload from '@/utils/upload'
export default {
name: 'listing',
components: { Item },
data: function () {
return {
show: 50
showLimit: 50,
dragCounter: 0
}
},
computed: {
...mapState(['req', 'selected', 'user']),
...mapState(['req', 'selected', 'user', 'show']),
nameSorted () {
return (this.req.sorting.by === 'name')
},
@@ -130,14 +130,14 @@ export default {
return { dirs, files }
},
dirs () {
return this.items.dirs.slice(0, this.show)
return this.items.dirs.slice(0, this.showLimit)
},
files () {
let show = this.show - this.items.dirs.length
let showLimit = this.showLimit - this.items.dirs.length
if (show < 0) show = 0
if (showLimit < 0) showLimit = 0
return this.items.files.slice(0, show)
return this.items.files.slice(0, showLimit)
},
nameIcon () {
if (this.nameSorted && !this.ascOrdered) {
@@ -170,6 +170,8 @@ export default {
window.addEventListener('resize', this.resizeEvent)
window.addEventListener('scroll', this.scrollEvent)
document.addEventListener('dragover', this.preventDefault)
document.addEventListener('dragenter', this.dragEnter)
document.addEventListener('dragleave', this.dragLeave)
document.addEventListener('drop', this.drop)
},
beforeDestroy () {
@@ -178,14 +180,20 @@ export default {
window.removeEventListener('resize', this.resizeEvent)
window.removeEventListener('scroll', this.scrollEvent)
document.removeEventListener('dragover', this.preventDefault)
document.removeEventListener('dragenter', this.dragEnter)
document.removeEventListener('dragleave', this.dragLeave)
document.removeEventListener('drop', this.drop)
},
methods: {
...mapMutations([ 'updateUser' ]),
...mapMutations([ 'updateUser', 'addSelected' ]),
base64: function (name) {
return window.btoa(unescape(encodeURIComponent(name)))
},
keyEvent (event) {
if (this.show !== null) {
return
}
if (!event.ctrlKey && !event.metaKey) {
return
}
@@ -204,6 +212,19 @@ export default {
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) {
@@ -230,7 +251,8 @@ export default {
this.$store.commit('updateClipboard', {
key: key,
items: items
items: items,
path: this.$route.path
})
},
paste (event) {
@@ -243,23 +265,56 @@ export default {
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 })
items.push({ from, to, name: item.name })
}
if (items.length === 0) {
return
}
if (this.$store.state.clipboard.key === 'x') {
api.move(items).then(() => {
let action = (overwrite, rename) => {
api.copy(items, overwrite, rename).then(() => {
this.$store.commit('setReload', true)
}).catch(this.$showError)
}
if (this.$store.state.clipboard.key === 'x') {
action = (overwrite, rename) => {
api.move(items, overwrite, rename).then(() => {
this.$store.commit('resetClipboard')
this.$store.commit('setReload', true)
}).catch(this.$showError)
}
}
if (this.$store.state.clipboard.path == this.$route.path) {
action(false, true)
return
}
api.copy(items).then(() => {
this.$store.commit('setReload', true)
}).catch(this.$showError)
let conflict = upload.checkConflict(items, this.req.items)
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)
},
resizeEvent () {
// Update the columns size based on the window width.
@@ -270,10 +325,12 @@ export default {
},
scrollEvent () {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
this.show += 50
this.showLimit += 50
}
},
dragEnter () {
this.dragCounter++
// When the user starts dragging an item, put every
// file on the listing with 50% opacity.
let items = document.getElementsByClassName('item')
@@ -282,18 +339,22 @@ export default {
file.style.opacity = 0.5
})
},
dragEnd () {
this.resetOpacity()
dragLeave () {
this.dragCounter--
if (this.dragCounter == 0) {
this.resetOpacity()
}
},
drop: function (event) {
drop: async function (event) {
event.preventDefault()
this.dragCounter = 0
this.resetOpacity()
let dt = event.dataTransfer
let files = dt.files
let el = event.target
if (files.length <= 0) return
if (dt.files.length <= 0) return
for (let i = 0; i < 5; i++) {
if (el !== null && !el.classList.contains('item')) {
@@ -306,51 +367,65 @@ export default {
base = el.querySelector('.name').innerHTML + '/'
}
let files = await upload.scanFiles(dt)
let path = this.$route.path.endsWith('/') ? this.$route.path + base : this.$route.path + '/' + base
let items = this.req.items
if (base !== '') {
api.fetch(this.$route.path + base)
.then(req => {
this.checkConflict(files, req.items, base)
})
.catch(this.$showError)
try {
items = (await api.fetch(path)).items
} catch (error) {
this.$showError(error)
}
}
let conflict = upload.checkConflict(files, items)
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace',
confirm: (event) => {
event.preventDefault()
this.$store.commit('closeHovers')
upload.handleFiles(files, path, true)
}
})
return
}
this.checkConflict(files, this.req.items, base)
},
checkConflict (files, items, base) {
if (typeof items === 'undefined' || items === null) {
items = []
}
let conflict = false
for (let i = 0; i < files.length; i++) {
let res = items.findIndex(function hasConflict (element) {
return (element.name === this)
}, files[i].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)
}
})
upload.handleFiles(files, path)
},
uploadInput (event) {
this.checkConflict(event.currentTarget.files, this.req.items, '')
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
}
}
let path = this.$route.path.endsWith('/') ? this.$route.path : this.$route.path + '/'
let conflict = upload.checkConflict(files, this.req.items)
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace',
confirm: (event) => {
event.preventDefault()
this.$store.commit('closeHovers')
upload.handleFiles(files, path, true)
}
})
return
}
upload.handleFiles(files, path)
},
resetOpacity () {
let items = document.getElementsByClassName('item')
@@ -359,45 +434,6 @@ export default {
file.style.opacity = 1
})
},
handleFiles (files, base, overwrite = false) {
buttons.loading('upload')
let promises = []
let progress = new Array(files.length).fill(0)
let onupload = (id) => (event) => {
progress[id] = (event.loaded / event.total) * 100
let sum = 0
for (let i = 0; i < progress.length; i++) {
sum += progress[i]
}
this.$store.commit('setProgress', Math.ceil(sum / progress.length))
}
for (let i = 0; i < files.length; i++) {
let file = files[i]
let filenameEncoded = url.encodeRFC5987ValueChars(file.name)
promises.push(api.post(this.$route.path + base + filenameEncoded, file, overwrite, onupload(i)))
}
let finish = () => {
buttons.success('upload')
this.$store.commit('setProgress', 0)
}
Promise.all(promises)
.then(() => {
finish()
this.$store.commit('setReload', true)
})
.catch(error => {
finish()
this.$showError(error)
})
return false
},
async sort (by) {
let asc = false

View File

@@ -2,18 +2,19 @@
<div class="item"
role="button"
tabindex="0"
draggable="true"
:draggable="isDraggable"
@dragstart="dragStart"
@dragover="dragOver"
@drop="drop"
@click="click"
@dblclick="open"
@click="itemClick"
@dblclick="dblclick"
@touchstart="touchstart"
:data-dir="isDir"
:aria-label="name"
:aria-selected="isSelected">
<div>
<i class="material-icons">{{ icon }}</i>
<img v-if="type==='image' && isThumbsEnabled && !isSharing" v-lazy="thumbnailUrl">
<i v-else class="material-icons">{{ icon }}</i>
</div>
<div>
@@ -30,10 +31,12 @@
</template>
<script>
import { baseURL, enableThumbs } from '@/utils/constants'
import { mapMutations, mapGetters, mapState } from 'vuex'
import filesize from 'filesize'
import moment from 'moment'
import { files as api } from '@/api'
import * as upload from '@/utils/upload'
export default {
name: 'item',
@@ -44,8 +47,12 @@ export default {
},
props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index'],
computed: {
...mapState(['selected', 'req']),
...mapGetters(['selectedCount']),
...mapState(['user', 'selected', 'req', 'jwt']),
...mapGetters(['selectedCount', 'isSharing']),
singleClick () {
if (this.isSharing) return false
return this.user.singleClick
},
isSelected () {
return (this.selected.indexOf(this.index) !== -1)
},
@@ -56,8 +63,11 @@ export default {
if (this.type === 'video') return 'movie'
return 'insert_drive_file'
},
isDraggable () {
return !this.isSharing && this.user.perm.rename
},
canDrop () {
if (!this.isDir) return false
if (!this.isDir || this.isSharing) return false
for (let i of this.selected) {
if (this.req.items[i].url === this.url) {
@@ -66,6 +76,13 @@ export default {
}
return true
},
thumbnailUrl () {
const path = this.url.replace(/^\/files\//, '')
return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true`
},
isThumbsEnabled () {
return enableThumbs
}
},
methods: {
@@ -101,35 +118,74 @@ export default {
el.style.opacity = 1
},
drop: function (event) {
drop: async function (event) {
if (!this.canDrop) return
event.preventDefault()
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 = []
for (let i of this.selected) {
items.push({
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
})
}
let base = el.querySelector('.name').innerHTML + '/'
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)
}).catch(this.$showError)
}
api.move(items)
.then(() => {
this.$store.commit('setReload', true)
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) {
if (this.selectedCount !== 0) event.preventDefault()
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault()
if (this.$store.state.selected.indexOf(this.index) !== -1) {
this.removeSelected(this.index)
return
}
if (event.shiftKey && this.selected.length === 1) {
if (event.shiftKey && this.selected.length > 0) {
let fi = 0
let la = 0
@@ -142,15 +198,20 @@ export default {
}
for (; fi <= la; fi++) {
this.addSelected(fi)
if (this.$store.state.selected.indexOf(fi) == -1) {
this.addSelected(fi)
}
}
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)
},
dblclick: function () {
if (!this.singleClick) this.open()
},
touchstart () {
setTimeout(() => {
this.touches = 0
@@ -166,4 +227,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -5,10 +5,27 @@
<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 class="title">{{ this.name }}</div>
<preview-size-button v-if="isResizeEnabled && this.req.type === 'image'" @change-size="toggleSize" v-bind:size="fullSize" :disabled="loading"></preview-size-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>
<div id="dropdown" :class="{ active : showMore }">
<rename-button :disabled="loading" v-if="user.perm.rename"></rename-button>
<delete-button :disabled="loading" v-if="user.perm.delete"></delete-button>
<download-button :disabled="loading" v-if="user.perm.download"></download-button>
<info-button :disabled="loading"></info-button>
</div>
</div>
<div class="loading" v-if="loading">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
</div>
<button class="action" @click="prev" v-show="hasPrevious" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')">
@@ -18,33 +35,38 @@
<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>
<template v-if="!loading">
<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.toLowerCase() == '.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>
</template>
<div v-show="showMore" @click="resetPrompts" class="overlay"></div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import { baseURL } from '@/utils/constants'
import { baseURL, resizePreview } from '@/utils/constants'
import { files as api } from '@/api'
import PreviewSizeButton from '@/components/buttons/PreviewSize'
import InfoButton from '@/components/buttons/Info'
import DeleteButton from '@/components/buttons/Delete'
import RenameButton from '@/components/buttons/Rename'
@@ -61,6 +83,7 @@ const mediaTypes = [
export default {
name: 'preview',
components: {
PreviewSizeButton,
InfoButton,
DeleteButton,
RenameButton,
@@ -72,11 +95,13 @@ export default {
previousLink: '',
nextLink: '',
listing: null,
subtitles: []
name: '',
subtitles: [],
fullSize: false
}
},
computed: {
...mapState(['req', 'user', 'oldReq', 'jwt']),
...mapState(['req', 'user', 'oldReq', 'jwt', 'loading', 'show']),
hasPrevious () {
return (this.previousLink !== '')
},
@@ -84,36 +109,55 @@ export default {
return (this.nextLink !== '')
},
download () {
return `${baseURL}/api/raw${this.req.path}?auth=${this.jwt}`
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
},
previewUrl () {
if (this.req.type === 'image' && !this.fullSize) {
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}`
}
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
},
raw () {
return `${this.download}&inline=true`
return `${this.previewUrl}&inline=true`
},
showMore () {
return this.$store.state.show === 'more'
},
isResizeEnabled () {
return resizePreview
}
},
watch: {
$route: function () {
this.updatePreview()
}
},
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)
}
window.addEventListener('keydown', this.key)
this.$store.commit('setPreviewMode', true)
this.listing = this.oldReq.items
this.$root.$on('preview-deleted', this.deleted)
this.updatePreview()
},
beforeDestroy () {
window.removeEventListener('keyup', this.key)
window.removeEventListener('keydown', this.key)
this.$store.commit('setPreviewMode', false)
this.$root.$off('preview-deleted', this.deleted)
},
methods: {
deleted () {
this.listing = this.listing.filter(item => item.name !== this.name)
if (this.hasNext) {
this.next()
} else if (!this.hasPrevious && !this.hasNext) {
this.back()
} else {
this.prev()
}
},
back () {
this.$store.commit('setPreviewMode', false)
let uri = url.removeLastDir(this.$route.path) + '/'
this.$router.push({ path: uri })
},
@@ -124,36 +168,70 @@ export default {
this.$router.push({ path: this.nextLink })
},
key (event) {
event.preventDefault()
if (this.show !== null) {
return
}
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()
} else if (event.which === 27) { // esc
this.back()
}
},
updateLinks (items) {
for (let i = 0; i < items.length; i++) {
if (items[i].name !== this.req.name) {
async updatePreview () {
if (this.req.subtitles) {
this.subtitles = this.req.subtitles.map(sub => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`)
}
let dirs = this.$route.fullPath.split("/")
this.name = decodeURIComponent(dirs[dirs.length - 1])
if (!this.listing) {
try {
const path = url.removeLastDir(this.$route.path)
const res = await api.fetch(path)
this.listing = res.items
} catch (e) {
this.$showError(e)
}
}
this.previousLink = ''
this.nextLink = ''
for (let i = 0; i < this.listing.length; i++) {
if (this.listing[i].name !== this.name) {
continue
}
for (let j = i - 1; j >= 0; j--) {
if (mediaTypes.includes(items[j].type)) {
this.previousLink = items[j].url
if (mediaTypes.includes(this.listing[j].type)) {
this.previousLink = this.listing[j].url
break
}
}
for (let j = i + 1; j < items.length; j++) {
if (mediaTypes.includes(items[j].type)) {
this.nextLink = items[j].url
for (let j = i + 1; j < this.listing.length; j++) {
if (mediaTypes.includes(this.listing[j].type)) {
this.nextLink = this.listing[j].url
break
}
}
return
}
},
openMore () {
this.$store.commit('showHover', 'more')
},
resetPrompts () {
this.$store.commit('closeHovers')
},
toggleSize () {
this.fullSize = !this.fullSize
}
}
}

View File

@@ -16,7 +16,6 @@
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="button button--flat"
@click="copy"
:disabled="$route.path === dest"
:aria-label="$t('buttons.copy')"
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
</div>
@@ -28,6 +27,7 @@ import { mapState } from 'vuex'
import FileList from './FileList'
import { files as api } from '@/api'
import buttons from '@/utils/buttons'
import * as upload from '@/utils/upload'
export default {
name: 'copy',
@@ -42,25 +42,66 @@ export default {
methods: {
copy: async function (event) {
event.preventDefault()
buttons.loading('copy')
let items = []
// Create a new promise for each file.
for (let item of this.selected) {
items.push({
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 {
await api.copy(items)
buttons.success('copy')
this.$router.push({ path: this.dest })
} catch (e) {
buttons.done('copy')
this.$showError(e)
let action = async (overwrite, rename) => {
buttons.loading('copy')
await api.copy(items, overwrite, rename).then(() => {
buttons.success('copy')
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,7 +20,6 @@
<script>
import {mapGetters, mapMutations, mapState} from 'vuex'
import { files as api } from '@/api'
import url from '@/utils/url'
import buttons from '@/utils/buttons'
export default {
@@ -32,17 +31,20 @@ export default {
methods: {
...mapMutations(['closeHovers']),
submit: async function () {
this.closeHovers()
buttons.loading('delete')
try {
if (!this.isListing) {
await api.remove(this.$route.path)
buttons.success('delete')
this.$router.push({ path: url.removeLastDir(this.$route.path) + '/' })
this.$root.$emit('preview-deleted')
this.closeHovers()
return
}
this.closeHovers()
if (this.selectedCount === 0) {
return
}

View File

@@ -1,7 +1,7 @@
<template>
<div>
<ul class="file-list">
<li @click="select"
<li @click="itemClick"
@touchstart="touchstart"
@dblclick="next"
role="button"
@@ -35,25 +35,13 @@ export default {
}
},
computed: {
...mapState([ 'req' ]),
...mapState([ 'req', 'user' ]),
nav () {
return decodeURIComponent(this.current)
}
},
mounted () {
// If we're showing this on a listing,
// 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)
this.fillOptions(this.req)
},
methods: {
fillOptions (req) {
@@ -123,6 +111,10 @@ export default {
this.next(event)
}
},
itemClick: function (event) {
if (this.user.singleClick) this.next(event)
else this.select(event)
},
select: function (event) {
// If the element is already selected, unselect it.
if (this.selected === event.currentTarget.dataset.url) {

View File

@@ -7,7 +7,7 @@
<div class="card-content">
<p v-if="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p>
<p v-if="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name }}</p>
<p class="break-word" v-if="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name }}</p>
<p v-if="!dir || selected.length > 1"><strong>{{ $t('prompts.size') }}:</strong> <span id="content_length"></span> {{ humanSize }}</p>
<p v-if="selected.length < 2"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime }}</p>
@@ -88,6 +88,7 @@ export default {
try {
const hash = await api.checksum(link, algo)
// eslint-disable-next-line
event.target.innerHTML = hash
} catch (e) {
this.$showError(e)

View File

@@ -27,6 +27,7 @@ import { mapState } from 'vuex'
import FileList from './FileList'
import { files as api } from '@/api'
import buttons from '@/utils/buttons'
import * as upload from '@/utils/upload'
export default {
name: 'move',
@@ -41,26 +42,51 @@ export default {
methods: {
move: async function (event) {
event.preventDefault()
buttons.loading('move')
let items = []
for (let item of this.selected) {
items.push({
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 {
api.move(items)
buttons.success('move')
this.$router.push({ path: this.dest })
} catch (e) {
buttons.done('move')
this.$showError(e)
let action = async (overwrite, rename) => {
buttons.loading('move')
await api.move(items, overwrite, rename).then(() => {
buttons.success('move')
this.$router.push({ path: this.dest })
}).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

@@ -1,16 +1,6 @@
<template>
<div>
<help v-if="showHelp" ></help>
<download v-else-if="showDownload"></download>
<new-file v-else-if="showNewFile"></new-file>
<new-dir v-else-if="showNewDir"></new-dir>
<rename v-else-if="showRename"></rename>
<delete v-else-if="showDelete"></delete>
<info v-else-if="showInfo"></info>
<move v-else-if="showMove"></move>
<copy v-else-if="showCopy"></copy>
<replace v-else-if="showReplace"></replace>
<share v-else-if="show === 'share'"></share>
<component ref="currentComponent" :is="currentComponent"></component>
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
</div>
</template>
@@ -26,7 +16,10 @@ import Copy from './Copy'
import NewFile from './NewFile'
import NewDir from './NewDir'
import Replace from './Replace'
import ReplaceRename from './ReplaceRename'
import Share from './Share'
import Upload from './Upload'
import ShareDelete from './ShareDelete'
import { mapState } from 'vuex'
import buttons from '@/utils/buttons'
@@ -43,7 +36,10 @@ export default {
NewFile,
NewDir,
Help,
Replace
Replace,
ReplaceRename,
Upload,
ShareDelete
},
data: function () {
return {
@@ -54,18 +50,55 @@ export default {
}
}
},
created () {
window.addEventListener('keydown', (event) => {
if (this.show == null)
return
let prompt = this.$refs.currentComponent;
// Enter
if (event.keyCode == 13) {
switch (this.show) {
case 'delete':
prompt.submit()
break;
case 'copy':
prompt.copy(event)
break;
case 'move':
prompt.move(event)
break;
case 'replace':
prompt.showConfirm(event)
break;
}
}
})
},
computed: {
...mapState(['show', 'plugins']),
showInfo: function () { return this.show === 'info' },
showHelp: function () { return this.show === 'help' },
showDelete: function () { return this.show === 'delete' },
showRename: function () { return this.show === 'rename' },
showMove: function () { return this.show === 'move' },
showCopy: function () { return this.show === 'copy' },
showNewFile: function () { return this.show === 'newFile' },
showNewDir: function () { return this.show === 'newDir' },
showDownload: function () { return this.show === 'download' },
showReplace: function () { return this.show === 'replace' },
currentComponent: function () {
const matched = [
'info',
'help',
'delete',
'rename',
'move',
'copy',
'newFile',
'newDir',
'download',
'replace',
'replace-rename',
'share',
'upload',
'share-delete'
].indexOf(this.show) >= 0;
return matched && this.show || null;
},
showOverlay: function () {
return (this.show !== null && this.show !== 'search' && this.show !== 'more')
}

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,14 +1,11 @@
<template>
<div class="card floating" id="share">
<div class="card floating share__promt__card" id="share">
<div class="card-title">
<h2>{{ $t('buttons.share') }}</h2>
</div>
<div class="card-content">
<ul>
<li v-if="!hasPermanent">
<a @click="getPermalink" :aria-label="$t('buttons.permalink')">{{ $t('buttons.permalink') }}</a>
</li>
<li v-for="link in links" :key="link.hash">
<a :href="buildLink(link.hash)" target="_blank">
@@ -27,6 +24,13 @@
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
</li>
<li v-if="!hasPermanent">
<div>
<input type="password" :placeholder="$t('prompts.optionalPassword')" v-model="passwordPermalink">
<a @click="getPermalink" :aria-label="$t('buttons.permalink')">{{ $t('buttons.permalink') }}</a>
</div>
</li>
<li>
<input v-focus
type="number"
@@ -40,6 +44,7 @@
<option value="hours">{{ $t('time.hours') }}</option>
<option value="days">{{ $t('time.days') }}</option>
</select>
<input type="password" :placeholder="$t('prompts.optionalPassword')" v-model="password">
<button class="action"
@click="submit"
:aria-label="$t('buttons.create')"
@@ -72,7 +77,9 @@ export default {
unit: 'hours',
hasPermanent: false,
links: [],
clip: null
clip: null,
password: '',
passwordPermalink: ''
}
},
computed: {
@@ -121,7 +128,7 @@ export default {
if (!this.time) return
try {
const res = await api.create(this.url, this.time, this.unit)
const res = await api.create(this.url, this.password, this.time, this.unit)
this.links.push(res)
this.sort()
} catch (e) {
@@ -130,7 +137,7 @@ export default {
},
getPermalink: async function () {
try {
const res = await api.create(this.url)
const res = await api.create(this.url, this.passwordPermalink)
this.links.push(res)
this.sort()
this.hasPermanent = true

View File

@@ -0,0 +1,47 @@
<template>
<div class="card floating">
<div class="card-content">
<p>{{ $t('prompts.deleteMessageShare', {path: hash.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 {mapMutations, mapState} from 'vuex'
import { share as api } from '@/api'
import buttons from '@/utils/buttons'
export default {
name: 'share-delete',
computed: {
...mapState(['hash'])
},
methods: {
...mapMutations(['closeHovers']),
submit: async function () {
buttons.loading('delete')
try {
await api.remove(this.hash.hash)
buttons.success('delete')
this.$root.$emit('share-deleted', this.hash.hash)
this.closeHovers()
} catch (e) {
buttons.done('delete')
this.$showError(e)
}
}
}
}
</script>

View File

@@ -0,0 +1,39 @@
<template>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.upload') }}</h2>
</div>
<div class="card-content">
<p>{{ $t('prompts.uploadMessage') }}</p>
</div>
<div class="card-action full">
<div @click="uploadFile" class="action">
<i class="material-icons">insert_drive_file</i>
<div class="title">File</div>
</div>
<div @click="uploadFolder" class="action">
<i class="material-icons">folder</i>
<div class="title">Folder</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'upload',
methods: {
uploadFile: function () {
document.getElementById('upload-input').value = ''
document.getElementById('upload-input').click()
},
uploadFolder: function () {
document.getElementById('upload-folder-input').value = ''
document.getElementById('upload-folder-input').click()
}
}
}
</script>

View File

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

View File

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

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.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.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.share"> {{ $t('settings.perm.share') }}</p>
</div>
</template>
<script>
import { enableExec } from '@/utils/constants'
export default {
name: 'permissions',
props: ['perm'],
@@ -33,7 +34,8 @@ export default {
this.perm.admin = value
}
}
},
isExecEnabled: () => enableExec
}
}
</script>

View File

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

View File

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

View File

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

View File

@@ -1,29 +1,78 @@
.share__box {
text-align: center;
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
background: #fff;
display: block;
border-radius: 0.2em;
width: 90%;
max-width: 25em;
margin: 6em auto;
.share {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: flex-start;
}
.share__box__download {
width: 100%;
@media (max-width: 736px) {
.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;
cursor: pointer;
background: #ffffff;
color: rgba(0, 0, 0, 0.5);
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
text-align: center;
}
.share__box__icon i {
font-size: 10em;
color: #40c4ff;
}
.share__box__center {
text-align: center;
}
.share__box__info {
padding: 2em 3em;
flex: 1 1 auto;
}
.share__box__title {
margin-top: .2em;
overflow: hidden;
text-overflow: ellipsis;
.share__box__element {
padding: 1em;
border-top: 1px solid rgba(0, 0, 0, 0.1);
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;
}
.share__promt__card {
max-width: max-content !important;
width: auto !important;
}

View File

@@ -124,3 +124,7 @@ main {
width: 0;
transition: .2s ease width;
}
.break-word {
word-break: break-all;
}

View File

@@ -96,6 +96,7 @@ table tr>*:last-child {
background-color: #fff;
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);
overflow: auto;
}
.card.floating {
@@ -366,3 +367,33 @@ table tr>*:last-child {
.card .collapsible .collapse {
padding: 0 1em;
}
.card .card-action.full {
padding-top: 0;
display: flex;
flex-wrap: wrap;
}
.card .card-action.full .action {
flex: 1;
padding: 2em;
border-radius: 0.2em;
border: 1px solid rgba(0, 0, 0, 0.1);
text-align: center;
}
.card .card-action.full .action {
margin: 0 0.25em 0.50em;
}
.card .card-action.full .action i {
display: block;
padding: 0;
margin-bottom: 0.25em;
font-size: 4em;
}
.card .card-action.full .action .title {
font-size: 1.5em;
font-weight: 500;
}

View File

@@ -12,10 +12,8 @@
#listing>div {
display: flex;
padding: 0;
flex-wrap: wrap;
justify-content: flex-start;
position: relative;
}
#listing .item {
@@ -54,6 +52,13 @@
vertical-align: bottom;
}
#listing .item img {
width: 4em;
height: 4em;
margin-right: 0.1em;
vertical-align: bottom;
}
.message {
text-align: center;
font-size: 2em;
@@ -131,6 +136,11 @@
font-size: 2em;
}
#listing.list .item div:first-of-type img {
width: 2em;
height: 2em;
}
#listing.list .item div:last-of-type {
width: calc(100% - 3em);
display: flex;
@@ -207,16 +217,6 @@
font-weight: bold;
}
@keyframes slidein {
from {
bottom: -4em;
}
to {
bottom: 0;
}
}
#listing #multiple-selection {
position: fixed;
bottom: -4em;
@@ -225,16 +225,13 @@
width: 100%;
background-color: var(--blue);
height: 4em;
display: none;
padding: 0.5em 0.5em 0.5em 1em;
justify-content: space-between;
align-items: center;
transition: .2s ease bottom;
}
#listing #multiple-selection.active {
animation: slidein 0.2s forwards;
display: flex;
bottom: 0;
}
#listing #multiple-selection p,

View File

@@ -119,14 +119,24 @@
#previewer .bar {
width: 100%;
text-align: right;
display: flex;
padding: 0.5em;
height: 3.7em;
}
#previewer .action:first-of-type {
margin-right: auto;
#previewer .bar > * {
flex: 0 0 auto;
}
#previewer .bar .title {
display: block;
flex: 1 1 auto;
padding: 0 1em;
line-height: 2.3em;
overflow: hidden;
text-overflow: ellipsis;
font-size: 1.2em;
color: #fff;
}
#previewer .action i {
@@ -184,6 +194,54 @@
right: 0.5em;
}
/* EDITOR */
#editor-container {
background-color: #fafafa;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 9999;
overflow: hidden;
}
#editor-container .bar {
width: 100%;
text-align: right;
display: flex;
padding: 0.5em;
height: 3.7em;
background-color: #fff;
border-bottom: 1px solid rgba(0, 0, 0, 0.075);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
}
#editor-container .title {
margin-right: auto;
padding: 0 1em;
line-height: 2.7em;
overflow: hidden;
word-break: break-word;
}
#previewer .loading {
height: 100%;
width: 100%;
}
#editor-container #editor {
height: calc(100vh - 8.2em);
}
#editor-container #breadcrumbs {
height: 2.3em;
padding: 0 1em;
}
#editor-container #breadcrumbs span {
font-size: 12px;
}
/* * * * * * * * * * * * * * * *
* PROMPT *

View File

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

View File

@@ -1,7 +1,5 @@
{
"permanent": "Permanent",
"buttons": {
"shell": "Kommandozeile ein/ausschalten",
"cancel": "Abbrechen",
"close": "Schließen",
"copy": "Kopieren",
@@ -10,6 +8,7 @@
"create": "Neu",
"delete": "Löschen",
"download": "Downloaden",
"hideDotfiles": "",
"info": "Info",
"more": "mehr",
"move": "Verschieben",
@@ -17,25 +16,28 @@
"new": "Neu",
"next": "nächste",
"ok": "OK",
"replace": "Ersetzen",
"permalink": "permanenten Verweis anzeigen",
"previous": "vorherige",
"publish": "Veröffentlichen",
"rename": "umbenennen",
"replace": "Ersetzen",
"reportIssue": "Fehler melden",
"save": "Speichern",
"schedule": "Planung",
"search": "Suchen",
"select": "Auswählen",
"share": "Teilen",
"publish": "Veröffentlichen",
"selectMultiple": "Mehrfachauswahl",
"schedule": "Planung",
"share": "Teilen",
"shell": "Kommandozeile ein/ausschalten",
"switchView": "Ansicht wechseln",
"toggleSidebar": "Seitenleiste anzeigen",
"update": "Update",
"upload": "Upload",
"permalink": "permanenten Verweis anzeigen"
"upload": "Upload"
},
"success": {
"linkCopied": "Verweis wurde kopiert!"
"download": {
"downloadFile": "Download Datei",
"downloadFolder": "Download Ordner",
"downloadSelected": ""
},
"errors": {
"forbidden": "Sie haben keine Berechtigung dies abzurufen.",
@@ -43,11 +45,11 @@
"notFound": "Dieser Ort kann nicht angezeigt werden."
},
"files": {
"folders": "Ordner",
"files": "Dateien",
"body": "Body",
"clear": "Clear",
"closePreview": "Vorschau schließen",
"files": "Dateien",
"folders": "Ordner",
"home": "Home",
"lastModified": "zuletzt verändert",
"loading": "Lade...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "Mehrfachauswahl ausgewählt",
"name": "Name",
"size": "Größe",
"sortByLastModified": "Nach Änderungsdatum sortieren",
"sortByName": "Nach Namen sortieren",
"sortBySize": "Nach Größe sortieren",
"sortByLastModified": "Nach Änderungsdatum sortieren"
"sortBySize": "Nach Größe sortieren"
},
"help": {
"click": "wähle Datei oder Ordner",
@@ -74,18 +76,39 @@
"f2": "Datei umbenennen",
"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": {
"password": "Passwort",
"passwordConfirm": "Passwort Bestätigung",
"submit": "Login",
"createAnAccount": "Account erstellen",
"loginInstead": "Account besteht bereits",
"password": "Passwort",
"passwordConfirm": "Passwort Bestätigung",
"passwordsDontMatch": "Passwörter stimmen nicht überein",
"usernameTaken": "Benutzername ist bereits vergeben",
"signup": "Registrieren",
"submit": "Login",
"username": "Benutzername",
"usernameTaken": "Benutzername ist bereits vergeben",
"wrongCredentials": "Falsche Zugangsdaten"
},
"permanent": "Permanent",
"prompts": {
"copy": "Kopieren",
"copyMessage": "Wählen Sie einen Zielort zum Kopieren:",
@@ -102,43 +125,64 @@
"lastModified": "Zuletzt geändert",
"move": "Verschieben",
"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",
"newDirMessage": "Geben Sie den Namen des neuen Ordners an.",
"newFile": "Neue Datei",
"newFileMessage": "Geben Sie den Namen der neuen Datei an.",
"numberDirs": "Anzahl der Ordner",
"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",
"renameMessage": "Fügen Sie einen Namen ein für",
"show": "Anzeigen",
"size": "Größe",
"replace": "Ersetzen",
"replaceMessage": "Eine der Datei mit dem gleichen Namen, wie die Sie hochladen wollen, existiert bereits. Soll die vorhandene Datei ersetzt werden ?\n",
"schedule": "Plan",
"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": {
"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",
"administrator": "Administrator",
"allowCommands": "Befehle ausführen",
"allowEdit": "Bearbeiten, Umbenennen und Löschen von Dateien oder Ordnern",
"allowNew": "Erstellen neuer Dateien und Ordner",
"allowPublish": "Veröffentlichen von neuen Beiträgen und Seiten",
"allowSignup": "Erlaube Benutzern sich zu registrieren",
"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",
"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}.",
"commandsUpdated": "Befehle aktualisiert!",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "Individuelles Stylesheet",
"defaultUserDescription": "Das sind die Standard Einstellunge für Benutzer",
"disableExternalLinks": "Externe Links deaktivieren (außer Dokumentation)",
"documentation": "Dokumentation",
"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",
"hideDotfiles": "",
"insertPath": "Pfad einfügen",
"insertRegex": "Regex Ausdruck einfügen",
"instanceName": "Instanzname",
"language": "Sprache",
"lockPassword": "Verhindere, dass der Benutzer sein Passwort ändert",
"newPassword": "Ihr neues Passwort.",
@@ -146,6 +190,16 @@
"newUser": "Neuer Benutzer",
"password": "Passwort",
"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",
"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",
@@ -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",
"scope": "Scope",
"settingsUpdated": "Einstellungen aktualisiert!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "Benutzer",
"userCommands": "Befehle",
"userCommandsHelp": "Eine Liste, mit einem Leerzeichen als Trennung, mit den für diesen Nutzer verfügbaren Befehlen. Example:\n",
"userCreated": "Benutzer angelegt!",
"userDefaults": "Benutzer Standard Einstellungen",
"userDeleted": "Benutzer gelöscht!",
"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!",
"userDefaults": "Benutzer Standard Einstellungen",
"defaultUserDescription": "Das sind die Standard Einstellunge für Benutzer",
"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"
}
"username": "Nutzername",
"users": "Nutzer"
},
"sidebar": {
"help": "Hilfe",
"hugoNew": "Hugo Neu",
"login": "Anmelden",
"signup": "Registrieren",
"logout": "Logout",
"myFiles": "Meine Dateien",
"newFile": "Neue Datei",
"newFolder": "Neuer Ordner",
"preview": "Vorschau",
"settings": "Einstellungen",
"siteSettings": "Seiteneinstellungen",
"hugoNew": "Hugo Neu",
"preview": "Vorschau"
"signup": "Registrieren",
"siteSettings": "Seiteneinstellungen"
},
"search": {
"images": "Bilder",
"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": "한국어"
"success": {
"linkCopied": "Verweis wurde kopiert!"
},
"time": {
"unit": "Zeiteinheit",
"seconds": "Sekunden",
"minutes": "Minuten",
"days": "Tage",
"hours": "Stunden",
"days": "Tage"
},
"download": {
"downloadFile": "Download Datei",
"downloadFolder": "Download Ordner"
"minutes": "Minuten",
"seconds": "Sekunden",
"unit": "Zeiteinheit"
}
}
}

View File

@@ -1,7 +1,5 @@
{
"permanent": "Permanent",
"buttons": {
"shell": "Toggle shell",
"cancel": "Cancel",
"close": "Close",
"copy": "Copy",
@@ -10,6 +8,7 @@
"create": "Create",
"delete": "Delete",
"download": "Download",
"hideDotfiles": "Hide dotfiles",
"info": "Info",
"more": "More",
"move": "Move",
@@ -17,25 +16,29 @@
"new": "New",
"next": "Next",
"ok": "OK",
"replace": "Replace",
"permalink": "Get Permanent Link",
"previous": "Previous",
"publish": "Publish",
"rename": "Rename",
"replace": "Replace",
"reportIssue": "Report Issue",
"save": "Save",
"schedule": "Schedule",
"search": "Search",
"select": "Select",
"share": "Share",
"publish": "Publish",
"selectMultiple": "Select multiple",
"schedule": "Schedule",
"share": "Share",
"shell": "Toggle shell",
"submit": "Submit",
"switchView": "Switch view",
"toggleSidebar": "Toggle sidebar",
"update": "Update",
"upload": "Upload",
"permalink": "Get Permanent Link"
"upload": "Upload"
},
"success": {
"linkCopied": "Link copied!"
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": "Download Selected"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
@@ -43,11 +46,11 @@
"notFound": "This location can't be reached."
},
"files": {
"folders": "Folders",
"files": "Files",
"body": "Body",
"clear": "Clear",
"closePreview": "Close preview",
"files": "Files",
"folders": "Folders",
"home": "Home",
"lastModified": "Last modified",
"loading": "Loading...",
@@ -56,9 +59,9 @@
"multipleSelectionEnabled": "Multiple selection enabled",
"name": "Name",
"size": "Size",
"sortByLastModified": "Sort by last modified",
"sortByName": "Sort by name",
"sortBySize": "Sort by size",
"sortByLastModified": "Sort by last modified"
"sortBySize": "Sort by size"
},
"help": {
"click": "select file or directory",
@@ -74,24 +77,46 @@
"f2": "rename file",
"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": {
"password": "Password",
"passwordConfirm": "Password Confirmation",
"submit": "Login",
"createAnAccount": "Create an account",
"loginInstead": "Already have an account",
"password": "Password",
"passwordConfirm": "Password Confirmation",
"passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup",
"submit": "Login",
"username": "Username",
"usernameTaken": "Username already taken",
"wrongCredentials": "Wrong credentials"
},
"permanent": "Permanent",
"prompts": {
"copy": "Copy",
"copyMessage": "Choose the place to copy your files:",
"currentlyNavigating": "Currently navigating on:",
"deleteMessageMultiple": "Are you sure you want to delete {count} file(s)?",
"deleteMessageSingle": "Are you sure you want to delete this file/folder?",
"deleteMessageShare": "Are you sure you want to delete this share({path})?",
"deleteTitle": "Delete files",
"displayName": "Display Name:",
"download": "Download files",
@@ -102,43 +127,65 @@
"lastModified": "Last Modified",
"move": "Move",
"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",
"newDirMessage": "Write the name of the new directory.",
"newFile": "New file",
"newFileMessage": "Write the name of the new file.",
"numberDirs": "Number of directories",
"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",
"renameMessage": "Insert a new name for",
"show": "Show",
"size": "Size",
"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",
"schedule": "Schedule",
"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",
"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": {
"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",
"administrator": "Administrator",
"allowCommands": "Execute commands",
"allowEdit": "Edit, rename and delete files or directories",
"allowNew": "Create new files and directories",
"allowPublish": "Publish new posts and pages",
"allowSignup": "Allow users to signup",
"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",
"commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "Commands updated!",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "Custom Stylesheet",
"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": "Global Settings",
"hideDotfiles": "Hide dotfiles",
"insertPath": "Insert the path",
"insertRegex": "Insert regex expression",
"instanceName": "Instance name",
"language": "Language",
"lockPassword": "Prevent the user from changing the password",
"newPassword": "Your new password",
@@ -146,6 +193,16 @@
"newUser": "New User",
"password": "Password",
"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",
"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",
@@ -155,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",
"scope": "Scope",
"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",
"userCommands": "Commands",
"userCommandsHelp": "A space separated list with the available commands for this user. Example:\n",
"userCreated": "User created!",
"userDefaults": "User default settings",
"userDeleted": "User deleted!",
"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!",
"userDefaults": "User default settings",
"defaultUserDescription": "This are the default settings for new 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"
}
"username": "Username",
"users": "Users"
},
"sidebar": {
"help": "Help",
"hugoNew": "Hugo New",
"login": "Login",
"signup": "Signup",
"logout": "Logout",
"myFiles": "My files",
"newFile": "New file",
"newFolder": "New folder",
"preview": "Preview",
"settings": "Settings",
"siteSettings": "Site Settings",
"hugoNew": "Hugo New",
"preview": "Preview"
"signup": "Signup",
"siteSettings": "Site Settings"
},
"search": {
"images": "Images",
"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)"
"success": {
"linkCopied": "Link copied!"
},
"time": {
"unit": "Time Unit",
"seconds": "Seconds",
"minutes": "Minutes",
"days": "Days",
"hours": "Hours",
"days": "Days"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
"minutes": "Minutes",
"seconds": "Seconds",
"unit": "Time Unit"
}
}
}

View File

@@ -1,7 +1,5 @@
{
"permanent": "Permanente",
"buttons": {
"shell": "Presiona Enter para buscar...",
"cancel": "Cancelar",
"close": "Cerrar",
"copy": "Copiar",
@@ -10,6 +8,7 @@
"create": "Crear",
"delete": "Borrar",
"download": "Descargar",
"hideDotfiles": "",
"info": "Info",
"more": "Más",
"move": "Mover",
@@ -17,25 +16,28 @@
"new": "Nuevo",
"next": "Siguiente",
"ok": "OK",
"replace": "Reemplazar",
"permalink": "Link permanente",
"previous": "Anterior",
"publish": "Publicar",
"rename": "Renombrar",
"replace": "Reemplazar",
"reportIssue": "Reportar problema",
"save": "Guardar",
"schedule": "Programar",
"search": "Buscar",
"select": "Seleccionar",
"share": "Compartir",
"publish": "Publicar",
"selectMultiple": "Selección múltiple",
"schedule": "Programar",
"share": "Compartir",
"shell": "Presiona Enter para buscar...",
"switchView": "Cambiar vista",
"toggleSidebar": "Mostrar/Ocultar menú",
"update": "Actualizar",
"upload": "Subir",
"permalink": "Link permanente"
"upload": "Subir"
},
"success": {
"linkCopied": "¡Link copiado!"
"download": {
"downloadFile": "Descargar fichero",
"downloadFolder": "Descargar directorio",
"downloadSelected": ""
},
"errors": {
"forbidden": "No tienes los permisos necesarios para acceder.",
@@ -43,11 +45,11 @@
"notFound": "No se puede acceder a este lugar."
},
"files": {
"folders": "Carpetas",
"files": "Archivos",
"body": "Cuerpo",
"clear": "Limpiar",
"closePreview": "Cerrar vista previa",
"files": "Archivos",
"folders": "Carpetas",
"home": "Inicio",
"lastModified": "Última modificación",
"loading": "Cargando...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "Selección múltiple activada",
"name": "Nombre",
"size": "Tamaño",
"sortByLastModified": "Ordenar por última modificación",
"sortByName": "Ordenar por nombre",
"sortBySize": "Ordenar por tamaño",
"sortByLastModified": "Ordenar por última modificación"
"sortBySize": "Ordenar por tamaño"
},
"help": {
"click": "seleccionar archivo o carpeta",
@@ -74,18 +76,39 @@
"f2": "renombrar archivo",
"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": {
"password": "Contraseña",
"passwordConfirm": "Confirmación de contraseña",
"submit": "Iniciar sesión",
"createAnAccount": "Crear una cuenta",
"loginInstead": "Usuario ya existente",
"password": "Contraseña",
"passwordConfirm": "Confirmación de contraseña",
"passwordsDontMatch": "Las contraseñas no coinciden",
"usernameTaken": "Nombre usuario no disponible",
"signup": "Registrate",
"submit": "Iniciar sesión",
"username": "Usuario",
"usernameTaken": "Nombre usuario no disponible",
"wrongCredentials": "Usuario y/o contraseña incorrectos"
},
"permanent": "Permanente",
"prompts": {
"copy": "Copiar",
"copyMessage": "Elige el lugar donde quieres copiar tus archivos:",
@@ -102,43 +125,64 @@
"lastModified": "Última modificación",
"move": "Mover",
"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",
"newDirMessage": "Escribe el nombre de la nueva carpeta.",
"newFile": "Nuevo archivo",
"newFileMessage": "Escribe el nombre del nuevo archivo.",
"numberDirs": "Número de carpetas",
"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",
"renameMessage": "Escribe el nuevo nombre para",
"show": "Mostrar",
"size": "Tamaño",
"replace": "Reemplazar",
"replaceMessage": "Uno de los archivos ue intentas subir está creando conflicto por su nombre. ¿Quieres cambiar el nombre del ya existente?\n",
"schedule": "Programar",
"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": {
"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",
"administrator": "Administrador",
"allowCommands": "Ejecutar comandos",
"allowEdit": "Editar, renombrar y borrar archivos o carpetas",
"allowNew": "Crear nuevos archivos y carpetas",
"allowPublish": "Publicar nuevos posts y páginas",
"allowSignup": "Permitir registro de usuarios",
"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",
"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}.",
"commandsUpdated": "¡Comandos actualizados!",
"createUserDir": "Crea automaticamente una carpeta de inicio cuando se agrega un usuario",
"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",
"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",
"hideDotfiles": "",
"insertPath": "Introduce la ruta",
"insertRegex": "Introducir expresión regular",
"instanceName": "Nombre de la instancia",
"language": "Idioma",
"lockPassword": "Evitar que el usuario cambie la contraseña",
"newPassword": "Tu nueva contraseña",
@@ -146,6 +190,16 @@
"newUser": "Nuevo usuario",
"password": "Contraseña",
"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",
"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",
@@ -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",
"scope": "Raíz",
"settingsUpdated": "¡Ajustes actualizados!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "Usuario",
"userCommands": "Comandos",
"userCommandsHelp": "Una lista separada por espacios con los comandos permitidos para este usuario. Ejemplo:\n",
"userCreated": "¡Usuario creado!",
"userDefaults": "Configuración de usuario por defecto",
"userDeleted": "¡Usuario eliminado!",
"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!",
"userDefaults": "Configuración de usuario por defecto",
"defaultUserDescription": "Estas son las configuraciones por defecto para nuevos 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"
}
"username": "Usuario",
"users": "Usuarios"
},
"sidebar": {
"help": "Ayuda",
"hugoNew": "Nuevo Hugo",
"login": "Iniciar sesión",
"signup": "Registrate",
"logout": "Cerrar sesión",
"myFiles": "Mis archivos",
"newFile": "Nuevo archivo",
"newFolder": "Nueva carpeta",
"preview": "Vista previa",
"settings": "Ajustes",
"siteSettings": "Ajustes del sitio",
"hugoNew": "Nuevo Hugo",
"preview": "Vista previa"
"signup": "Registrate",
"siteSettings": "Ajustes del sitio"
},
"search": {
"images": "Images",
"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": "한국어"
"success": {
"linkCopied": "¡Link copiado!"
},
"time": {
"unit": "Unidad",
"seconds": "Segundos",
"minutes": "Minutos",
"days": "Días",
"hours": "Horas",
"days": "Días"
},
"download": {
"downloadFile": "Descargar fichero",
"downloadFolder": "Descargar directorio"
"minutes": "Minutos",
"seconds": "Segundos",
"unit": "Unidad"
}
}
}

View File

@@ -1,7 +1,5 @@
{
"permanent": "Permanent",
"buttons": {
"shell": "Toggle shell",
"cancel": "Annuler",
"close": "Fermer",
"copy": "Copier",
@@ -10,6 +8,7 @@
"create": "Créer",
"delete": "Supprimer",
"download": "Télécharger",
"hideDotfiles": "",
"info": "Info",
"more": "Plus",
"move": "Déplacer",
@@ -17,25 +16,28 @@
"new": "Nouveau",
"next": "Suivant",
"ok": "OK",
"replace": "Remplacer",
"permalink": "Obtenir un lien permanent",
"previous": "Précédent",
"publish": "Publier",
"rename": "Renommer",
"replace": "Remplacer",
"reportIssue": "Rapport d'erreur",
"save": "Enregistrer",
"schedule": "Fixer la date",
"search": "Chercher",
"select": "Sélectionner",
"share": "Partager",
"publish": "Publier",
"selectMultiple": "Sélection multiple",
"schedule": "Fixer la date",
"share": "Partager",
"shell": "Toggle shell",
"switchView": "Changer le mode d'affichage",
"toggleSidebar": "Afficher/Masquer la barre latérale",
"update": "Mettre à jour",
"upload": "Importer",
"permalink": "Obtenir un lien permanent"
"upload": "Importer"
},
"success": {
"linkCopied": "Link copied!"
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": ""
},
"errors": {
"forbidden": "You don't have permissions to access this.",
@@ -43,22 +45,22 @@
"notFound": "Impossible d'accéder à cet emplacement."
},
"files": {
"folders": "Dossiers",
"files": "Fichiers",
"body": "Corps",
"clear": "Fermer",
"closePreview": "Fermer la prévisualisation",
"files": "Fichiers",
"folders": "Dossiers",
"home": "Accueil",
"lastModified": "Dernière modification",
"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",
"multipleSelectionEnabled": "Sélection multiple activée",
"name": "Nom",
"size": "Taille",
"sortByLastModified": "Trier par date de dernière modification",
"sortByName": "Trier par nom",
"sortBySize": "Trier par taille",
"sortByLastModified": "Trier par date de dernière modification"
"sortBySize": "Trier par taille"
},
"help": {
"click": "Sélectionner un élément",
@@ -74,18 +76,39 @@
"f2": "Renommer le fichier",
"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": {
"password": "Mot de passe",
"passwordConfirm": "Password Confirmation",
"submit": "Se connecter",
"createAnAccount": "Create an account",
"loginInstead": "Already have an account",
"password": "Mot de passe",
"passwordConfirm": "Password Confirmation",
"passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup",
"submit": "Se connecter",
"username": "Utilisateur",
"usernameTaken": "Username already taken",
"wrongCredentials": "Identifiants incorrects !"
},
"permanent": "Permanent",
"prompts": {
"copy": "Copier",
"copyMessage": "Choisissez l'emplacement où copier la sélection :",
@@ -102,43 +125,64 @@
"lastModified": "Dernière modification",
"move": "Déplacer",
"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",
"newDirMessage": "Nom du nouveau dossier :",
"newFile": "Nouveau fichier",
"newFileMessage": "Nom du nouveau fichier :",
"numberDirs": "Nombre de dossiers",
"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",
"renameMessage": "Nouveau nom pour",
"show": "Montrer",
"size": "Taille",
"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",
"schedule": "Fixer la date",
"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": {
"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",
"administrator": "Administrateur",
"allowCommands": "Exécuter des commandes",
"allowEdit": "Editer, renommer et supprimer des fichiers ou des dossiers",
"allowNew": "Créer de nouveaux fichiers et dossiers",
"allowPublish": "Publier de nouveaux posts et pages",
"allowSignup": "Allow users to signup",
"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",
"commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "Commandes mises à jour !",
"createUserDir": "Auto create user home dir while adding new user",
"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",
"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",
"hideDotfiles": "",
"insertPath": "Insert the path",
"insertRegex": "Insert regex expression",
"instanceName": "Instance name",
"language": "Langue",
"lockPassword": "Prevent the user from changing the password",
"newPassword": "Votre nouveau mot de passe",
@@ -146,6 +190,16 @@
"newUser": "Nouvel Utilisateur",
"password": "Mot de passe",
"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",
"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",
@@ -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",
"scope": "Portée du dossier utilisateur",
"settingsUpdated": "Les paramètres ont été mis à jour !",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "Utilisateur",
"userCommands": "Commandes",
"userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur. Exemple :",
"userCreated": "Utilisateur créé !",
"userDefaults": "User default settings",
"userDeleted": "Utilisateur supprimé !",
"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 !",
"userDefaults": "User default settings",
"defaultUserDescription": "This are the default settings for new 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"
}
"username": "Nom d'utilisateur",
"users": "Utilisateurs"
},
"sidebar": {
"help": "Aide",
"hugoNew": "Nouveau Hugo",
"login": "Login",
"signup": "Signup",
"logout": "Se déconnecter",
"myFiles": "Mes fichiers",
"newFile": "Nouveau fichier",
"newFolder": "Nouveau dossier",
"preview": "Prévisualiser",
"settings": "Paramètres",
"siteSettings": "Paramètres du site",
"hugoNew": "Nouveau Hugo",
"preview": "Prévisualiser"
"signup": "Signup",
"siteSettings": "Paramètres du site"
},
"search": {
"images": "Images",
"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": "한국어"
"success": {
"linkCopied": "Link copied!"
},
"time": {
"unit": "Unité de temps",
"seconds": "Secondes",
"minutes": "Minutes",
"days": "Jours",
"hours": "Heures",
"days": "Jours"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
"minutes": "Minutes",
"seconds": "Secondes",
"unit": "Unité de temps"
}
}
}

View File

@@ -77,28 +77,39 @@ export function detectLocale () {
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({
locale: detectLocale(),
fallbackLocale: 'en',
messages: {
'ar': ar,
'de': de,
'ar': removeEmpty(ar),
'de': removeEmpty(de),
'en': en,
'es': es,
'fr': fr,
'is': is,
'it': it,
'ja': ja,
'ko': ko,
'nl-be': nlBE,
'pl': pl,
'pt-br': ptBR,
'pt': pt,
'ru': ru,
'ro': ro,
'sv-se': svSE,
'zh-cn': zhCN,
'zh-tw': zhTW
'es': removeEmpty(es),
'fr': removeEmpty(fr),
'is': removeEmpty(is),
'it': removeEmpty(it),
'ja': removeEmpty(ja),
'ko': removeEmpty(ko),
'nl-be': removeEmpty(nlBE),
'pl': removeEmpty(pl),
'pt-br': removeEmpty(ptBR),
'pt': removeEmpty(pt),
'ru': removeEmpty(ru),
'ro': removeEmpty(ro),
'sv-se': removeEmpty(svSE),
'zh-cn': removeEmpty(zhCN),
'zh-tw': removeEmpty(zhTW)
}
})

View File

@@ -1,7 +1,5 @@
{
"permanent": "Varanlegt",
"buttons": {
"shell": "Sýna skipanaglugga",
"cancel": "Hætta við",
"close": "Loka",
"copy": "Afrita",
@@ -10,6 +8,7 @@
"create": "Búa til",
"delete": "Eyða",
"download": "Sækja",
"hideDotfiles": "",
"info": "Upplýsingar",
"more": "Meira",
"move": "Færa",
@@ -17,25 +16,28 @@
"new": "Nýtt",
"next": "Næsta",
"ok": "OK",
"replace": "Skipta út",
"permalink": "Sækja fastan hlekk",
"previous": "Fyrri",
"publish": "Gefa út",
"rename": "Endurnefna",
"replace": "Skipta út",
"reportIssue": "Tilkynna vandamál",
"save": "Vista",
"schedule": "Áætlun",
"search": "Leita",
"select": "Velja",
"share": "Deila",
"publish": "Gefa út",
"selectMultiple": "Velja mörg",
"schedule": "Áætlun",
"share": "Deila",
"shell": "Sýna skipanaglugga",
"switchView": "Skipta um útlit",
"toggleSidebar": "Sýna hliðarstiku",
"update": "Vista",
"upload": "Hlaða upp",
"permalink": "Sækja fastan hlekk"
"upload": "Hlaða upp"
},
"success": {
"linkCopied": "Hlekkur afritaður!"
"download": {
"downloadFile": "Sækja skjal",
"downloadFolder": "Sækja möppu",
"downloadSelected": ""
},
"errors": {
"forbidden": "Þú hefur ekki aðgang að þessari síðu.",
@@ -43,11 +45,11 @@
"notFound": "Ekki er hægt að opna þessa síðu."
},
"files": {
"folders": "Möppur",
"files": "Skjöl",
"body": "Meginmál",
"clear": "Hreinsa",
"closePreview": "Loka forskoðun",
"files": "Skjöl",
"folders": "Möppur",
"home": "Heim",
"lastModified": "Seinast breytt",
"loading": "Hleð...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "Hægt að velja mörg skjöl/möppur",
"name": "Nafn",
"size": "Stærð",
"sortByLastModified": "Flokka eftir Seinast breytt",
"sortByName": "Flokka eftir nafni",
"sortBySize": "Flokka eftir stærð",
"sortByLastModified": "Flokka eftir Seinast breytt"
"sortBySize": "Flokka eftir stærð"
},
"help": {
"click": "velja skjal eða möppu",
@@ -74,18 +76,39 @@
"f2": "endurnefna skjal",
"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": {
"password": "Lykilorð",
"passwordConfirm": "Staðfesting lykilorðs",
"submit": "Innskráning",
"createAnAccount": "Búa til nýjan aðgang",
"loginInstead": "Þú ert þegar með aðgang",
"password": "Lykilorð",
"passwordConfirm": "Staðfesting lykilorðs",
"passwordsDontMatch": "Lykilorð eru mismunandi",
"usernameTaken": "Þetta norendanafn er þegar í notkun",
"signup": "Nýskráning",
"submit": "Innskráning",
"username": "Notendanafn",
"usernameTaken": "Þetta norendanafn er þegar í notkun",
"wrongCredentials": "Rangar notendaupplýsingar"
},
"permanent": "Varanlegt",
"prompts": {
"copy": "Afrita",
"copyMessage": "Veldu staðsetningu til að afrita gögn: ",
@@ -102,43 +125,64 @@
"lastModified": "Seinast breytt",
"move": "Færa",
"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",
"newDirMessage": "Hvað á mappan að heita?",
"newFile": "Nýtt skjal",
"newFileMessage": "Hvað á skjalið að heita?",
"numberDirs": "Fjöldi mappa",
"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",
"renameMessage": "Settu inn nýtt nafn fyrir",
"show": "Sýna",
"size": "Stærð",
"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",
"schedule": "Áætlun",
"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": {
"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",
"administrator": "Stjórnandi",
"allowCommands": "Senda skipanir",
"allowEdit": "Breyta, endurnefna og eyða skjölum eða möppum",
"allowNew": "Búa til ný skjöl og möppur",
"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ð)",
"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",
"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}. ",
"commandsUpdated": "Skipanastillingar vistaðar!",
"createUserDir": "Auto create user home dir while adding new user",
"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",
"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",
"hideDotfiles": "",
"insertPath": "Settu inn slóð",
"insertRegex": "Setja inn reglulega segð",
"instanceName": "Nafn tilviks",
"language": "Tungumál",
"lockPassword": "Koma í veg fyrir að notandi breyti lykilorðinu",
"newPassword": "Nýja lykilorðið þitt",
@@ -146,6 +190,16 @@
"newUser": "Nýr notandi",
"password": "Lykilorð",
"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",
"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",
@@ -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. ",
"scope": "Sýn notandans",
"settingsUpdated": "Stillingar vistaðar!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "Notandi",
"userCommands": "Skipanir",
"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!",
"userDefaults": "Sjálfgefnar notendastillingar",
"userDeleted": "Notanda eytt!",
"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!",
"userDefaults": "Sjálfgefnar notendastillingar",
"defaultUserDescription": "Þetta eru sjálfgefnar stillingar fyrir nýja 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"
}
"username": "Notendanafn",
"users": "Notendur"
},
"sidebar": {
"help": "Hjálp",
"hugoNew": "Hugo New",
"login": "Innskráning",
"signup": "Nýskráning",
"logout": "Útskráning",
"myFiles": "Gögnin mín",
"newFile": "Nýtt skjal",
"newFolder": "Ný mappa",
"preview": "Sýnishorn",
"settings": "Stillingar",
"siteSettings": "Stillingar síðu",
"hugoNew": "Hugo New",
"preview": "Sýnishorn"
"signup": "Nýskráning",
"siteSettings": "Stillingar síðu"
},
"search": {
"images": "Myndir",
"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": "한국어"
"success": {
"linkCopied": "Hlekkur afritaður!"
},
"time": {
"unit": "Tímastilling",
"seconds": "Sekúndur",
"minutes": "Mínútur",
"days": "Dagar",
"hours": "Klukkutímar",
"days": "Dagar"
},
"download": {
"downloadFile": "Sækja skjal",
"downloadFolder": "Sækja möppu"
"minutes": "Mínútur",
"seconds": "Sekúndur",
"unit": "Tímastilling"
}
}
}

View File

@@ -1,7 +1,5 @@
{
"permanent": "Permanente",
"buttons": {
"shell": "Toggle shell",
"cancel": "Annulla",
"close": "Chiudi",
"copy": "Copia",
@@ -10,6 +8,7 @@
"create": "Crea",
"delete": "Elimina",
"download": "Scarica",
"hideDotfiles": "",
"info": "Informazioni",
"more": "Altro",
"move": "Sposta",
@@ -17,25 +16,28 @@
"new": "Nuovo",
"next": "Successivo",
"ok": "OK",
"replace": "Sostituisci",
"permalink": "Ottieni link permanente",
"previous": "Precedente",
"publish": "Publica",
"rename": "Rinomina",
"replace": "Sostituisci",
"reportIssue": "Segnala un problema",
"save": "Salva",
"schedule": "Programma",
"search": "Cerca",
"select": "Seleziona",
"share": "Condividi",
"publish": "Publica",
"selectMultiple": "Seleziona molteplici",
"schedule": "Programma",
"share": "Condividi",
"shell": "Toggle shell",
"switchView": "Cambia vista",
"toggleSidebar": "Mostra/nascondi la barra laterale",
"update": "Aggiorna",
"upload": "Carica",
"permalink": "Ottieni link permanente"
"upload": "Carica"
},
"success": {
"linkCopied": "Link copiato!"
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": ""
},
"errors": {
"forbidden": "You don't have permissions to access this.",
@@ -43,11 +45,11 @@
"notFound": "Questo percorso non può essere raggiunto."
},
"files": {
"folders": "Cartelle",
"files": "Files",
"body": "Contenuto",
"clear": "Cancella",
"closePreview": "Chiudi anteprima",
"files": "Files",
"folders": "Cartelle",
"home": "Home",
"lastModified": "Ultima modifica",
"loading": "Caricamento...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "Selezione multipla attivata",
"name": "Nome",
"size": "Grandezza",
"sortByLastModified": "Ordina per ultima modifica",
"sortByName": "Ordina per nome",
"sortBySize": "Ordina per dimensione",
"sortByLastModified": "Ordina per ultima modifica"
"sortBySize": "Ordina per dimensione"
},
"help": {
"click": "seleziona un file o una cartella",
@@ -74,18 +76,39 @@
"f2": "rinomina un file",
"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": {
"password": "Password",
"passwordConfirm": "Password Confirmation",
"submit": "Entra",
"createAnAccount": "Create an account",
"loginInstead": "Already have an account",
"password": "Password",
"passwordConfirm": "Password Confirmation",
"passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup",
"submit": "Entra",
"username": "Nome utente",
"usernameTaken": "Username already taken",
"wrongCredentials": "Credenziali errate"
},
"permanent": "Permanente",
"prompts": {
"copy": "Copia",
"copyMessage": "Seleziona la cartella in cui copiare i file:",
@@ -102,43 +125,64 @@
"lastModified": "Ultima modifica",
"move": "Sposta",
"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",
"newDirMessage": "Scrivi il nome della nuova cartella.",
"newFile": "Nuovo file",
"newFileMessage": "Scrivi il nome del nuovo file.",
"numberDirs": "Numero di cartelle",
"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",
"renameMessage": "Inserisci un nuovo nome per",
"show": "Mostra",
"size": "Grandezza",
"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",
"schedule": "Pianifica",
"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": {
"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",
"administrator": "Amministratore",
"allowCommands": "Esegui comandi",
"allowEdit": "Modifica, rinomina ed elimina file o cartelle",
"allowNew": "Crea nuovi files o cartelle",
"allowPublish": "Pubblica nuovi post e pagine",
"allowSignup": "Allow users to signup",
"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",
"commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "Comandi aggiornati!",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "Folgio di stile personalizzato",
"defaultUserDescription": "This are the default settings for new users.",
"disableExternalLinks": "Disable external links (except documentation)",
"documentation": "documentation",
"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",
"hideDotfiles": "",
"insertPath": "Insert the path",
"insertRegex": "Insert regex expression",
"instanceName": "Instance name",
"language": "Lingua",
"lockPassword": "Impedisci all'utente di modificare la password",
"newPassword": "La tua nuova password",
@@ -146,6 +190,16 @@
"newUser": "Nuovo utente",
"password": "Password",
"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",
"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",
@@ -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",
"scope": "Scopo",
"settingsUpdated": "Impostazioni aggiornate!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "Utente",
"userCommands": "Comandi",
"userCommandsHelp": "Una lista separata dal spazi con i comandi disponibili per questo utente. Example:\n",
"userCreated": "Utente creato!",
"userDefaults": "User default settings",
"userDeleted": "Utente eliminato!",
"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!",
"userDefaults": "User default settings",
"defaultUserDescription": "This are the default settings for new 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"
}
"username": "Nome utente",
"users": "Utenti"
},
"sidebar": {
"help": "Aiuto",
"hugoNew": "Hugo New",
"login": "Login",
"signup": "Signup",
"logout": "Esci",
"myFiles": "I miei file",
"newFile": "Nuovo file",
"newFolder": "Nuova cartella",
"preview": "Anteprima",
"settings": "Impostazioni",
"siteSettings": "Impostaizoni del sito",
"hugoNew": "Hugo New",
"preview": "Anteprima"
"signup": "Signup",
"siteSettings": "Impostaizoni del sito"
},
"search": {
"images": "Immagini",
"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": "한국어"
"success": {
"linkCopied": "Link copiato!"
},
"time": {
"unit": "Unità di tempo",
"seconds": "Secondi",
"minutes": "Minuti",
"days": "Giorni",
"hours": "Ore",
"days": "Giorni"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
"minutes": "Minuti",
"seconds": "Secondi",
"unit": "Unità di tempo"
}
}
}

View File

@@ -1,7 +1,5 @@
{
"permanent": "永久",
"buttons": {
"shell": "Toggle shell",
"cancel": "キャンセル",
"close": "閉じる",
"copy": "コピー",
@@ -10,6 +8,7 @@
"create": "作成",
"delete": "削除",
"download": "ダウンロード",
"hideDotfiles": "",
"info": "情報",
"more": "More",
"move": "移動",
@@ -17,25 +16,28 @@
"new": "新規",
"next": "次",
"ok": "OK",
"replace": "置き換える",
"permalink": "固定リンク",
"previous": "前",
"publish": "発表",
"rename": "名前を変更",
"replace": "置き換える",
"reportIssue": "問題を報告",
"save": "保存",
"schedule": "スケジュール",
"search": "検索",
"select": "選択",
"share": "シェア",
"publish": "発表",
"selectMultiple": "複数選択",
"schedule": "スケジュール",
"share": "シェア",
"shell": "Toggle shell",
"switchView": "表示を切り替わる",
"toggleSidebar": "サイドバーを表示する",
"update": "更新",
"upload": "アップロード",
"permalink": "固定リンク"
"upload": "アップロード"
},
"success": {
"linkCopied": "リンクがコピーされました!"
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": ""
},
"errors": {
"forbidden": "You don't have permissions to access this.",
@@ -43,11 +45,11 @@
"notFound": "リソースが見つからなりませんでした。"
},
"files": {
"folders": "フォルダ",
"files": "ファイル",
"body": "本文",
"clear": "クリアー",
"closePreview": "プレビューを閉じる",
"files": "ファイル",
"folders": "フォルダ",
"home": "ホーム",
"lastModified": "最終変更",
"loading": "ローディング...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "複数選択有効",
"name": "名前",
"size": "サイズ",
"sortByLastModified": "最終変更日付によるソート",
"sortByName": "名前によるソート",
"sortBySize": "サイズによるソート",
"sortByLastModified": "最終変更日付によるソート"
"sortBySize": "サイズによるソート"
},
"help": {
"click": "ファイルやディレクトリを選択",
@@ -74,18 +76,39 @@
"f2": "ファイルの名前を変更",
"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": {
"password": "パスワード",
"passwordConfirm": "Password Confirmation",
"submit": "ログイン",
"createAnAccount": "Create an account",
"loginInstead": "Already have an account",
"password": "パスワード",
"passwordConfirm": "Password Confirmation",
"passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup",
"submit": "ログイン",
"username": "ユーザ名",
"usernameTaken": "Username already taken",
"wrongCredentials": "ユーザ名またはパスワードが間違っています。"
},
"permanent": "永久",
"prompts": {
"copy": "コピー",
"copyMessage": "コピーの目標ディレクトリを選択してください:",
@@ -102,43 +125,64 @@
"lastModified": "最終変更",
"move": "移動",
"moveMessage": "移動の目標ディレクトリを選択してください:",
"newArchetype": "ある元型に基づいて新しいポストを作成します。ファイルは コンテンツフォルダに作成されます。",
"newDir": "新しいディレクトリを作成",
"newDirMessage": "新しいディレクトリの名前を入力してください。",
"newFile": "新しいファイルを作成",
"newFileMessage": "新しいファイルの名前を入力してください。",
"numberDirs": "ディレクトリ個数",
"numberFiles": "ファイル個数",
"replace": "置き換える",
"replaceMessage": "アップロードするファイルの中でかち合う名前が一つあります。 既存のファイルを置き換えりませんか。\n",
"rename": "名前を変更",
"renameMessage": "名前を変更しようファイルは:",
"show": "表示",
"size": "サイズ",
"replace": "置き換える",
"replaceMessage": "アップロードするファイルの中でかち合う名前が一つあります。 既存のファイルを置き換えりませんか。\n",
"schedule": "スケジュール",
"scheduleMessage": "このポストの発表日付をスケジュールしてください。",
"newArchetype": "ある元型に基づいて新しいポストを作成します。ファイルは コンテンツフォルダに作成されます。"
"show": "表示",
"size": "サイズ",
"upload": "",
"uploadMessage": ""
},
"search": {
"images": "画像",
"music": "音楽",
"pdf": "PDF",
"pressToSearch": "Press enter to search...",
"search": "検索...",
"typeToSearch": "Type to search...",
"types": "種類",
"video": "ビデオ"
},
"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": "管理者",
"administrator": "管理者",
"allowCommands": "コマンドの実行",
"allowEdit": "ファイルやディレクトリの編集、名前変更と削除",
"allowNew": "ファイルとディレクトリの作成",
"allowPublish": "ポストとぺーじの発表",
"allowSignup": "Allow users to signup",
"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": "パスワードを変更",
"commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "コマンドは更新されました!",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "カスタムスタイルシ ート",
"defaultUserDescription": "This are the default settings for new users.",
"disableExternalLinks": "Disable external links (except documentation)",
"documentation": "documentation",
"examples": "例",
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"globalSettings": "グローバル設定",
"hideDotfiles": "",
"insertPath": "Insert the path",
"insertRegex": "Insert regex expression",
"instanceName": "Instance name",
"language": "言語",
"lockPassword": "新しいパスワードを変更に禁止",
"newPassword": "新しいパスワード",
@@ -146,6 +190,16 @@
"newUser": "新しいユーザー",
"password": "パスワード",
"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": "権限",
"permissionsHelp": "あなたはユーザーを管理者に設定し、または権限を個々に設定しできます。\"管理者\"を選択する場合、その他のすべての選択肢は自動的に設定されます。ユーザーの管理は管理者の権限として保留されました。",
"profileSettings": "プロファイル設定",
@@ -155,82 +209,46 @@
"rulesHelp": "ここに、あなたはこのユーザーの許可または拒否規則を設定できます。ブロックされたファイルはリストに表示されません、それではアクセスも制限されます。正規表現(regex)のサポートと範囲に相対のパスが提供されています。",
"scope": "範囲",
"settingsUpdated": "設定は更新されました!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "ユーザー",
"userCommands": "ユーザーのコマンド",
"userCommandsHelp": "空白区切りの有効のコマンドのリストを指定してください。例:",
"userCreated": "ユーザーは作成されました!",
"userDefaults": "User default settings",
"userDeleted": "ユーザーは削除されました!",
"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": "ユーザーは更新されました!",
"userDefaults": "User default settings",
"defaultUserDescription": "This are the default settings for new 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"
}
"username": "ユーザー名",
"users": "ユーザー"
},
"sidebar": {
"help": "ヘルプ",
"hugoNew": "Hugo New",
"login": "Login",
"signup": "Signup",
"logout": "ログアウト",
"myFiles": "私のファイル",
"newFile": "新しいファイルを作成",
"newFolder": "新しいフォルダを作成",
"preview": "プレビュー",
"settings": "設定",
"siteSettings": "サイト設定",
"hugoNew": "Hugo New",
"preview": "プレビュー"
"signup": "Signup",
"siteSettings": "サイト設定"
},
"search": {
"images": "画像",
"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": "한국어"
"success": {
"linkCopied": "リンクがコピーされました!"
},
"time": {
"unit": "時間単位",
"seconds": "秒",
"minutes": "分",
"days": "",
"hours": "時間",
"days": "日"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
"minutes": "分",
"seconds": "秒",
"unit": "時間単位"
}
}
}

View File

@@ -1,7 +1,5 @@
{
"permanent": "영구",
"buttons": {
"shell": "쉘 전환",
"cancel": "취소",
"close": "닫기",
"copy": "복사",
@@ -10,6 +8,7 @@
"create": "생성",
"delete": "삭제",
"download": "다운로드",
"hideDotfiles": "",
"info": "정보",
"more": "더보기",
"move": "이동",
@@ -17,25 +16,28 @@
"new": "신규",
"next": "다음",
"ok": "확인",
"replace": "대체",
"permalink": "링크 얻기",
"previous": "이전",
"publish": "게시",
"rename": "이름 바꾸기",
"replace": "대체",
"reportIssue": "이슈 보내기",
"save": "저장",
"schedule": "일정",
"search": "검색",
"select": "선택",
"share": "공유",
"publish": "게시",
"selectMultiple": "다중 선택",
"schedule": "일정",
"share": "공유",
"shell": "쉘 전환",
"switchView": "보기 전환",
"toggleSidebar": "사이드바 전환",
"update": "업데이트",
"upload": "업로드",
"permalink": "링크 얻기"
"upload": "업로드"
},
"success": {
"linkCopied": "링크가 복사되었습니다!"
"download": {
"downloadFile": "파일 다운로드",
"downloadFolder": "폴더 다운로드",
"downloadSelected": ""
},
"errors": {
"forbidden": "접근 권한이 없습니다.",
@@ -43,11 +45,11 @@
"notFound": "해당 경로를 찾을 수 없습니다."
},
"files": {
"folders": "폴더",
"files": "파일",
"body": "본문",
"clear": "지우기",
"closePreview": "미리보기 닫기",
"files": "파일",
"folders": "폴더",
"home": "홈",
"lastModified": "최종 수정",
"loading": "로딩중...",
@@ -56,9 +58,9 @@
"multipleSelectionEnabled": "다중 선택 켜짐",
"name": "이름",
"size": "크기",
"sortByLastModified": "수정시간순 정렬",
"sortByName": "이름순",
"sortBySize": "크기순",
"sortByLastModified": "수정시간순 정렬"
"sortBySize": "크기순"
},
"help": {
"click": "파일이나 디렉토리를 선택해주세요.",
@@ -74,18 +76,39 @@
"f2": "파일 이름 변경",
"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": {
"password": "비밀번호",
"passwordConfirm": "비밀번호 확인",
"submit": "로그인",
"createAnAccount": "계정 생성",
"loginInstead": "이미 계정이 있습니다",
"password": "비밀번호",
"passwordConfirm": "비밀번호 확인",
"passwordsDontMatch": "비밀번호가 일치하지 않습니다",
"usernameTaken": "사용자 이름이 존재합니다",
"signup": "가입하기",
"submit": "로그인",
"username": "사용자 이름",
"usernameTaken": "사용자 이름이 존재합니다",
"wrongCredentials": "사용자 이름 또는 비밀번호를 확인하십시오"
},
"permanent": "영구",
"prompts": {
"copy": "복사",
"copyMessage": "복사할 디렉토리:",
@@ -102,43 +125,64 @@
"lastModified": "최종 수정",
"move": "이동",
"moveMessage": "이동할 화일 또는 디렉토리를 선택하세요:",
"newArchetype": "원형을 유지하는 포스트를 생성합니다. 파일은 컨텐트 폴더에 생성됩니다.",
"newDir": "새 디렉토리",
"newDirMessage": "새 디렉토리 이름을 입력해주세요.",
"newFile": "새 파일",
"newFileMessage": "새 파일 이름을 입력해주세요.",
"numberDirs": "디렉토리 수",
"numberFiles": "파일 수",
"replace": "대체하기",
"replaceMessage": "동일한 파일 이름이 존재합니다. 현재 파일을 덮어쓸까요?\n",
"rename": "이름 변경",
"renameMessage": "새로운 이름을 입력하세요.",
"show": "기",
"size": "크기",
"replace": "대체하기",
"replaceMessage": "동일한 파일 이름이 존재합니다. 현재 파일을 덮어쓸까요?\n",
"schedule": "일정",
"scheduleMessage": "이 글을 공개할 시간을 알려주세요.",
"newArchetype": "원형을 유지하는 포스트를 생성합니다. 파일은 컨텐트 폴더에 생성됩니다."
"show": "보기",
"size": "크기",
"upload": "",
"uploadMessage": ""
},
"search": {
"images": "이미지",
"music": "음악",
"pdf": "PDF",
"pressToSearch": "검색하려면 엔터를 입력하세요",
"search": "검색...",
"typeToSearch": "검색어 입력...",
"types": "Types",
"video": "비디오"
},
"settings": {
"instanceName": "인스턴스 이름",
"brandingDirectoryPath": "브랜드 디렉토리 경로",
"documentation": "문서",
"branding": "브랜딩",
"disableExternalLinks": "외부 링크 감추기",
"brandingHelp": "File Browser 인스턴스는 이름, 로고, 스타일 등을 변경할 수 있습니다. 자세한 사항은 여기{0}에서 확인하세요.",
"admin": "관리자",
"administrator": "관리자",
"allowCommands": "명령 실행",
"allowEdit": "파일/디렉토리의 수정/변경/삭제 허용",
"allowNew": "파일/디렉토리 생성 허용",
"allowPublish": "새 포스트/페이지 생성 허용",
"allowSignup": "사용자 가입 허용",
"avoidChanges": "(수정하지 않으면 비워두세요)",
"branding": "브랜딩",
"brandingDirectoryPath": "브랜드 디렉토리 경로",
"brandingHelp": "File Browser 인스턴스는 이름, 로고, 스타일 등을 변경할 수 있습니다. 자세한 사항은 여기{0}에서 확인하세요.",
"changePassword": "비밀번호 변경",
"commandRunner": "명령 실행기",
"commandRunnerHelp": "이벤트에 해당하는 명령을 설정하세요. 줄당 1개의 명령을 적으세요. 환경 변수{0} 와 {1}이 사용가능하며, {0} 은 {1}에 상대 경로 입니다. 자세한 사항은 {2} 를 참조하세요.",
"commandsUpdated": "명령 수정됨!",
"createUserDir": "Auto create user home dir while adding new user",
"customStylesheet": "커스텀 스타일시트",
"defaultUserDescription": "아래 사항은 신규 사용자들에 대한 기본 설정입니다.",
"disableExternalLinks": "외부 링크 감추기",
"documentation": "문서",
"examples": "예",
"executeOnShell": "쉘에서 실행",
"executeOnShellDescription": "기본적으로 File Browser 는 바이너리를 명령어로 호출하여 실행합니다. 쉘을 통해 실행하기를 원한다면, Bash 또는 PowerShell 에 필요한 인수와 플래그를 설정하세요. 사용자 명령어와 이벤트 훅에 모두 적용됩니다.",
"globalRules": "규칙에 대한 전역설정으로 모든 사용자에게 적용됩니다. 지정된 규칙은 사용자 설정을 덮어쓰기 합니다.",
"globalSettings": "전역 설정",
"hideDotfiles": "",
"insertPath": "경로 입력",
"insertRegex": "정규식 입력",
"instanceName": "인스턴스 이름",
"language": "언어",
"lockPassword": "사용자에 의한 비밀번호 변경을 허용하지 않음",
"newPassword": "새로운 비밀번호",
@@ -146,6 +190,16 @@
"newUser": "새로운 사용자",
"password": "비밀번호",
"passwordUpdated": "비밀번호 수정 완료!",
"path": "",
"perm": {
"create": "파일이나 디렉토리 생성하기",
"delete": "화일이나 디렉토리 삭제하기",
"download": "다운로드",
"execute": "명령 실행",
"modify": "파일 편집",
"rename": "파일 이름 변경 또는 디렉토리 이동",
"share": "파일 공유하기"
},
"permissions": "권한",
"permissionsHelp": "사용자를 관리자로 만들거나 권한을 부여할 수 있습니다. 관리자를 선택하면, 모든 옵션이 자동으로 선택됩니다. 사용자 관리는 현재 관리자만 할 수 있습니다.\n",
"profileSettings": "프로필 설정",
@@ -155,82 +209,46 @@
"rulesHelp": "사용자별로 규칙을 허용/방지를 지정할 수 있습니다. 방지된 파일은 보이지 않고 사용자들은 접근할 수 없습니다. 사용자의 접근 허용 범위와 관련해 정규표현식(regex)과 경로를 지원합니다.\n",
"scope": "범위",
"settingsUpdated": "설정 수정됨!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"themes": {
"dark": "",
"light": "",
"title": ""
},
"user": "사용자",
"userCommands": "명령어",
"userCommandsHelp": "사용에게 허용할 명령어를 공백으로 구분하여 입력하세요. 예:\n",
"userCreated": "사용자 생성됨!",
"userDefaults": "사용자 기본 설정",
"userDeleted": "사용자 삭제됨!",
"userManagement": "사용자 관리",
"username": "사용자 이름",
"users": "사용자",
"globalRules": "규칙에 대한 전역설정으로 모든 사용자에게 적용됩니다. 지정된 규칙은 사용자 설정을 덮어쓰기 합니다.",
"allowSignup": "사용자 가입 허용",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "정규식 입력",
"insertPath": "경로 입력",
"userUpdated": "사용자 수정됨!",
"userDefaults": "사용자 기본 설정",
"defaultUserDescription": "아래 사항은 신규 사용자들에 대한 기본 설정입니다.",
"executeOnShell": "쉘에서 실행",
"executeOnShellDescription": "기본적으로 File Browser 는 바이너리를 명령어로 호출하여 실행합니다. 쉘을 통해 실행하기를 원한다면, Bash 또는 PowerShell 에 필요한 인수와 플래그를 설정하세요. 사용자 명령어와 이벤트 훅에 모두 적용됩니다.",
"perm": {
"create": "파일이나 디렉토리 생성하기",
"delete": "화일이나 디렉토리 삭제하기",
"download": "다운로드",
"modify": "파일 편집",
"execute": "명령 실행",
"rename": "파일 이름 변경 또는 디렉토리 이동",
"share": "파일 공유하기"
}
"username": "사용자 이름",
"users": "사용자"
},
"sidebar": {
"help": "도움말",
"hugoNew": "Hugo New",
"login": "로그인",
"signup": "가입하기",
"logout": "로그아웃",
"myFiles": "내 파일",
"newFile": "새로운 파일",
"newFolder": "새로운 폴더",
"preview": "미리보기",
"settings": "설정",
"siteSettings": "사이트 설정",
"hugoNew": "Hugo New",
"preview": "미리보기"
"signup": "가입하기",
"siteSettings": "사이트 설정"
},
"search": {
"images": "이미지",
"music": "음악",
"pdf": "PDF",
"types": "Types",
"video": "비디오",
"search": "검색...",
"typeToSearch": "검색어 입력...",
"pressToSearch": "검색하려면 엔터를 입력하세요"
},
"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": "한국어"
"success": {
"linkCopied": "링크가 복사되었습니다!"
},
"time": {
"unit": "Time Unit",
"seconds": "초",
"minutes": "분",
"days": "",
"hours": "시",
"days": "일"
},
"download": {
"downloadFile": "파일 다운로드",
"downloadFolder": "폴더 다운로드"
"minutes": "분",
"seconds": "초",
"unit": "Time Unit"
}
}
}

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