Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f2d221083 | ||
|
|
74b7cd8e81 | ||
|
|
6cb51b4eb4 | ||
|
|
f09bf3e1d0 | ||
|
|
6f345be3e4 | ||
|
|
ddd4ffa4ca | ||
|
|
deabc80fd7 | ||
|
|
b6a51bed51 | ||
|
|
0426629a59 | ||
|
|
0358e42d2c | ||
|
|
3768e3345f | ||
|
|
16e434be66 | ||
|
|
bf303c536a | ||
|
|
43a460993c | ||
|
|
7f0673ee70 | ||
|
|
4c3099a086 | ||
|
|
f0bc9167b1 | ||
|
|
23d646c456 | ||
|
|
76add9e527 | ||
|
|
c63cc5a2d2 | ||
|
|
25c8788390 | ||
|
|
aa52b07bb1 | ||
|
|
76b466f649 | ||
|
|
8ecc2da947 | ||
|
|
8650d2ffe7 | ||
|
|
34d7d2c8c4 | ||
|
|
201329abce | ||
|
|
f2b5dd3787 | ||
|
|
5072bbb2cb | ||
|
|
6b19ab6613 | ||
|
|
730be5ef6b | ||
|
|
46ee595389 | ||
|
|
dee465ab86 | ||
|
|
209f9fa77f | ||
|
|
ba8c09f454 | ||
|
|
16a34defc0 | ||
|
|
7d1e03075d | ||
|
|
1c25f6ee69 | ||
|
|
aa172b8bb5 | ||
|
|
4711e7bcd5 | ||
|
|
8a47aee137 | ||
|
|
190cb99a79 | ||
|
|
603203848a | ||
|
|
5e6f14b5dc | ||
|
|
976eb5583d | ||
|
|
b92152693f | ||
|
|
7ec24d9d77 | ||
|
|
20ebbf6611 | ||
|
|
ba7e71a7c3 | ||
|
|
8973c4598f | ||
|
|
18889ad725 | ||
|
|
73ccbe912f | ||
|
|
84e3a98303 | ||
|
|
7dd5b34d42 | ||
|
|
4470d0a704 | ||
|
|
a76e01d2b7 | ||
|
|
2697093ac1 | ||
|
|
59f9964e80 | ||
|
|
1516d9932b | ||
|
|
fcb115f42d | ||
|
|
e410272e6b | ||
|
|
87f1881b42 | ||
|
|
c0d85f3d85 | ||
|
|
98d79b8ed9 | ||
|
|
fe80730bb1 | ||
|
|
6c8ee96e6a | ||
|
|
b521dec8f9 | ||
|
|
e9baf0c4b6 | ||
|
|
e1a6f593e1 | ||
|
|
4b068b3058 | ||
|
|
da54bd6c21 |
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -4,19 +4,19 @@ about: Create a report to help us improve
|
||||
---
|
||||
|
||||
**Description**
|
||||
A clear and concise description of what the issue is about. What are you trying to do?
|
||||
<!-- A clear and concise description of what the issue is about. What are you trying to do? -->
|
||||
|
||||
**Expected behaviour**
|
||||
What did you expect to happen?
|
||||
<!-- What did you expect to happen? -->
|
||||
|
||||
**What is happening instead?**
|
||||
Please, give full error messages and/or log.
|
||||
<!-- Please, give full error messages and/or log. -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here. If applicable, add screenshots to help explain your problem.
|
||||
<!-- Add any other context about the problem here. If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**How to reproduce?**
|
||||
Tell us how to reproduce this issue. How can someone who is starting from scratch reproduce this behaviour as minimally as possible?
|
||||
<!-- Tell us how to reproduce this issue. How can someone who is starting from scratch reproduce this behaviour as minimally as possible? -->
|
||||
|
||||
**Files**
|
||||
A list of relevant files for this issue. Large files can be uploaded one-by-one or in a tarball/zipfile.
|
||||
<!-- A list of relevant files for this issue. Large files can be uploaded one-by-one or in a tarball/zipfile. -->
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -4,13 +4,13 @@ about: Suggest an idea for this project
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
Add a clear and concise description of what the problem is. E.g. *I'm always frustrated when [...]*
|
||||
<!-- Add a clear and concise description of what the problem is. E.g. *I'm always frustrated when [...]* -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
Add a clear and concise description of what you want to happen.
|
||||
<!-- Add a clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
Add a clear and concise description of any alternative solutions or features you've considered.
|
||||
<!-- Add a clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,6 +1,8 @@
|
||||
**Description**
|
||||
<!--
|
||||
Please explain the changes you made here.
|
||||
If the feature changes current behaviour, explain why your solution is better.
|
||||
-->
|
||||
|
||||
:rotating_light: Before submitting your PR, please read [community](https://github.com/filebrowser/community), and indicate which issues (in any of the repos) are either fixed or closed by this PR. See [GitHub Help: Closing issues using keywords](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||
|
||||
@@ -11,6 +13,8 @@ If the feature changes current behaviour, explain why your solution is better.
|
||||
- [ ] AVOID breaking the continuous integration build.
|
||||
|
||||
**Further comments**
|
||||
<!--
|
||||
If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did, what alternatives you considered, etc.
|
||||
|
||||
:heart: Thank you!
|
||||
-->
|
||||
|
||||
@@ -6,6 +6,8 @@ linters-settings:
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 50
|
||||
gci:
|
||||
local-prefixes: github.com/filebrowser/filebrowser
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
@@ -26,8 +28,6 @@ linters-settings:
|
||||
min-complexity: 15
|
||||
goimports:
|
||||
local-prefixes: github.com/filebrowser/filebrowser
|
||||
golint:
|
||||
min-confidence: 0
|
||||
gomnd:
|
||||
settings:
|
||||
mnd:
|
||||
@@ -58,26 +58,25 @@ linters:
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- exportloopref
|
||||
- exhaustive
|
||||
- funlen
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- goimports
|
||||
- golint
|
||||
- gomnd
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- interfacer
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- rowserrcheck
|
||||
- scopelint
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- stylecheck
|
||||
@@ -89,19 +88,6 @@ linters:
|
||||
- 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
|
||||
@@ -118,6 +104,9 @@ issues:
|
||||
- text: "Auther"
|
||||
linters:
|
||||
- misspell
|
||||
- text: "strconv.Parse"
|
||||
linters:
|
||||
- gomnd
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
|
||||
114
CHANGELOG.md
114
CHANGELOG.md
@@ -2,6 +2,120 @@
|
||||
|
||||
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.18.0](https://github.com/filebrowser/filebrowser/compare/v2.17.2...v2.18.0) (2021-10-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability to select file modified time format ([#1536](https://github.com/filebrowser/filebrowser/issues/1536)) ([0426629](https://github.com/filebrowser/filebrowser/commit/0426629a59c712849570d3e29956948ae7725a4a))
|
||||
* add manifest theme color param ([#1542](https://github.com/filebrowser/filebrowser/issues/1542)) ([0358e42](https://github.com/filebrowser/filebrowser/commit/0358e42d2c206732fffa77714f5a66f4fe50a69d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* back button behaviour in preview ([#1573](https://github.com/filebrowser/filebrowser/issues/1573)) ([deabc80](https://github.com/filebrowser/filebrowser/commit/deabc80fd7670983039dfcd29531b45002ca5d9e))
|
||||
* fix sidebar navigation on mobile devices ([#1618](https://github.com/filebrowser/filebrowser/issues/1618)) ([f09bf3e](https://github.com/filebrowser/filebrowser/commit/f09bf3e1d076b27d29ba8a91cf448a99993bc444))
|
||||
* search box is misaligned when the browser preferred font size is other than 16px ([#1613](https://github.com/filebrowser/filebrowser/issues/1613)) ([6f345be](https://github.com/filebrowser/filebrowser/commit/6f345be3e47ba57ecc1eb9a62587ab949078c125))
|
||||
* security issue in command runner (closes [#1621](https://github.com/filebrowser/filebrowser/issues/1621)) ([74b7cd8](https://github.com/filebrowser/filebrowser/commit/74b7cd8e81840537a8206317344f118093153e8d))
|
||||
* set correct editor height regardless of preferred font size ([#1614](https://github.com/filebrowser/filebrowser/issues/1614)) ([ddd4ffa](https://github.com/filebrowser/filebrowser/commit/ddd4ffa4caa6b292a3a644ecd897aba1237c7503))
|
||||
* zoom pics when dlclick at first time ([#1561](https://github.com/filebrowser/filebrowser/issues/1561)) ([b6a51be](https://github.com/filebrowser/filebrowser/commit/b6a51bed516814944f8aa41440652242d57824c5))
|
||||
|
||||
### [2.17.2](https://github.com/filebrowser/filebrowser/compare/v2.17.1...v2.17.2) (2021-08-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bug with inlineLink not creating url properly ([#1515](https://github.com/filebrowser/filebrowser/issues/1515)) ([43a4609](https://github.com/filebrowser/filebrowser/commit/43a460993c3f0d158b876db4b20caa7963e9f361))
|
||||
|
||||
### [2.17.1](https://github.com/filebrowser/filebrowser/compare/v2.17.0...v2.17.1) (2021-08-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* internal server error if --disable-preview-resize flag is set (closes [#1510](https://github.com/filebrowser/filebrowser/issues/1510)) ([4c3099a](https://github.com/filebrowser/filebrowser/commit/4c3099a086c206dcb3bc70ee8c8da02eee61c30b))
|
||||
|
||||
## [2.17.0](https://github.com/filebrowser/filebrowser/compare/v2.16.1...v2.17.0) (2021-08-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* open file option on preview ([76add9e](https://github.com/filebrowser/filebrowser/commit/76add9e5274b0373c6b983e3b20e387a14ea6c9e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 401 error in share view open file button ([#1495](https://github.com/filebrowser/filebrowser/issues/1495)) ([25c8788](https://github.com/filebrowser/filebrowser/commit/25c87883908babde073390a2e2320a8e5880a87c))
|
||||
* escape quote on index template ([23d646c](https://github.com/filebrowser/filebrowser/commit/23d646c456876d06cf48e71c1e57b69de99511f0)), closes [#1501](https://github.com/filebrowser/filebrowser/issues/1501)
|
||||
* file caching directive ([c63cc5a](https://github.com/filebrowser/filebrowser/commit/c63cc5a2d25909cc4e2f2e7235f276ec66c32bf2))
|
||||
|
||||
### [2.16.1](https://github.com/filebrowser/filebrowser/compare/v2.16.0...v2.16.1) (2021-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check symlink target type (closes [#1488](https://github.com/filebrowser/filebrowser/issues/1488)) ([76b466f](https://github.com/filebrowser/filebrowser/commit/76b466f6492e74cf13e66a33e7e5f597ac92b240))
|
||||
|
||||
## [2.16.0](https://github.com/filebrowser/filebrowser/compare/v2.15.0...v2.16.0) (2021-07-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* browser cache directives ([190cb99](https://github.com/filebrowser/filebrowser/commit/190cb99a79a0d438eca2da13539f8c6449ad73ac))
|
||||
* display error messages on settings ([6032038](https://github.com/filebrowser/filebrowser/commit/603203848a8b2221158088b6d849609db4c0c46c))
|
||||
* file name on page title ([16a34de](https://github.com/filebrowser/filebrowser/commit/16a34defc02554a77c6ac47b9e17e69d098a09fe))
|
||||
* gzip encoding for static js files ([aa172b8](https://github.com/filebrowser/filebrowser/commit/aa172b8bb5f17d5f5cb9666bfb5ee650d8091fb5))
|
||||
* loading spinner on views navigation ([976eb55](https://github.com/filebrowser/filebrowser/commit/976eb5583dae474125fd7ddec5dc19b6c291f98f))
|
||||
* message for connection error ([5e6f14b](https://github.com/filebrowser/filebrowser/commit/5e6f14b5dcb9c5efdf526f1346e09c2d0b2f6974))
|
||||
* mod time title on file info ([7d1e030](https://github.com/filebrowser/filebrowser/commit/7d1e03075d2c27148f60813defa0f68403d1d3c2))
|
||||
* open file option on share ([1c25f6e](https://github.com/filebrowser/filebrowser/commit/1c25f6ee69bd71eed82af7020006d0e27537a967))
|
||||
* show more button on share ([ba8c09f](https://github.com/filebrowser/filebrowser/commit/ba8c09f454feeadf4a1e97547a34151a81b389d5))
|
||||
* support for IE11 browser ([7ec24d9](https://github.com/filebrowser/filebrowser/commit/7ec24d9d7794fa37825f64ca2d1575f568fb1362))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* break resource create/update handlers on error (closes [#1464](https://github.com/filebrowser/filebrowser/issues/1464)) ([5072bbb](https://github.com/filebrowser/filebrowser/commit/5072bbb2cbf5b29d041629faa8367f15e4d145a2))
|
||||
* copying files with special characters ([20ebbf6](https://github.com/filebrowser/filebrowser/commit/20ebbf6611b734371426fb1b9cb5e388be90bf7e))
|
||||
* delete image cache when moving ([8973c45](https://github.com/filebrowser/filebrowser/commit/8973c4598ff817647f1f1ad6ee36480054cd2776))
|
||||
* don't remove files on unsuccessful updates (closes [#1456](https://github.com/filebrowser/filebrowser/issues/1456)) ([6b19ab6](https://github.com/filebrowser/filebrowser/commit/6b19ab6613b12be7f075299cd98f4b41d43827c7))
|
||||
* failure on broken symlink deletion ([8650d2f](https://github.com/filebrowser/filebrowser/commit/8650d2ffe7a29cbafa800efcecbf6a61598a9f0c))
|
||||
* inconsistent double click on listing item ([ba7e71a](https://github.com/filebrowser/filebrowser/commit/ba7e71a7c3b0cc71012e5adf94b1c642e554972e))
|
||||
* no items displayed on file listing ([18889ad](https://github.com/filebrowser/filebrowser/commit/18889ad725f7f7e5a7e3f7abcf156487556dbeaf))
|
||||
* omit file content ([209f9fa](https://github.com/filebrowser/filebrowser/commit/209f9fa77f751054512355f2b74b9b7258465d0b))
|
||||
* short commit sha and typo fix in Makefile ([#1411](https://github.com/filebrowser/filebrowser/issues/1411)) ([46ee595](https://github.com/filebrowser/filebrowser/commit/46ee59538914dc2859f0da6b32e2d062d0a01b10))
|
||||
|
||||
## [2.15.0](https://github.com/filebrowser/filebrowser/compare/v2.14.1...v2.15.0) (2021-04-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add EXIF thumbnail support for JPEG files ([#1234](https://github.com/filebrowser/filebrowser/issues/1234)) ([7dd5b34](https://github.com/filebrowser/filebrowser/commit/7dd5b34d425dfbc2782152310483cbecf85c800a))
|
||||
* dynamic autoplay on previewer ([a76e01d](https://github.com/filebrowser/filebrowser/commit/a76e01d2b78a785f3665a8b3532c7cc566bfabce))
|
||||
* dynamic item count on file listing ([6c8ee96](https://github.com/filebrowser/filebrowser/commit/6c8ee96e6a21fae5d4608bdc7a5c5a161d7dafd3))
|
||||
* dynamic zoom limit on previewer ([e410272](https://github.com/filebrowser/filebrowser/commit/e410272e6be6a0b660efe8d4eee6c6e9dd834cc5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* buttons without permission on header ([1516d99](https://github.com/filebrowser/filebrowser/commit/1516d9932bf9926ac8b4cb3e738a5f51e80d5b1d))
|
||||
* check modify permission on file overwrite ([59f9964](https://github.com/filebrowser/filebrowser/commit/59f9964e80c8233775f27be33a4c16a31bfe848a))
|
||||
* empty archive name on directory download ([2697093](https://github.com/filebrowser/filebrowser/commit/2697093ac151f74eea3022951d128acfe04d1dcf))
|
||||
* empty text file on editor ([e9baf0c](https://github.com/filebrowser/filebrowser/commit/e9baf0c4b688fab291cdc842ec464c7a7a816499))
|
||||
* error causes panic on upload ([e1a6f59](https://github.com/filebrowser/filebrowser/commit/e1a6f593e1824e7fa4345a61dff5b1bb8cd22d05))
|
||||
* hidden editor header on Safari ([b521dec](https://github.com/filebrowser/filebrowser/commit/b521dec8f9b14dd92248c429e902ebc639046389))
|
||||
* image quality switch on previewer ([c0d85f3](https://github.com/filebrowser/filebrowser/commit/c0d85f3d85926c8790757bf142140d19455ae8ca))
|
||||
* list item interactions on share ([87f1881](https://github.com/filebrowser/filebrowser/commit/87f1881b429877a740ea84a8e783ad4103248289))
|
||||
* missing bold variation for Roboto font ([98d79b8](https://github.com/filebrowser/filebrowser/commit/98d79b8ed955df5691a306d709b4ab60d91da408))
|
||||
* mouse wheel zoom on previewer ([fcb115f](https://github.com/filebrowser/filebrowser/commit/fcb115f42d33db2be7a4d428ec53d65d6050320b))
|
||||
* no header button animations on file listing ([fe80730](https://github.com/filebrowser/filebrowser/commit/fe80730bb135b38e4d9de470c75cbe10b1aec201))
|
||||
|
||||
### [2.14.1](https://github.com/filebrowser/filebrowser/compare/v2.14.0...v2.14.1) (2021-03-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* display public routes with header proxy auth ([da54bd6](https://github.com/filebrowser/filebrowser/commit/da54bd6c214d7ee39b71d710ddfe6dd25fc4e5d6))
|
||||
|
||||
## [2.14.0](https://github.com/filebrowser/filebrowser/compare/v2.13.0...v2.14.0) (2021-03-21)
|
||||
|
||||
|
||||
|
||||
8
Makefile
8
Makefile
@@ -20,10 +20,10 @@ 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)
|
||||
VERSION_HASH = $(shell git rev-parse --short HEAD)
|
||||
BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
LDFLAGS += -X "$(MODULE)/varsion.Version=$(VERSION)" -X "$(MODULE)/varsion.CommitSHA=$(VERSION_HASH)"
|
||||
LDFLAGS += -X "$(MODULE)/version.Version=$(VERSION)" -X "$(MODULE)/version.CommitSHA=$(VERSION_HASH)"
|
||||
|
||||
# tools
|
||||
$(BIN):
|
||||
@@ -32,10 +32,10 @@ $(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
|
||||
$(BIN)/golangci-lint: PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lint@v1.41.1
|
||||
|
||||
GOIMPORTS = $(BIN)/goimports
|
||||
$(BIN)/goimports: PACKAGE=golang.org/x/tools/cmd/goimports@v0.1.0
|
||||
$(BIN)/goimports: PACKAGE=golang.org/x/tools/cmd/goimports@v0.1.5
|
||||
|
||||
## build: Build
|
||||
.PHONY: build
|
||||
|
||||
26
SECURITY.md
Normal file
26
SECURITY.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x | :white_check_mark: |
|
||||
| < 2.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Vulnerabilities should be reported to filebrowser@googlegroups.com - which is a private, maintainer-only group. Maintainers will attempt to respond to/confirm reports within 2-3 days, but if you believe your report to be "critical" to user safety and security, please note as such in the subject. We have tens of thousands of users using our software, and take security vulnerabilities seriously.
|
||||
|
||||
When reporting an issue, where possible, please provide at least:
|
||||
|
||||
* The commit version the issue was identified at
|
||||
* A proof of concept (plaintext; no binaries)
|
||||
* Steps to reproduce
|
||||
* Your recommended remediation(s), if any.
|
||||
|
||||
The FileBrowser team is a volunteer-only effort, and may reach back out for clarification.
|
||||
|
||||
> Note: Please do not open public issues for security issues, as GitHub does not provide facility for private issues, and deleting the issue makes it hard to triage/respond back to the reporter.
|
||||
@@ -41,6 +41,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
||||
flags.String("recaptcha.secret", "", "ReCaptcha secret")
|
||||
|
||||
flags.String("branding.name", "", "replace 'File Browser' by this name")
|
||||
flags.String("branding.color", "", "set the theme color")
|
||||
flags.String("branding.files", "", "path to directory with images and custom styles")
|
||||
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
|
||||
}
|
||||
@@ -121,7 +122,7 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
|
||||
}
|
||||
|
||||
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) //nolint:gomnd
|
||||
|
||||
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
||||
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
||||
@@ -131,6 +132,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
||||
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
|
||||
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
|
||||
fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Branding.DisableExternal)
|
||||
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color)
|
||||
fmt.Fprintln(w, "\nServer:")
|
||||
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
|
||||
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)
|
||||
|
||||
@@ -51,6 +51,8 @@ you want to change. Other options will remain unchanged.`,
|
||||
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
|
||||
case "branding.name":
|
||||
set.Branding.Name = mustGetString(flags, flag.Name)
|
||||
case "branding.color":
|
||||
set.Branding.Color = mustGetString(flags, flag.Name)
|
||||
case "branding.disableExternal":
|
||||
set.Branding.DisableExternal = mustGetBool(flags, flag.Name)
|
||||
case "branding.files":
|
||||
|
||||
@@ -61,10 +61,10 @@ 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.Uint32("socket-perm", 0666, "unix socket file permissions") //nolint:gomnd
|
||||
flags.StringP("baseurl", "b", "", "base url")
|
||||
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
||||
flags.Int("img-processors", 4, "image processors count")
|
||||
flags.Int("img-processors", 4, "image processors count") //nolint:gomnd
|
||||
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
||||
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
||||
flags.Bool("disable-exec", false, "disables Command Runner feature")
|
||||
@@ -128,7 +128,7 @@ user created with the credentials from options "username" and "password".`,
|
||||
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
||||
checkErr(err)
|
||||
if cacheDir != "" {
|
||||
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet
|
||||
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet,gomnd
|
||||
log.Fatalf("can't make directory %s: %s", cacheDir, err)
|
||||
}
|
||||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||
|
||||
@@ -28,7 +28,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(1, 2)(cmd, args); err != nil {
|
||||
if err := cobra.RangeArgs(1, 2)(cmd, args); err != nil { //nolint:gomnd
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ var usersCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func printUsers(usrs []*users.User) {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) //nolint:gomnd
|
||||
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
|
||||
|
||||
for _, u := range usrs {
|
||||
@@ -53,7 +53,7 @@ func printUsers(usrs []*users.User) {
|
||||
}
|
||||
|
||||
func parseUsernameOrID(arg string) (username string, id uint) {
|
||||
id64, err := strconv.ParseUint(arg, 10, 0)
|
||||
id64, err := strconv.ParseUint(arg, 10, 64) //nolint:gomnd
|
||||
if err != nil {
|
||||
return arg, 0
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func dbExists(path string) (bool, error) {
|
||||
d := filepath.Dir(path)
|
||||
_, err = os.Stat(d)
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet
|
||||
if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet,gomnd
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
|
||||
@@ -37,11 +37,11 @@ func (f *FileCache) Store(ctx context.Context, key string, value []byte) error {
|
||||
defer mu.Unlock()
|
||||
|
||||
fileName := f.getFileName(key)
|
||||
if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil {
|
||||
if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil { //nolint:gomnd
|
||||
return err
|
||||
}
|
||||
|
||||
if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil {
|
||||
if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil { //nolint:gomnd
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ type FileInfo struct {
|
||||
ModTime time.Time `json:"modified"`
|
||||
Mode os.FileMode `json:"mode"`
|
||||
IsDir bool `json:"isDir"`
|
||||
IsSymlink bool `json:"isSymlink"`
|
||||
Type string `json:"type"`
|
||||
Subtitles []string `json:"subtitles,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
@@ -50,6 +51,7 @@ type FileOptions struct {
|
||||
ReadHeader bool
|
||||
Token string
|
||||
Checker rules.Checker
|
||||
Content bool
|
||||
}
|
||||
|
||||
// NewFileInfo creates a File object from a path and a given user. This File
|
||||
@@ -60,12 +62,73 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
|
||||
info, err := opts.Fs.Stat(opts.Path)
|
||||
file, err := stat(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file := &FileInfo{
|
||||
if opts.Expand {
|
||||
if file.IsDir {
|
||||
if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
err = file.detectType(opts.Modify, opts.Content, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return file, err
|
||||
}
|
||||
|
||||
func stat(opts FileOptions) (*FileInfo, error) {
|
||||
var file *FileInfo
|
||||
|
||||
if lstaterFs, ok := opts.Fs.(afero.Lstater); ok {
|
||||
info, _, err := lstaterFs.LstatIfPossible(opts.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file = &FileInfo{
|
||||
Fs: opts.Fs,
|
||||
Path: opts.Path,
|
||||
Name: info.Name(),
|
||||
ModTime: info.ModTime(),
|
||||
Mode: info.Mode(),
|
||||
IsDir: info.IsDir(),
|
||||
IsSymlink: IsSymlink(info.Mode()),
|
||||
Size: info.Size(),
|
||||
Extension: filepath.Ext(info.Name()),
|
||||
Token: opts.Token,
|
||||
}
|
||||
}
|
||||
|
||||
// regular file
|
||||
if file != nil && !file.IsSymlink {
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// fs doesn't support afero.Lstater interface or the file is a symlink
|
||||
info, err := opts.Fs.Stat(opts.Path)
|
||||
if err != nil {
|
||||
// can't follow symlink
|
||||
if file != nil && file.IsSymlink {
|
||||
return file, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set correct file size in case of symlink
|
||||
if file != nil && file.IsSymlink {
|
||||
file.Size = info.Size()
|
||||
file.IsDir = info.IsDir()
|
||||
return file, nil
|
||||
}
|
||||
|
||||
file = &FileInfo{
|
||||
Fs: opts.Fs,
|
||||
Path: opts.Path,
|
||||
Name: info.Name(),
|
||||
@@ -77,21 +140,7 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
||||
Token: opts.Token,
|
||||
}
|
||||
|
||||
if opts.Expand {
|
||||
if file.IsDir {
|
||||
if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
err = file.detectType(opts.Modify, true, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return file, err
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// Checksum checksums a given File for a given User, using a specific
|
||||
@@ -170,7 +219,7 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
|
||||
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
|
||||
case (strings.HasPrefix(mimetype, "text") || !isBinary(buffer)) && i.Size <= 10*1024*1024: // 10 MB
|
||||
i.Type = "text"
|
||||
|
||||
if !modify {
|
||||
@@ -203,7 +252,7 @@ func (i *FileInfo) readFirstBytes() []byte {
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
buffer := make([]byte, 512)
|
||||
buffer := make([]byte, 512) //nolint:gomnd
|
||||
n, err := reader.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Print(err)
|
||||
@@ -251,7 +300,9 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
||||
continue
|
||||
}
|
||||
|
||||
isSymlink := false
|
||||
if IsSymlink(f.Mode()) {
|
||||
isSymlink = true
|
||||
// It's a symbolic link. We try to follow it. If it doesn't work,
|
||||
// we stay with the link information instead of the target's.
|
||||
info, err := i.Fs.Stat(fPath)
|
||||
@@ -267,6 +318,7 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
||||
ModTime: f.ModTime(),
|
||||
Mode: f.Mode(),
|
||||
IsDir: f.IsDir(),
|
||||
IsSymlink: isSymlink,
|
||||
Extension: filepath.Ext(name),
|
||||
Path: fPath,
|
||||
}
|
||||
|
||||
@@ -40,13 +40,13 @@ func CopyFile(fs afero.Fs, source, dest string) error {
|
||||
|
||||
// Makes the directory needed to create the dst
|
||||
// file.
|
||||
err = fs.MkdirAll(filepath.Dir(dest), 0666)
|
||||
err = fs.MkdirAll(filepath.Dir(dest), 0666) //nolint:gomnd
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the destination file.
|
||||
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
||||
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) //nolint:gomnd
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
480
frontend/package-lock.json
generated
480
frontend/package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"ace-builds": "^1.4.7",
|
||||
"clipboard": "^2.0.4",
|
||||
"core-js": "^3.9.1",
|
||||
"css-vars-ponyfill": "^2.4.3",
|
||||
"js-base64": "^2.5.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
@@ -25,7 +26,8 @@
|
||||
"vue-lazyload": "^1.3.3",
|
||||
"vue-router": "^3.1.3",
|
||||
"vuex": "^3.1.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
"vuex-router-sync": "^5.0.0",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.1.2",
|
||||
@@ -33,6 +35,7 @@
|
||||
"@vue/cli-service": "^4.1.2",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"compression-webpack-plugin": "^6.0.3",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
@@ -1383,6 +1386,46 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/move-file": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
|
||||
"integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"rimraf": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/move-file/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/move-file/node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@soda/friendly-errors-webpack-plugin": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz",
|
||||
@@ -4092,6 +4135,165 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-6.0.3.tgz",
|
||||
"integrity": "sha512-xzSWiZWwBs+HHGhlYxw0oFaYL/0VYErEqDHCAJhJ3Mza5fmF5JJ4iaB6Ap2JT68C0UhhmoI4Mh37LVz/THv2Fw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cacache": "^15.0.5",
|
||||
"find-cache-dir": "^3.3.1",
|
||||
"schema-utils": "^3.0.0",
|
||||
"serialize-javascript": "^5.0.1",
|
||||
"webpack-sources": "^1.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin/node_modules/cacache": {
|
||||
"version": "15.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.6.tgz",
|
||||
"integrity": "sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@npmcli/move-file": "^1.0.1",
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"glob": "^7.1.4",
|
||||
"infer-owner": "^1.0.4",
|
||||
"lru-cache": "^6.0.0",
|
||||
"minipass": "^3.1.1",
|
||||
"minipass-collect": "^1.0.2",
|
||||
"minipass-flush": "^1.0.5",
|
||||
"minipass-pipeline": "^1.2.2",
|
||||
"mkdirp": "^1.0.3",
|
||||
"p-map": "^4.0.0",
|
||||
"promise-inflight": "^1.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"ssri": "^8.0.1",
|
||||
"tar": "^6.0.2",
|
||||
"unique-filename": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin/node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin/node_modules/p-map": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
|
||||
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"aggregate-error": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin/node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin/node_modules/schema-utils": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin/node_modules/serialize-javascript": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
|
||||
"integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin/node_modules/ssri": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
|
||||
"integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/compression/node_modules/bytes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
@@ -4663,6 +4865,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-vars-ponyfill": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/css-vars-ponyfill/-/css-vars-ponyfill-2.4.3.tgz",
|
||||
"integrity": "sha512-PBfIwjSu27s8kebu8taEYFM8ehVr8o2Qw4H4nSlJzHAJgcduAqxz4oPmYTJuzgauOKaWII9SHWStQ965fxsdZA=="
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
|
||||
@@ -9045,6 +9252,25 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mississippi": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||
@@ -12746,6 +12972,50 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
|
||||
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^3.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
|
||||
@@ -14709,6 +14979,11 @@
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-fetch": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
|
||||
"integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
@@ -16234,6 +16509,33 @@
|
||||
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
|
||||
"dev": true
|
||||
},
|
||||
"@npmcli/move-file": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
|
||||
"integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"rimraf": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@soda/friendly-errors-webpack-plugin": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz",
|
||||
@@ -18503,6 +18805,120 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"compression-webpack-plugin": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-6.0.3.tgz",
|
||||
"integrity": "sha512-xzSWiZWwBs+HHGhlYxw0oFaYL/0VYErEqDHCAJhJ3Mza5fmF5JJ4iaB6Ap2JT68C0UhhmoI4Mh37LVz/THv2Fw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cacache": "^15.0.5",
|
||||
"find-cache-dir": "^3.3.1",
|
||||
"schema-utils": "^3.0.0",
|
||||
"serialize-javascript": "^5.0.1",
|
||||
"webpack-sources": "^1.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"cacache": {
|
||||
"version": "15.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.6.tgz",
|
||||
"integrity": "sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@npmcli/move-file": "^1.0.1",
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"glob": "^7.1.4",
|
||||
"infer-owner": "^1.0.4",
|
||||
"lru-cache": "^6.0.0",
|
||||
"minipass": "^3.1.1",
|
||||
"minipass-collect": "^1.0.2",
|
||||
"minipass-flush": "^1.0.5",
|
||||
"minipass-pipeline": "^1.2.2",
|
||||
"mkdirp": "^1.0.3",
|
||||
"p-map": "^4.0.0",
|
||||
"promise-inflight": "^1.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"ssri": "^8.0.1",
|
||||
"tar": "^6.0.2",
|
||||
"unique-filename": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
},
|
||||
"p-map": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
|
||||
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"aggregate-error": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
|
||||
"integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"ssri": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
|
||||
"integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minipass": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -18948,6 +19364,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"css-vars-ponyfill": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/css-vars-ponyfill/-/css-vars-ponyfill-2.4.3.tgz",
|
||||
"integrity": "sha512-PBfIwjSu27s8kebu8taEYFM8ehVr8o2Qw4H4nSlJzHAJgcduAqxz4oPmYTJuzgauOKaWII9SHWStQ965fxsdZA=="
|
||||
},
|
||||
"css-what": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
|
||||
@@ -22407,6 +22828,24 @@
|
||||
"minipass": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"mississippi": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||
@@ -25512,6 +25951,40 @@
|
||||
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
|
||||
"dev": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
|
||||
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^3.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
|
||||
@@ -27088,6 +27561,11 @@
|
||||
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
|
||||
"integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"ace-builds": "^1.4.7",
|
||||
"clipboard": "^2.0.4",
|
||||
"core-js": "^3.9.1",
|
||||
"css-vars-ponyfill": "^2.4.3",
|
||||
"js-base64": "^2.5.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
@@ -27,7 +28,8 @@
|
||||
"vue-lazyload": "^1.3.3",
|
||||
"vue-router": "^3.1.3",
|
||||
"vuex": "^3.1.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
"vuex-router-sync": "^5.0.0",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.1.2",
|
||||
@@ -35,6 +37,7 @@
|
||||
"@vue/cli-service": "^4.1.2",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"compression-webpack-plugin": "^6.0.3",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
@@ -64,6 +67,6 @@
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
"not ie < 11"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
|
||||
<meta name="theme-color" content="#2979ff">
|
||||
<meta name="theme-color" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
|
||||
|
||||
<!-- Add to home screen for Safari on iOS/iPadOS -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
@@ -26,11 +26,11 @@
|
||||
|
||||
<!-- Add to home screen for Windows -->
|
||||
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png">
|
||||
<meta name="msapplication-TileColor" content="#2979ff">
|
||||
<meta name="msapplication-TileColor" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
|
||||
|
||||
<!-- Inject Some Variables and generate the manifest json -->
|
||||
<script>
|
||||
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
|
||||
window.FileBrowser = JSON.parse('[{[ .Json ]}]');
|
||||
|
||||
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
|
||||
var dynamicManifest = {
|
||||
@@ -51,7 +51,7 @@
|
||||
"start_url": window.location.origin + window.FileBrowser.BaseURL,
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#455a64"
|
||||
"theme_color": window.FileBrowser.Color || "#455a64"
|
||||
}
|
||||
|
||||
const stringManifest = JSON.stringify(dynamicManifest);
|
||||
@@ -77,7 +77,7 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
#loading .spinner {
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
@@ -87,7 +87,7 @@
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.spinner > div {
|
||||
#loading .spinner > div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #333;
|
||||
@@ -97,12 +97,12 @@
|
||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
.spinner .bounce1 {
|
||||
#loading .spinner .bounce1 {
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.spinner .bounce2 {
|
||||
#loading .spinner .bounce2 {
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ body {
|
||||
#loading {
|
||||
background: var(--background);
|
||||
}
|
||||
#loading .spinner div, #previewer .loading .spinner div {
|
||||
#loading .spinner div, main .spinner div {
|
||||
background: var(--icon);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// eslint-disable-next-line no-undef
|
||||
__webpack_public_path__ = window.FileBrowser.StaticURL + "/";
|
||||
|
||||
export default {
|
||||
name: "app",
|
||||
mounted() {
|
||||
|
||||
@@ -8,13 +8,18 @@ export async function fetchURL(url, opts) {
|
||||
|
||||
let { headers, ...rest } = opts;
|
||||
|
||||
const res = await fetch(`${baseURL}${url}`, {
|
||||
headers: {
|
||||
"X-Auth": store.state.jwt,
|
||||
...headers,
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(`${baseURL}${url}`, {
|
||||
headers: {
|
||||
"X-Auth": store.state.jwt,
|
||||
...headers,
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
} catch (error) {
|
||||
return { status: 0 };
|
||||
}
|
||||
|
||||
if (res.headers.get("X-Renew-Token") === "true") {
|
||||
await renew(store.state.jwt);
|
||||
|
||||
BIN
frontend/src/assets/fonts/roboto/bold-cyrillic-ext.woff2
Executable file
BIN
frontend/src/assets/fonts/roboto/bold-cyrillic-ext.woff2
Executable file
Binary file not shown.
BIN
frontend/src/assets/fonts/roboto/bold-cyrillic.woff2
Executable file
BIN
frontend/src/assets/fonts/roboto/bold-cyrillic.woff2
Executable file
Binary file not shown.
BIN
frontend/src/assets/fonts/roboto/bold-greek-ext.woff2
Executable file
BIN
frontend/src/assets/fonts/roboto/bold-greek-ext.woff2
Executable file
Binary file not shown.
BIN
frontend/src/assets/fonts/roboto/bold-greek.woff2
Executable file
BIN
frontend/src/assets/fonts/roboto/bold-greek.woff2
Executable file
Binary file not shown.
BIN
frontend/src/assets/fonts/roboto/bold-latin-ext.woff2
Executable file
BIN
frontend/src/assets/fonts/roboto/bold-latin-ext.woff2
Executable file
Binary file not shown.
BIN
frontend/src/assets/fonts/roboto/bold-latin.woff2
Executable file
BIN
frontend/src/assets/fonts/roboto/bold-latin.woff2
Executable file
Binary file not shown.
BIN
frontend/src/assets/fonts/roboto/bold-vietnamese.woff2
Executable file
BIN
frontend/src/assets/fonts/roboto/bold-vietnamese.woff2
Executable file
Binary file not shown.
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<nav :class="{ active }">
|
||||
<template v-if="isLogged">
|
||||
<router-link
|
||||
<button
|
||||
class="action"
|
||||
to="/files/"
|
||||
@click="toRoot"
|
||||
:aria-label="$t('sidebar.myFiles')"
|
||||
:title="$t('sidebar.myFiles')"
|
||||
>
|
||||
<i class="material-icons">folder</i>
|
||||
<span>{{ $t("sidebar.myFiles") }}</span>
|
||||
</router-link>
|
||||
</button>
|
||||
|
||||
<div v-if="user.perm.create">
|
||||
<button
|
||||
@@ -34,15 +34,15 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<router-link
|
||||
<button
|
||||
class="action"
|
||||
to="/settings"
|
||||
@click="toSettings"
|
||||
:aria-label="$t('sidebar.settings')"
|
||||
:title="$t('sidebar.settings')"
|
||||
>
|
||||
<i class="material-icons">settings_applications</i>
|
||||
<span>{{ $t("sidebar.settings") }}</span>
|
||||
</router-link>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="authMethod == 'json'"
|
||||
@@ -125,6 +125,14 @@ export default {
|
||||
authMethod: () => authMethod,
|
||||
},
|
||||
methods: {
|
||||
toRoot() {
|
||||
this.$router.push({ path: "/files/" }, () => {});
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
toSettings() {
|
||||
this.$router.push({ path: "/settings" }, () => {});
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
help() {
|
||||
this.$store.commit("showHover", "help");
|
||||
},
|
||||
|
||||
@@ -29,14 +29,6 @@ export default {
|
||||
type: Number,
|
||||
default: () => 200,
|
||||
},
|
||||
maxScale: {
|
||||
type: Number,
|
||||
default: () => 4,
|
||||
},
|
||||
minScale: {
|
||||
type: Number,
|
||||
default: () => 0.25,
|
||||
},
|
||||
classList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
@@ -45,10 +37,6 @@ export default {
|
||||
type: Number,
|
||||
default: () => 0.25,
|
||||
},
|
||||
autofill: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -56,6 +44,7 @@ export default {
|
||||
lastX: null,
|
||||
lastY: null,
|
||||
inDrag: false,
|
||||
touches: 0,
|
||||
lastTouchDistance: 0,
|
||||
moveDisabled: false,
|
||||
disabledTimer: null,
|
||||
@@ -64,6 +53,8 @@ export default {
|
||||
center: { x: 0, y: 0 },
|
||||
relative: { x: 0, y: 0 },
|
||||
},
|
||||
maxScale: 4,
|
||||
minScale: 0.25,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@@ -88,6 +79,10 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
src: function () {
|
||||
if (!this.decodeUTIF()) {
|
||||
this.$refs.imgex.src = this.src;
|
||||
}
|
||||
|
||||
this.scale = 1;
|
||||
this.setZoom();
|
||||
this.setCenter();
|
||||
@@ -122,6 +117,21 @@ export default {
|
||||
img.classList.add("image-ex-img-ready");
|
||||
|
||||
document.addEventListener("mouseup", this.onMouseUp);
|
||||
|
||||
let realSize = img.naturalWidth;
|
||||
let displaySize = img.offsetWidth;
|
||||
|
||||
// Image is in portrait orientation
|
||||
if (img.naturalHeight > img.naturalWidth) {
|
||||
realSize = img.naturalHeight;
|
||||
displaySize = img.offsetHeight;
|
||||
}
|
||||
|
||||
// Scale needed to display the image on full size
|
||||
const fullScale = realSize / displaySize;
|
||||
|
||||
// Full size plus additional zoom
|
||||
this.maxScale = fullScale + 4;
|
||||
},
|
||||
onMouseUp() {
|
||||
this.inDrag = false;
|
||||
@@ -251,7 +261,7 @@ export default {
|
||||
}
|
||||
},
|
||||
wheelMove(event) {
|
||||
this.scale += (event.wheelDeltaY / 100) * this.zoomStep;
|
||||
this.scale += -Math.sign(event.deltaY) * this.zoomStep;
|
||||
this.setZoom();
|
||||
},
|
||||
setZoom() {
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
@dragover="dragOver"
|
||||
@drop="drop"
|
||||
@click="itemClick"
|
||||
@dblclick="dblclick"
|
||||
@touchstart="touchstart"
|
||||
:data-dir="isDir"
|
||||
:aria-label="name"
|
||||
:aria-selected="isSelected"
|
||||
@@ -96,7 +94,7 @@ export default {
|
||||
// reload the image when the file is replaced
|
||||
const key = Date.parse(this.modified);
|
||||
|
||||
return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true&k=${key}`;
|
||||
return `${baseURL}/api/preview/thumb/${path}?k=${key}&inline=true`;
|
||||
},
|
||||
isThumbsEnabled() {
|
||||
return enableThumbs;
|
||||
@@ -108,6 +106,9 @@ export default {
|
||||
return filesize(this.size);
|
||||
},
|
||||
humanTime: function () {
|
||||
if (this.user.dateFormat) {
|
||||
return moment(this.modified).format("L LT");
|
||||
}
|
||||
return moment(this.modified).fromNow();
|
||||
},
|
||||
dragStart: function () {
|
||||
@@ -153,13 +154,13 @@ export default {
|
||||
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 + encodeURIComponent(this.req.items[i].name),
|
||||
name: this.req.items[i].name,
|
||||
});
|
||||
}
|
||||
|
||||
let base = el.querySelector(".name").innerHTML + "/";
|
||||
let path = this.$route.path + base;
|
||||
// Get url from ListingItem instance
|
||||
let path = el.__vue__.url;
|
||||
let baseItems = (await api.fetch(path)).items;
|
||||
|
||||
let action = (overwrite, rename) => {
|
||||
@@ -200,6 +201,16 @@ export default {
|
||||
},
|
||||
click: function (event) {
|
||||
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault();
|
||||
|
||||
setTimeout(() => {
|
||||
this.touches = 0;
|
||||
}, 300);
|
||||
|
||||
this.touches++;
|
||||
if (this.touches > 1) {
|
||||
this.open();
|
||||
}
|
||||
|
||||
if (this.$store.state.selected.indexOf(this.index) !== -1) {
|
||||
this.removeSelected(this.index);
|
||||
return;
|
||||
@@ -235,19 +246,6 @@ export default {
|
||||
this.resetSelected();
|
||||
this.addSelected(this.index);
|
||||
},
|
||||
dblclick: function () {
|
||||
if (!this.singleClick) this.open();
|
||||
},
|
||||
touchstart() {
|
||||
setTimeout(() => {
|
||||
this.touches = 0;
|
||||
}, 300);
|
||||
|
||||
this.touches++;
|
||||
if (this.touches > 1) {
|
||||
this.open();
|
||||
}
|
||||
},
|
||||
open: function () {
|
||||
this.$router.push({ path: this.url });
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<strong>{{ $t("prompts.size") }}:</strong>
|
||||
<span id="content_length"></span> {{ humanSize }}
|
||||
</p>
|
||||
<p v-if="selected.length < 2">
|
||||
<p v-if="selected.length < 2" :title="modTime">
|
||||
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
|
||||
</p>
|
||||
|
||||
@@ -110,6 +110,9 @@ export default {
|
||||
|
||||
return moment(this.req.items[this.selected[0]].modified).fromNow();
|
||||
},
|
||||
modTime: function () {
|
||||
return new Date(Date.parse(this.req.modified)).toLocaleString();
|
||||
},
|
||||
name: function () {
|
||||
return this.selectedCount === 0
|
||||
? this.req.name
|
||||
|
||||
@@ -56,6 +56,12 @@ export default {
|
||||
|
||||
let prompt = this.$refs.currentComponent;
|
||||
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
event.stopImmediatePropagation();
|
||||
this.$store.commit("closeHovers");
|
||||
}
|
||||
|
||||
// Enter
|
||||
if (event.keyCode == 13) {
|
||||
switch (this.show) {
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
<div class="card-action full">
|
||||
<div @click="uploadFile" class="action">
|
||||
<i class="material-icons">insert_drive_file</i>
|
||||
<div class="title">File</div>
|
||||
<div class="title">{{ $t("buttons.file") }}</div>
|
||||
</div>
|
||||
<div @click="uploadFolder" class="action">
|
||||
<i class="material-icons">folder</i>
|
||||
<div class="title">Folder</div>
|
||||
<div class="title">{{ $t("buttons.folder") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,9 @@ export default {
|
||||
pt: "pt",
|
||||
ro: "ro",
|
||||
ru: "ru",
|
||||
sk: "sk",
|
||||
"sv-se": "svSE",
|
||||
tr: "tr",
|
||||
"zh-cn": "zhCN",
|
||||
"zh-tw": "zhTW",
|
||||
},
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
}
|
||||
|
||||
.share__box__info {
|
||||
flex: 1 1 auto;
|
||||
flex: 1 1 18em;
|
||||
}
|
||||
|
||||
.share__box__element {
|
||||
@@ -43,6 +43,15 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.share__box__element .button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.share__box__element .button i {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.share__box__items {
|
||||
text-align: left;
|
||||
flex: 10 0 25em;
|
||||
|
||||
@@ -110,4 +110,60 @@
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-cyrillic-ext.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-cyrillic.woff2) format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-greek-ext.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-greek.woff2) format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-vietnamese.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
||||
@import "~material-design-icons/iconfont/material-icons.css";
|
||||
|
||||
@@ -84,7 +84,8 @@ header .menu-button {
|
||||
#search #input {
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
padding: 0.75em;
|
||||
height: 100%;
|
||||
padding: 0em 0.75em;
|
||||
border-radius: 0.3em;
|
||||
transition: .1s ease all;
|
||||
align-items: center;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
transition: .1s ease background, .1s ease opacity;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#listing .item div:last-of-type {
|
||||
@@ -55,6 +56,7 @@
|
||||
#listing .item img {
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
object-fit: cover;
|
||||
margin-right: 0.1em;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,48 @@
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
main .spinner {
|
||||
display: block;
|
||||
text-align: center;
|
||||
line-height: 0;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
main .spinner > div {
|
||||
width: .8em;
|
||||
height: .8em;
|
||||
margin: 0 .1em;
|
||||
font-size: 1em;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
main .spinner .bounce1 {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
main .spinner .bounce2 {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
.delayed {
|
||||
animation: delayed linear 100ms;
|
||||
}
|
||||
|
||||
@keyframes delayed {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
99% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* ACTION *
|
||||
* * * * * * * * * * * * * * * */
|
||||
@@ -109,6 +151,7 @@
|
||||
|
||||
#previewer {
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
padding-top: 4em;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -142,7 +185,6 @@
|
||||
}
|
||||
|
||||
#previewer .preview {
|
||||
margin-top: 4em;
|
||||
text-align: center;
|
||||
height: calc(100vh - 4em);
|
||||
}
|
||||
@@ -163,6 +205,34 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#previewer .preview .info {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 1.5em;
|
||||
color: #fff;
|
||||
}
|
||||
#previewer .preview .info .title {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
#previewer .preview .info .title i {
|
||||
display: block;
|
||||
margin-bottom: .1em;
|
||||
font-size: 4em;
|
||||
}
|
||||
#previewer .preview .info .button {
|
||||
display: inline-block;
|
||||
}
|
||||
#previewer .preview .info .button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2)
|
||||
}
|
||||
#previewer .preview .info .button i {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
#previewer .pdf {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -204,26 +274,38 @@
|
||||
right: 0.5em;
|
||||
}
|
||||
|
||||
#previewer .spinner {
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
top: calc(50% + 1.85em);
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#previewer .spinner > div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* EDITOR */
|
||||
|
||||
#editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fafafa;
|
||||
position: fixed;
|
||||
margin-top: 4em;
|
||||
padding-top: 4em;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#previewer .loading {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#editor-container #editor {
|
||||
height: calc(100vh - 8.4em);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#editor-container .breadcrumbs {
|
||||
@@ -283,7 +365,6 @@
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
-webkit-transform: rotate(-360deg);
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"create": "Create",
|
||||
"delete": "Delete",
|
||||
"download": "Download",
|
||||
"file": "File",
|
||||
"folder": "Folder",
|
||||
"hideDotfiles": "Hide dotfiles",
|
||||
"info": "Info",
|
||||
"more": "More",
|
||||
@@ -33,7 +35,8 @@
|
||||
"switchView": "Switch view",
|
||||
"toggleSidebar": "Toggle sidebar",
|
||||
"update": "Update",
|
||||
"upload": "Upload"
|
||||
"upload": "Upload",
|
||||
"openFile": "Open file"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download File",
|
||||
@@ -43,7 +46,8 @@
|
||||
"errors": {
|
||||
"forbidden": "You don't have permissions to access this.",
|
||||
"internal": "Something really went wrong.",
|
||||
"notFound": "This location can't be reached."
|
||||
"notFound": "This location can't be reached.",
|
||||
"connection": "The server can't be reached."
|
||||
},
|
||||
"files": {
|
||||
"body": "Body",
|
||||
@@ -61,7 +65,8 @@
|
||||
"size": "Size",
|
||||
"sortByLastModified": "Sort by last modified",
|
||||
"sortByName": "Sort by name",
|
||||
"sortBySize": "Sort by size"
|
||||
"sortBySize": "Sort by size",
|
||||
"noPreview": "Preview is not available for this file."
|
||||
},
|
||||
"help": {
|
||||
"click": "select file or directory",
|
||||
@@ -93,7 +98,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "Romanian",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "Swedish (Sweden)",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
@@ -211,9 +218,11 @@
|
||||
"rules": "Rules",
|
||||
"rulesHelp": "Here you can define a set of allow and disallow rules for this specific user. The blocked files won't show up in the listings and they wont be accessible to the user. We support regex and paths relative to the users scope.\n",
|
||||
"scope": "Scope",
|
||||
"setDateFormat": "Set exact date format",
|
||||
"settingsUpdated": "Settings updated!",
|
||||
"shareDuration": "Share Duration",
|
||||
"shareManagement": "Share Management",
|
||||
"shareDeleted": "Share deleted!",
|
||||
"singleClick": "Use single clicks to open files and directories",
|
||||
"themes": {
|
||||
"dark": "Dark",
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ import pt from "./pt.json";
|
||||
import ptBR from "./pt-br.json";
|
||||
import ro from "./ro.json";
|
||||
import ru from "./ru.json";
|
||||
import sk from "./sk.json";
|
||||
import svSE from "./sv-se.json";
|
||||
import zhCN from "./zh-cn.json";
|
||||
import zhTW from "./zh-tw.json";
|
||||
@@ -70,6 +71,9 @@ export function detectLocale() {
|
||||
case /^ko.*/i.test(locale):
|
||||
locale = "ko";
|
||||
break;
|
||||
case /^sk.*/i.test(locale):
|
||||
locale = "sk";
|
||||
break;
|
||||
default:
|
||||
locale = "en";
|
||||
}
|
||||
@@ -107,6 +111,7 @@ const i18n = new VueI18n({
|
||||
pt: removeEmpty(pt),
|
||||
ru: removeEmpty(ru),
|
||||
ro: removeEmpty(ro),
|
||||
sk: removeEmpty(sk),
|
||||
"sv-se": removeEmpty(svSE),
|
||||
"zh-cn": removeEmpty(zhCN),
|
||||
"zh-tw": removeEmpty(zhTW),
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Portugees (Brazilië)",
|
||||
"ro": "",
|
||||
"ru": "Russisch",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "Chinees (vereenvoudigd)",
|
||||
"zhTW": "Chinees (traditioneel)"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Russo",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "Chinês simplificado",
|
||||
"zhTW": "Chinês tradicional"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
265
frontend/src/i18n/sk.json
Normal file
265
frontend/src/i18n/sk.json
Normal file
@@ -0,0 +1,265 @@
|
||||
{
|
||||
"buttons": {
|
||||
"cancel": "Zrušiť",
|
||||
"close": "Zavrieť",
|
||||
"copy": "Kopírovať",
|
||||
"copyFile": "Kopírovať súbor",
|
||||
"copyToClipboard": "Kopírovať do schránky",
|
||||
"create": "Vytvoriť",
|
||||
"delete": "Odstrániť",
|
||||
"download": "Stiahnuť",
|
||||
"file": "Súbor",
|
||||
"folder": "Priečinok",
|
||||
"hideDotfiles": "Skryť súbory začínajúce bodkou",
|
||||
"info": "Info",
|
||||
"more": "Viac",
|
||||
"move": "Presunúť",
|
||||
"moveFile": "Presunúť súbory",
|
||||
"new": "Nový",
|
||||
"next": "Ďalšie",
|
||||
"ok": "OK",
|
||||
"permalink": "Získať trvalý odkaz",
|
||||
"previous": "Predošlé",
|
||||
"publish": "Zverejniť",
|
||||
"rename": "Premenovať",
|
||||
"replace": "Nahradiť",
|
||||
"reportIssue": "Nahlásiť problém",
|
||||
"save": "Uložiť",
|
||||
"schedule": "Naplánovať",
|
||||
"search": "Hľadať",
|
||||
"select": "Vybrať",
|
||||
"selectMultiple": "Vybrať viaceré",
|
||||
"share": "Zdieľať",
|
||||
"shell": "Prepnúť shell",
|
||||
"submit": "Poslať",
|
||||
"switchView": "Prepnúť pohľad",
|
||||
"toggleSidebar": "Prepnúť sidebar",
|
||||
"update": "Aktualizovať",
|
||||
"upload": "Nahrať",
|
||||
"openFile": "Otvoriť súbor"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Stiahnuť súbor",
|
||||
"downloadFolder": "Stiahnuť priečinok",
|
||||
"downloadSelected": "Stiahnuť vybraté"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "You don't have permissions to access this.",
|
||||
"internal": "Something really went wrong.",
|
||||
"notFound": "This location can't be reached.",
|
||||
"connection": "The server can't be reached."
|
||||
},
|
||||
"files": {
|
||||
"body": "Telo",
|
||||
"clear": "Zrušiť výber",
|
||||
"closePreview": "Zavrieť náhľad",
|
||||
"files": "Súbory",
|
||||
"folders": "Priečinky",
|
||||
"home": "Domov",
|
||||
"lastModified": "Posledná zmena",
|
||||
"loading": "Načítanie...",
|
||||
"lonely": "Je tu tak pusto...",
|
||||
"metadata": "Metadata",
|
||||
"multipleSelectionEnabled": "Zapnutý viacnásobný výber",
|
||||
"name": "Názov",
|
||||
"size": "Veľkosť",
|
||||
"sortByLastModified": "Zoradiť podľa dátumu",
|
||||
"sortByName": "Zoradiť podľa názvu",
|
||||
"sortBySize": "Zoradiť podľa veľkosti",
|
||||
"noPreview": "Pre tento súbor nie je dostupný náhľad."
|
||||
},
|
||||
"help": {
|
||||
"click": "vyberie súbor alebo priečinok",
|
||||
"ctrl": {
|
||||
"click": "vyberie viac súborov alebo priečinkov",
|
||||
"f": "otvorí vyhľadávanie",
|
||||
"s": "uloží súbor alebo stiahne priečinok tam kde ste"
|
||||
},
|
||||
"del": "odstráni vybraté položky",
|
||||
"doubleClick": "otvorí súbor alebo priečinok",
|
||||
"esc": "zruší výber a/alebo zavrie okno",
|
||||
"f1": "tieto informácie",
|
||||
"f2": "premenuje súbor",
|
||||
"help": "Pomoc"
|
||||
},
|
||||
"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": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "Swedish (Sweden)",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
"login": {
|
||||
"createAnAccount": "Vytvoriť účet",
|
||||
"loginInstead": "Už mám účet",
|
||||
"password": "Heslo",
|
||||
"passwordConfirm": "Potvrdenie hesla",
|
||||
"passwordsDontMatch": "Heslá nesúhlasia",
|
||||
"signup": "Registrovať",
|
||||
"submit": "Prihlásiť",
|
||||
"username": "Používateľské meno",
|
||||
"usernameTaken": "Meno je už obsadené",
|
||||
"wrongCredentials": "Nesprávne prihlasovacie údaje"
|
||||
},
|
||||
"permanent": "Trvalé",
|
||||
"prompts": {
|
||||
"copy": "Kopírovať",
|
||||
"copyMessage": "Zvoľte miesto, kde chcete kopírovať súbory:",
|
||||
"currentlyNavigating": "Aktuálna cesta:",
|
||||
"deleteMessageMultiple": "Naozaj chcete odstrániť {count} súbor(ov)?",
|
||||
"deleteMessageSingle": "Naozaj chcete odstrániť tento súbor/priečinok?",
|
||||
"deleteMessageShare": "Naozaj chcete odstrániť toto zdieľanie({path})?",
|
||||
"deleteTitle": "Odstránenie súborov",
|
||||
"displayName": "Zobrazený názov:",
|
||||
"download": "Stiahnuť súbory",
|
||||
"downloadMessage": "Vyberte formát, ktorý chcete stiahnuť.",
|
||||
"error": "Niečo sa pokazilo",
|
||||
"fileInfo": "Informácie o súbore",
|
||||
"filesSelected": "{count} súborov vybratých.",
|
||||
"lastModified": "Dátum zmeny",
|
||||
"move": "Presunúť",
|
||||
"moveMessage": "Zvoľte nový domov pre vaše súbory/priečinky:",
|
||||
"newArchetype": "Vytvorí nový príspevok z archetypu. Nový súbor sa vytvorí v priečinku s obsahom.",
|
||||
"newDir": "Nový priečinok",
|
||||
"newDirMessage": "Napíšte názov nového priečinka.",
|
||||
"newFile": "Nový súbor",
|
||||
"newFileMessage": "Napíšte názov nového súboru.",
|
||||
"numberDirs": "Počet priečinkov",
|
||||
"numberFiles": "Počet súborov",
|
||||
"rename": "Premenovať",
|
||||
"renameMessage": "Zadajte nový názov pre",
|
||||
"replace": "Nahradiť",
|
||||
"replaceMessage": "Niektorý nahrávaný súbor je v konflikte názvov. Chcete nahradiť existujúci súbor?\n",
|
||||
"schedule": "Naplánovať",
|
||||
"scheduleMessage": "Pick a date and time to schedule the publication of this post.",
|
||||
"show": "Zobraziť",
|
||||
"size": "Veľkosť",
|
||||
"upload": "Nahrať",
|
||||
"uploadMessage": "Zvoľte možnosť nahrávania.",
|
||||
"optionalPassword": "Voliteľné heslo"
|
||||
},
|
||||
"search": {
|
||||
"images": "Obrázky",
|
||||
"music": "Hudba",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Vyhľadáte stlačením Enter...",
|
||||
"search": "Hľadať...",
|
||||
"typeToSearch": "Vyhľadáte písaním...",
|
||||
"types": "Typy",
|
||||
"video": "Video"
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
"administrator": "Administrátor",
|
||||
"allowCommands": "Vykonávať príkazy",
|
||||
"allowEdit": "Upravovať, premenovať a odstraňovať súbory a priečinky",
|
||||
"allowNew": "Vytvárať nové súbory a priečinky",
|
||||
"allowPublish": "Zverejňovať nové príspevky a stránky",
|
||||
"allowSignup": "Povoliť registráciu používateľov",
|
||||
"avoidChanges": "(nechajte prázdne, aby sa nezmenilo)",
|
||||
"branding": "Vlastný vzhľad",
|
||||
"brandingDirectoryPath": "Cesta k priečinku s vlastným vzhľadom",
|
||||
"brandingHelp": "Môžete si prispôsobiť ako bude vyzerá váš File Browser instance zmenou jeho názvu, výmenou loga a pridaním vlastný štýlov alebo vypnutím externých odkazov na GitHub.\nViac informácií o vlastnom vzhľade nájdete na {0}.",
|
||||
"changePassword": "Zmeniť heslo",
|
||||
"commandRunner": "Spúšťač príkazov",
|
||||
"commandRunnerHelp": "Sem môžete nastaviť príkazy, ktoré sa vykonajú pri určitých udalostiach. Musíte písať jeden na riadok. Premenné prostredia {0} a {1} sú k dispozícii, s tým že {0} relatívne k {1}. Viac informácií o tejto funkcionalite a dostupných premenných prostredia nájdete na {2}.",
|
||||
"commandsUpdated": "Príkazy upravené!",
|
||||
"createUserDir": "Automaticky vytvoriť domovský priečinok pri pridaní používateľa",
|
||||
"customStylesheet": "Vlastný Stylesheet",
|
||||
"defaultUserDescription": "Toto sú predvolané nastavenia nového používateľa.",
|
||||
"disableExternalLinks": "Vypnúť externé odkazy (okrem dokumentácie)",
|
||||
"documentation": "dokumentácia",
|
||||
"examples": "Príklady",
|
||||
"executeOnShell": "Vykonať cez shell",
|
||||
"executeOnShellDescription": "Predvolene File Browser vykonáva príkazy volaním priamo ich binárok. Ak ich chcete spúšťať cez shell (napr. Bash alebo PowerShell), môžete ho napísať sem a pridať potrebné argumenty a flagy. Ak je nastavený, tak sa príkazy budú spúšťať pridaním na koniec ako argument. Toto sa týka používateľských príkazov aj udalostí.",
|
||||
"globalRules": "Toto je globálne nastavenie pravidiel. Aplikujú sa na všetkých používateľov. Môžete definovať špecifické pravidlá pre každého používateľa a prekryť tak pravidlá nastavené tu.",
|
||||
"globalSettings": "Globálne nastavenia",
|
||||
"hideDotfiles": "Skryť súroby začínajúce bodkou",
|
||||
"insertPath": "Vložte cestu",
|
||||
"insertRegex": "Vložte regex výraz",
|
||||
"instanceName": "Názov inštalácie",
|
||||
"language": "Jazyk",
|
||||
"lockPassword": "Zabrániť používateľovi meniť heslo",
|
||||
"newPassword": "Nové heslo",
|
||||
"newPasswordConfirm": "Potvrenie nového hesla",
|
||||
"newUser": "Nový používateľ",
|
||||
"password": "Heslo",
|
||||
"passwordUpdated": "Heslo zmenené!",
|
||||
"path": "Cesta",
|
||||
"perm": {
|
||||
"create": "Vytvárať súbory a priečinky",
|
||||
"delete": "Odstraňovať súbory a priečinky",
|
||||
"download": "Stiahnuť",
|
||||
"execute": "Vykonávať príkazy",
|
||||
"modify": "Upravovať súbory",
|
||||
"rename": "Premenovať a presúvať súbory a priečinky",
|
||||
"share": "Zdieľať súbory"
|
||||
},
|
||||
"permissions": "Práva",
|
||||
"permissionsHelp": "Môžete nastaviť používateľa, aby bol administrátorom alebo vybrať práva jednotlivo. Ak zvolíte \"Administrator\", všetky ďalši budú automaticky zaškrtnuté. Manažment používateľov ostáva v správe administrátora.\n",
|
||||
"profileSettings": "Nastavenia profilu",
|
||||
"ruleExample1": "blokuje prístup ku všetkým súborom začínajúcim bodkou (napríklad .git, .gitignore) v každom priečinku.\n",
|
||||
"ruleExample2": "blokuje prístup k súborom s názvom Caddyfile v koreňovom priečinku.",
|
||||
"rules": "Pravidlá",
|
||||
"rulesHelp": "Tu môžete definovať pravidilá pre konkrétneho používateľa. Blokované súbory používateľ nebude vidieť a ani nebude k nim mať prístup. Podporujeme regex a cesty relatívne k používateľovi.\n",
|
||||
"scope": "Scope",
|
||||
"settingsUpdated": "Nastavenia upravené!",
|
||||
"shareDuration": "Trvanie zdieľania",
|
||||
"shareManagement": "Správa zdieľania",
|
||||
"shareDeleted": "Zdieľanie odstránené!",
|
||||
"singleClick": "Používať jeden klik na otváranie súborov a priečinkov",
|
||||
"themes": {
|
||||
"dark": "Tmavá",
|
||||
"light": "Svetlá",
|
||||
"title": "Téma"
|
||||
},
|
||||
"user": "Používateľ",
|
||||
"userCommands": "Príkazy",
|
||||
"userCommandsHelp": "Zoznam povolených príkazov oddelených medzerou pre tohoto používateľa. Napríklad:\n",
|
||||
"userCreated": "Používateľ vytvorený!",
|
||||
"userDefaults": "Predovolené nastavenia používateľa",
|
||||
"userDeleted": "Používateľ odstránený!",
|
||||
"userManagement": "Správa používateľov",
|
||||
"userUpdated": "Používateľ upravený!",
|
||||
"username": "Meno používateľa",
|
||||
"users": "Používatelia"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Pomoc",
|
||||
"hugoNew": "Nový Hugo",
|
||||
"login": "Prihlásiť",
|
||||
"logout": "Odhlásiť",
|
||||
"myFiles": "Moje súbory",
|
||||
"newFile": "Nový súbor",
|
||||
"newFolder": "Nový priečinok",
|
||||
"preview": "Náhľad",
|
||||
"settings": "Nastavenia",
|
||||
"signup": "Registrovať",
|
||||
"siteSettings": "Nastavenia stránky"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Odkaz skopírovaný!"
|
||||
},
|
||||
"time": {
|
||||
"days": "Dni",
|
||||
"hours": "Hodiny",
|
||||
"minutes": "Minúty",
|
||||
"seconds": "Sekundy",
|
||||
"unit": "Jednotka času"
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
263
frontend/src/i18n/tr.json
Normal file
263
frontend/src/i18n/tr.json
Normal file
@@ -0,0 +1,263 @@
|
||||
{
|
||||
"buttons": {
|
||||
"cancel": "Vazgeç",
|
||||
"close": "Kapat",
|
||||
"copy": "Kopyala",
|
||||
"copyFile": "Dosyayı kopyala",
|
||||
"copyToClipboard": "Panoya kopyala",
|
||||
"create": "Oluştur",
|
||||
"delete": "Sil",
|
||||
"download": "İndir",
|
||||
"hideDotfiles": "Nokta dosyalarını gizle",
|
||||
"info": "Bilgi",
|
||||
"more": "Daha fazla",
|
||||
"move": "Taşı",
|
||||
"moveFile": "Dosyayı taşı",
|
||||
"new": "Yeni",
|
||||
"next": "Sonraki",
|
||||
"ok": "Tamam",
|
||||
"permalink": "Kalıcı Bağlantı Alın",
|
||||
"previous": "Önceki",
|
||||
"publish": "Yayınla",
|
||||
"rename": "Yeniden anlandır",
|
||||
"replace": "Değiştir",
|
||||
"reportIssue": "Sorun bildir",
|
||||
"save": "Kaydet",
|
||||
"schedule": "Planla",
|
||||
"search": "Ara",
|
||||
"select": "Seç",
|
||||
"selectMultiple": "Çoklu seçim",
|
||||
"share": "Paylaş",
|
||||
"shell": "Komut satırı aç/kapat",
|
||||
"submit": "Gönder",
|
||||
"switchView": "Görünümü değiştir",
|
||||
"toggleSidebar": "Menüyü aç/kapat",
|
||||
"update": "Güncelle",
|
||||
"upload": "Yükle",
|
||||
"openFile": "Dosyayı aç"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Dosyayı indir",
|
||||
"downloadFolder": "Klasörü indir",
|
||||
"downloadSelected": "Seçilileri indir"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Buna erişim izniniz yok.",
|
||||
"internal": "Bir şeyler ters gitti.",
|
||||
"notFound": "Bu konuma ulaşılamıyor.",
|
||||
"connection": "Sunucuya ulaşılamıyor."
|
||||
},
|
||||
"files": {
|
||||
"body": "Sayfa",
|
||||
"clear": "Temizle",
|
||||
"closePreview": "Önizlemeyi kapat",
|
||||
"files": "Dosyalar",
|
||||
"folders": "Klasörler",
|
||||
"home": "Ana dizin",
|
||||
"lastModified": "Son güncellenme",
|
||||
"loading": "Yükleniyor...",
|
||||
"lonely": "Burada yalnızlık hissediyorum...",
|
||||
"metadata": "meta veri",
|
||||
"multipleSelectionEnabled": "Çoklu seçim etkin",
|
||||
"name": "İsim",
|
||||
"size": "Boyut",
|
||||
"sortByLastModified": "Güncelleme tarihine göre sırala",
|
||||
"sortByName": "İsme göre sırala",
|
||||
"sortBySize": "Boyuta göre sırala",
|
||||
"noPreview": "Bu dosya için önizleme aktif değil"
|
||||
},
|
||||
"help": {
|
||||
"click": "dosya veya klasör seçin",
|
||||
"ctrl": {
|
||||
"click": "çoklu dosya ve klasör seçin",
|
||||
"f": "Aramayı aç",
|
||||
"s": "bir dosyayı kaydedin veya bulunduğunuz dizini indirin"
|
||||
},
|
||||
"del": "seçilileri sil",
|
||||
"doubleClick": "dosya veya dizini açın",
|
||||
"esc": "seçimi temizle veya kapatın",
|
||||
"f1": "bu bilgi",
|
||||
"f2": "dosyayı yeniden adlandır",
|
||||
"help": "Yardım"
|
||||
},
|
||||
"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": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "Swedish (Sweden)",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
"login": {
|
||||
"createAnAccount": "Bir hesap oluşturun",
|
||||
"loginInstead": "Zaten hesabınız var mı",
|
||||
"password": "Şifre",
|
||||
"passwordConfirm": "Şifre tekrarı",
|
||||
"passwordsDontMatch": "Şifreler uyuşmuyor",
|
||||
"signup": "Üye Ol",
|
||||
"submit": "Giriş",
|
||||
"username": "Kullanıcı adı",
|
||||
"usernameTaken": "Kullanıcı adı mevcut",
|
||||
"wrongCredentials": "Yanlış hesap bilgileri"
|
||||
},
|
||||
"permanent": "Kalıcı",
|
||||
"prompts": {
|
||||
"copy": "Kopyala",
|
||||
"copyMessage": "Dosyalarınızı kopyalayacağınız yeri seçin:",
|
||||
"currentlyNavigating": "Şu anki lokasyon:",
|
||||
"deleteMessageMultiple": "{count} dosyayı/dosyaları silmek istediğinizden emin misiniz?",
|
||||
"deleteMessageSingle": "Bu dosyayı/klasörü silmek istediğinizden emin misiniz?",
|
||||
"deleteMessageShare": "Bu paylaşımı({path}) silmek istediğinizden emin misiniz?",
|
||||
"deleteTitle": "Dosyaları sil",
|
||||
"displayName": "Görünen Ad:",
|
||||
"download": "Dosyaları indirŞ",
|
||||
"downloadMessage": "İndirmek istediğiniz formatı seçin.",
|
||||
"error": "Bir şeyler yanlış gitti",
|
||||
"fileInfo": "Dosya bilgisi",
|
||||
"filesSelected": "{count} dosya seçildi.",
|
||||
"lastModified": "Son güncellenme",
|
||||
"move": "Taşı",
|
||||
"moveMessage": "Dosya(lar)ınız/klasör(ler)iniz için yeni ana dizin seçin:",
|
||||
"newArchetype": "Bir prototip temelinde yeni bir gönderi oluşturun. Dosyanız içerik klasöründe oluşturulacaktır.",
|
||||
"newDir": "Yeni dizin",
|
||||
"newDirMessage": "Yeni dizinin adını yazın.",
|
||||
"newFile": "Yeni dosya",
|
||||
"newFileMessage": "Yeni dosyanın adını yazın.",
|
||||
"numberDirs": "Dizin sayısı",
|
||||
"numberFiles": "Dosya sayısı",
|
||||
"rename": "Yeniden adlandır",
|
||||
"renameMessage": "için yeni bir ad girin",
|
||||
"replace": "Değiştir",
|
||||
"replaceMessage": "Yüklemeye çalıştığınız dosyalardan biri, adı nedeniyle çakışıyor. Mevcut olanı değiştirmek istiyor musunuz?\n",
|
||||
"schedule": "Planla",
|
||||
"scheduleMessage": "Bu paylaşımın yayınlanmasını planlamak için bir tarih ve saat seçin.",
|
||||
"show": "Göster",
|
||||
"size": "Boyut",
|
||||
"upload": "Gönder",
|
||||
"uploadMessage": "Yüklemek için bir seçenek belirleyin.",
|
||||
"optionalPassword": "İsteğe bağlı şifre"
|
||||
},
|
||||
"search": {
|
||||
"images": "Görseller",
|
||||
"music": "Müzik",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Aramak için enter'a basın...",
|
||||
"search": "Ara...",
|
||||
"typeToSearch": "Aramak için yazın...",
|
||||
"types": "Türler",
|
||||
"video": "Video"
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Yönetim",
|
||||
"administrator": "Yönetici",
|
||||
"allowCommands": "Komutları çalıştır",
|
||||
"allowEdit": "Dosyaları veya dizinleri düzenleyin, yeniden adlandırın ve silin",
|
||||
"allowNew": "Yeni dosyalar ve dizinler oluşturun",
|
||||
"allowPublish": "Yeni linkler ve sayfaları yayınlayın",
|
||||
"allowSignup": "Kullanıcıların kaydolmasına izin ver",
|
||||
"avoidChanges": "(değişiklikleri önlemek için boş bırakın)",
|
||||
"branding": "Marka",
|
||||
"brandingDirectoryPath": "Marka dizin yolu",
|
||||
"brandingHelp": "Adını değiştirerek, logoyu değiştirerek, özel stiller ekleyerek ve hatta GitHub'a harici bağlantıları devre dışı bırakarak Filebrowser örneğinizin görünüşünü ve hissini özelleştirebilirsiniz.\nÖzel marka bilinci oluşturma hakkında daha fazla bilgi için lütfen {0} sayfasına göz atın.",
|
||||
"changePassword": "Şifre Değiştir",
|
||||
"commandRunner": "Komut satırı",
|
||||
"commandRunnerHelp": "Burada, adlandırılmış olaylarda yürütülen komutları ayarlayabilirsiniz. Her satıra bir tane yazmalısınız. {0} ve {1} ortam değişkenleri, {1}'ye göre {0} olacak şekilde kullanılabilir olacaktır. Bu özellik ve mevcut ortam değişkenleri hakkında daha fazla bilgi için lütfen {2}'yi okuyun.",
|
||||
"commandsUpdated": "Komutlar güncellendi!",
|
||||
"createUserDir": "Kullanıcı eklerken, kullanıcı ana dizinini otomatik oluştur",
|
||||
"customStylesheet": "Özel CSS",
|
||||
"defaultUserDescription": "Bu, yeni kullanıcılar için varsayılan ayarlardır.",
|
||||
"disableExternalLinks": "Harici bağlantıları devre dışı bırakın (dökümantasyon hariç)",
|
||||
"documentation": "dökümantasyon",
|
||||
"examples": "Örnekler",
|
||||
"executeOnShell": "Komut satırında çalıştır",
|
||||
"executeOnShellDescription": "Varsayılan olarak, FileBrowser komutları doğrudan dosyaları çağırarak yürütür. Bunları komut satırında çalıştırmak istiyorsanız (Bash veya PowerShell gibi), burada gerekli argümanlar ve flagler tanımlayabilirsiniz. Ayarlanırsa, yürüttüğünüz komut argüman olarak eklenir. Bu, hem kullanıcı komutları hem de event hooklar için geçerlidir.",
|
||||
"globalRules": "Bu, genel bir izin verme ve izin vermeme kurallar bütünüdür. Her kullanıcı için geçerlidirler. Bunları geçersiz kılmak için her kullanıcının ayarlarında belirli kurallar tanımlayabilirsiniz.",
|
||||
"globalSettings": "Genel Ayarlar",
|
||||
"hideDotfiles": ". ile başlayan dosyaları gizle",
|
||||
"insertPath": "Dizini ekle",
|
||||
"insertRegex": "Regex ifadesini ekle",
|
||||
"instanceName": "Instance adı",
|
||||
"language": "Dil",
|
||||
"lockPassword": "Kullanıcının parolayı değiştirmesini engelle",
|
||||
"newPassword": "Yeni şifre",
|
||||
"newPasswordConfirm": "Yeni şifre tekrarı",
|
||||
"newUser": "Yeni Kullanıcı",
|
||||
"password": "Şifre",
|
||||
"passwordUpdated": "Şifre güncellendi",
|
||||
"path": "Yol",
|
||||
"perm": {
|
||||
"create": "Dosyalar ve dizinler oluşturun",
|
||||
"delete": "Dosyalar ve dizinleri silin",
|
||||
"download": "İndir",
|
||||
"execute": "Komutları çalıştır",
|
||||
"modify": "Dosyaları değiştir",
|
||||
"rename": "Dosyaları ve dizinleri yeniden adlandırın veya taşıyın",
|
||||
"share": "Dosyaları paylaş"
|
||||
},
|
||||
"permissions": "İzinler",
|
||||
"permissionsHelp": "Kullanıcıyı yönetici olarak ayarlayabilir veya izinleri ayrı ayrı seçebilirsiniz. \"Yönetici\"yi seçerseniz, diğer tüm seçenekler otomatik olarak kontrol edilecektir. Kullanıcıların yönetimi, bir yöneticinin yetkisi olarak kalır.\n",
|
||||
"profileSettings": "Profil ayarları",
|
||||
"ruleExample1": "her klasördeki herhangi bir noktalı dosyaya (.git, .gitignore gibi) erişimi engeller.\n",
|
||||
"ruleExample2": "Root erişimidenki CaddyFile dosyalarına erişimi engelle.",
|
||||
"rules": "Kurallar",
|
||||
"rulesHelp": "Burada, bu belirli kullanıcı için bir dizi izin verme ve izin vermeme kuralı tanımlayabilirsiniz. Engellenen dosyalar listelerde görünmeyecek ve kullanıcı bunlara erişemeyecek. Kullanıcı erişimine göre regex ifadeleri destekliyoruz.\n",
|
||||
"scope": "Kapsam",
|
||||
"settingsUpdated": "Ayarlar güncellendi!",
|
||||
"shareDuration": "Paylaşım süresi",
|
||||
"shareManagement": "Paylaşım yönetimi",
|
||||
"shareDeleted": "Paylaşım silindi!",
|
||||
"singleClick": "Dosyaları ve dizinleri açmak için tek tıklamayı kullanın",
|
||||
"themes": {
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"title": "Theme"
|
||||
},
|
||||
"user": "Kullanıcı",
|
||||
"userCommands": "Komutları",
|
||||
"userCommandsHelp": "Bu kullanıcı için mevcut komutları içeren boşlukla ayrılmış bir liste. Örnek:\n",
|
||||
"userCreated": "Kullanıcı oluşturuldu!",
|
||||
"userDefaults": "Kullanıcı varsayılan ayarları",
|
||||
"userDeleted": "Kullanıcı silindi!",
|
||||
"userManagement": "Kullanıcı yönetimi",
|
||||
"userUpdated": "Kullanıcı güncellendi!",
|
||||
"username": "Kullanıcı adı",
|
||||
"users": "Kullanıcılar"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Yardım",
|
||||
"hugoNew": "Yeni Hugo",
|
||||
"login": "Giriş",
|
||||
"logout": "Çıkış",
|
||||
"myFiles": "Dosyalarım",
|
||||
"newFile": "Yeni dosya",
|
||||
"newFolder": "Yeni klasör",
|
||||
"preview": "Önizleme",
|
||||
"settings": "Ayarlar",
|
||||
"signup": "Kayıt",
|
||||
"siteSettings": "Site ayarları!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link kopyalandı!"
|
||||
},
|
||||
"time": {
|
||||
"days": "Gün",
|
||||
"hours": "Saat",
|
||||
"minutes": "Dakika",
|
||||
"seconds": "Saniye",
|
||||
"unit": "Zaman birimi"
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,9 @@
|
||||
"ptBR": "Português(Brasil)",
|
||||
"ro": "Romanian",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "Swedish(Sweden)",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文(简体)",
|
||||
"zhTW": "中文(繁體)"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ro": "Romanian",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"svSE": "Swedish(Sweden)",
|
||||
"tr" : "Türkçe",
|
||||
"zhCN": "中文 (简体)",
|
||||
"zhTW": "中文 (繁體)"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "whatwg-fetch";
|
||||
import cssVars from "css-vars-ponyfill";
|
||||
import { sync } from "vuex-router-sync";
|
||||
import store from "@/store";
|
||||
import router from "@/router";
|
||||
@@ -7,13 +9,19 @@ import { recaptcha, loginPage } from "@/utils/constants";
|
||||
import { login, validateLogin } from "@/utils/auth";
|
||||
import App from "@/App";
|
||||
|
||||
cssVars();
|
||||
|
||||
sync(store, router);
|
||||
|
||||
async function start() {
|
||||
if (loginPage) {
|
||||
await validateLogin();
|
||||
} else {
|
||||
await login("", "", "");
|
||||
try {
|
||||
if (loginPage) {
|
||||
await validateLogin();
|
||||
} else {
|
||||
await login("", "", "");
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if (recaptcha) {
|
||||
|
||||
@@ -12,6 +12,8 @@ export function parseToken(token) {
|
||||
|
||||
const data = JSON.parse(Base64.decode(parts[1]));
|
||||
|
||||
document.cookie = `auth=${token}; path=/`;
|
||||
|
||||
localStorage.setItem("jwt", token);
|
||||
store.commit("setJWT", token);
|
||||
store.commit("setUser", data.user);
|
||||
@@ -81,6 +83,8 @@ export async function signup(username, password) {
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
|
||||
|
||||
store.commit("setJWT", "");
|
||||
store.commit("setUser", null);
|
||||
localStorage.setItem("jwt", null);
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<header-bar v-if="showHeader" showMenu showLogo />
|
||||
|
||||
<h2 class="message">
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
<span>{{ message }}</span>
|
||||
<i class="material-icons">{{ info.icon }}</i>
|
||||
<span>{{ $t(info.message) }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
@@ -13,6 +13,10 @@
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
|
||||
const errors = {
|
||||
0: {
|
||||
icon: "cloud_off",
|
||||
message: "errors.connection",
|
||||
},
|
||||
403: {
|
||||
icon: "error",
|
||||
message: "errors.forbidden",
|
||||
@@ -33,11 +37,17 @@ export default {
|
||||
HeaderBar,
|
||||
},
|
||||
props: ["errorCode", "showHeader"],
|
||||
data: function () {
|
||||
return {
|
||||
icon: errors[this.errorCode].icon,
|
||||
message: this.$t(errors[this.errorCode].message),
|
||||
};
|
||||
computed: {
|
||||
code() {
|
||||
return this.errorCode === "0" ||
|
||||
this.errorCode === "404" ||
|
||||
this.errorCode === "403"
|
||||
? parseInt(this.errorCode)
|
||||
: 500;
|
||||
},
|
||||
info() {
|
||||
return errors[this.code];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<header-bar v-if="error || !req.type" showMenu showLogo />
|
||||
<header-bar v-if="error || req.type == null" showMenu showLogo />
|
||||
|
||||
<breadcrumbs base="/files" />
|
||||
|
||||
<errors v-if="error" :errorCode="errorCode" />
|
||||
<errors v-if="error" :errorCode="error.message" />
|
||||
<component v-else-if="currentView" :is="currentView"></component>
|
||||
<div v-else>
|
||||
<h2 class="message">
|
||||
<h2 class="message delayed">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
<div class="bounce3"></div>
|
||||
</div>
|
||||
<span>{{ $t("files.loading") }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
@@ -62,11 +67,6 @@ export default {
|
||||
return "preview";
|
||||
}
|
||||
},
|
||||
errorCode() {
|
||||
return this.error.message === "404" || this.error.message === "403"
|
||||
? parseInt(this.error.message)
|
||||
: 500;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.fetchData();
|
||||
@@ -116,7 +116,7 @@ export default {
|
||||
}
|
||||
|
||||
this.$store.commit("updateRequest", res);
|
||||
document.title = res.name;
|
||||
document.title = `${res.name} - ${this.$route.name}`;
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
} finally {
|
||||
@@ -124,15 +124,6 @@ export default {
|
||||
}
|
||||
},
|
||||
keyEvent(event) {
|
||||
if (this.show !== null) {
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
this.$store.commit("closeHovers");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// F1!
|
||||
if (event.keyCode === 112) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -10,26 +10,22 @@
|
||||
{{ $t("settings.profileSettings") }}
|
||||
</li></router-link
|
||||
>
|
||||
<router-link to="/settings/shares"
|
||||
<router-link to="/settings/shares" v-if="user.perm.share"
|
||||
><li :class="{ active: $route.path === '/settings/shares' }">
|
||||
{{ $t("settings.shareManagement") }}
|
||||
</li></router-link
|
||||
>
|
||||
<router-link to="/settings/global"
|
||||
><li
|
||||
:class="{ active: $route.path === '/settings/global' }"
|
||||
v-if="user.perm.admin"
|
||||
>
|
||||
<router-link to="/settings/global" v-if="user.perm.admin"
|
||||
><li :class="{ active: $route.path === '/settings/global' }">
|
||||
{{ $t("settings.globalSettings") }}
|
||||
</li></router-link
|
||||
>
|
||||
<router-link to="/settings/users"
|
||||
<router-link to="/settings/users" v-if="user.perm.admin"
|
||||
><li
|
||||
:class="{
|
||||
active:
|
||||
$route.path === '/settings/users' || $route.name === 'User',
|
||||
}"
|
||||
v-if="user.perm.admin"
|
||||
>
|
||||
{{ $t("settings.userManagement") }}
|
||||
</li></router-link
|
||||
@@ -38,6 +34,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading">
|
||||
<h2 class="message delayed">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
<div class="bounce3"></div>
|
||||
</div>
|
||||
<span>{{ $t("files.loading") }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
@@ -53,7 +60,7 @@ export default {
|
||||
HeaderBar,
|
||||
},
|
||||
computed: {
|
||||
...mapState(["user"]),
|
||||
...mapState(["user", "loading"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,50 @@
|
||||
|
||||
<breadcrumbs :base="'/share/' + hash" />
|
||||
|
||||
<div v-if="!loading">
|
||||
<div v-if="loading">
|
||||
<h2 class="message delayed">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
<div class="bounce3"></div>
|
||||
</div>
|
||||
<span>{{ $t("files.loading") }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div v-else-if="error">
|
||||
<div v-if="error.message === '401'">
|
||||
<div class="card floating" id="password">
|
||||
<div v-if="attemptedPasswordLogin" class="share__wrong__password">
|
||||
{{ $t("login.wrongCredentials") }}
|
||||
</div>
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("login.password") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<input
|
||||
v-focus
|
||||
type="password"
|
||||
:placeholder="$t('login.password')"
|
||||
v-model="password"
|
||||
@keyup.enter="fetchData"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="fetchData"
|
||||
:aria-label="$t('buttons.submit')"
|
||||
:title="$t('buttons.submit')"
|
||||
>
|
||||
{{ $t("buttons.submit") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<errors v-else :errorCode="error.message" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="share">
|
||||
<div class="share__box share__box__info">
|
||||
<div class="share__box__header">
|
||||
@@ -35,16 +78,30 @@
|
||||
<div class="share__box__element">
|
||||
<strong>{{ $t("prompts.displayName") }}</strong> {{ req.name }}
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<div class="share__box__element" :title="modTime">
|
||||
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<strong>{{ $t("prompts.size") }}:</strong> {{ humanSize }}
|
||||
</div>
|
||||
<div class="share__box__element share__box__center">
|
||||
<a target="_blank" :href="link" class="button button--flat">{{
|
||||
$t("buttons.download")
|
||||
}}</a>
|
||||
<a target="_blank" :href="link" class="button button--flat">
|
||||
<div>
|
||||
<i class="material-icons">file_download</i
|
||||
>{{ $t("buttons.download") }}
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
:href="inlineLink"
|
||||
class="button button--flat"
|
||||
v-if="!req.isDir"
|
||||
>
|
||||
<div>
|
||||
<i class="material-icons">open_in_new</i
|
||||
>{{ $t("buttons.openFile") }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="share__box__element share__box__center">
|
||||
<qrcode-vue :value="fullLink" size="200" level="M"></qrcode-vue>
|
||||
@@ -71,7 +128,11 @@
|
||||
readOnly
|
||||
>
|
||||
</item>
|
||||
<div v-if="req.items.length > showLimit" class="item">
|
||||
<div
|
||||
v-if="req.items.length > showLimit"
|
||||
class="item"
|
||||
@click="showLimit += 100"
|
||||
>
|
||||
<div>
|
||||
<p class="name">+ {{ req.items.length - showLimit }}</p>
|
||||
</div>
|
||||
@@ -106,39 +167,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="error">
|
||||
<div v-if="error.message === '401'">
|
||||
<div class="card floating" id="password">
|
||||
<div v-if="attemptedPasswordLogin" class="share__wrong__password">
|
||||
{{ $t("login.wrongCredentials") }}
|
||||
</div>
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("login.password") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<input
|
||||
v-focus
|
||||
type="password"
|
||||
:placeholder="$t('login.password')"
|
||||
v-model="password"
|
||||
@keyup.enter="fetchData"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="fetchData"
|
||||
:aria-label="$t('buttons.submit')"
|
||||
:title="$t('buttons.submit')"
|
||||
>
|
||||
{{ $t("buttons.submit") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<errors v-else :errorCode="errorCode" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -168,14 +196,18 @@ export default {
|
||||
},
|
||||
data: () => ({
|
||||
error: null,
|
||||
showLimit: 500,
|
||||
showLimit: 100,
|
||||
password: "",
|
||||
attemptedPasswordLogin: false,
|
||||
hash: null,
|
||||
token: null,
|
||||
}),
|
||||
watch: {
|
||||
$route: "fetchData",
|
||||
$route: function () {
|
||||
this.showLimit = 100;
|
||||
|
||||
this.fetchData();
|
||||
},
|
||||
},
|
||||
created: async function () {
|
||||
const hash = this.$route.params.pathMatch.split("/")[0];
|
||||
@@ -207,6 +239,11 @@ export default {
|
||||
const path = this.$route.path.split("/").splice(2).join("/");
|
||||
return `${baseURL}/api/public/dl/${path}${queryArg}`;
|
||||
},
|
||||
inlineLink: function () {
|
||||
let url = new URL(this.fullLink);
|
||||
url.searchParams.set("inline", "true");
|
||||
return url.href;
|
||||
},
|
||||
fullLink: function () {
|
||||
return window.location.origin + this.link;
|
||||
},
|
||||
@@ -220,10 +257,8 @@ export default {
|
||||
humanTime: function () {
|
||||
return moment(this.req.modified).fromNow();
|
||||
},
|
||||
errorCode() {
|
||||
return this.error.message === "404" || this.error.message === "403"
|
||||
? parseInt(this.error.message)
|
||||
: 500;
|
||||
modTime: function () {
|
||||
return new Date(Date.parse(this.req.modified)).toLocaleString();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -256,9 +291,11 @@ export default {
|
||||
this.token = file.token || "";
|
||||
|
||||
this.updateRequest(file);
|
||||
this.setLoading(false);
|
||||
document.title = `${file.name} - ${this.$route.name}`;
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
},
|
||||
keyEvent(event) {
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
<action icon="close" :label="$t('buttons.close')" @action="close()" />
|
||||
<title>{{ req.name }}</title>
|
||||
|
||||
<template #actions>
|
||||
<action
|
||||
id="save-button"
|
||||
icon="save"
|
||||
:label="$t('buttons.save')"
|
||||
@action="save()"
|
||||
/>
|
||||
</template>
|
||||
<action
|
||||
v-if="user.perm.modify"
|
||||
id="save-button"
|
||||
icon="save"
|
||||
:label="$t('buttons.save')"
|
||||
@action="save()"
|
||||
/>
|
||||
</header-bar>
|
||||
|
||||
<breadcrumbs base="/files" noLink />
|
||||
|
||||
@@ -25,18 +25,21 @@
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.copy"
|
||||
id="copy-button"
|
||||
icon="content_copy"
|
||||
:label="$t('buttons.copyFile')"
|
||||
show="copy"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.move"
|
||||
id="move-button"
|
||||
icon="forward"
|
||||
:label="$t('buttons.moveFile')"
|
||||
show="move"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.delete"
|
||||
id="delete-button"
|
||||
icon="delete"
|
||||
:label="$t('buttons.delete')"
|
||||
show="delete"
|
||||
@@ -55,13 +58,16 @@
|
||||
@action="switchView"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.download"
|
||||
icon="file_download"
|
||||
:label="$t('buttons.download')"
|
||||
@action="download"
|
||||
:counter="selectedCount"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.upload"
|
||||
icon="file_upload"
|
||||
id="upload-button"
|
||||
:label="$t('buttons.upload')"
|
||||
@action="upload"
|
||||
/>
|
||||
@@ -108,8 +114,13 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="$store.state.loading">
|
||||
<h2 class="message">
|
||||
<div v-if="loading">
|
||||
<h2 class="message delayed">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
<div class="bounce3"></div>
|
||||
</div>
|
||||
<span>{{ $t("files.loading") }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
@@ -135,7 +146,7 @@
|
||||
multiple
|
||||
/>
|
||||
</div>
|
||||
<div v-else id="listing" :class="user.viewMode">
|
||||
<div v-else id="listing" ref="listing" :class="user.viewMode">
|
||||
<div>
|
||||
<div class="item header">
|
||||
<div></div>
|
||||
@@ -248,11 +259,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import { mapState, mapGetters, mapMutations } from "vuex";
|
||||
import { users, files as api } from "@/api";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
import * as upload from "@/utils/upload";
|
||||
import css from "@/utils/css";
|
||||
import throttle from "lodash.throttle";
|
||||
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Action from "@/components/header/Action";
|
||||
@@ -272,10 +285,19 @@ export default {
|
||||
showLimit: 50,
|
||||
dragCounter: 0,
|
||||
width: window.innerWidth,
|
||||
itemWeight: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "selected", "user", "show", "multiple", "selected"]),
|
||||
...mapState([
|
||||
"req",
|
||||
"selected",
|
||||
"user",
|
||||
"show",
|
||||
"multiple",
|
||||
"selected",
|
||||
"loading",
|
||||
]),
|
||||
...mapGetters(["selectedCount"]),
|
||||
nameSorted() {
|
||||
return this.req.sorting.by === "name";
|
||||
@@ -350,15 +372,37 @@ export default {
|
||||
return this.width <= 736;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
req: function () {
|
||||
// Reset the show value
|
||||
this.showLimit = 50;
|
||||
|
||||
// Ensures that the listing is displayed
|
||||
Vue.nextTick(() => {
|
||||
// How much every listing item affects the window height
|
||||
this.setItemWeight();
|
||||
|
||||
// Fill and fit the window with listing items
|
||||
this.fillWindow(true);
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
// Check the columns size for the first time.
|
||||
this.resizeEvent();
|
||||
this.colunmsResize();
|
||||
|
||||
// How much every listing item affects the window height
|
||||
this.setItemWeight();
|
||||
|
||||
// Fill and fit the window with listing items
|
||||
this.fillWindow(true);
|
||||
|
||||
// Add the needed event listeners to the window and document.
|
||||
window.addEventListener("keydown", this.keyEvent);
|
||||
window.addEventListener("resize", this.resizeEvent);
|
||||
window.addEventListener("scroll", this.scrollEvent);
|
||||
window.addEventListener("resize", this.windowsResize);
|
||||
|
||||
if (!this.user.perm.create) return;
|
||||
document.addEventListener("dragover", this.preventDefault);
|
||||
document.addEventListener("dragenter", this.dragEnter);
|
||||
document.addEventListener("dragleave", this.dragLeave);
|
||||
@@ -367,9 +411,10 @@ export default {
|
||||
beforeDestroy() {
|
||||
// Remove event listeners before destroying this page.
|
||||
window.removeEventListener("keydown", this.keyEvent);
|
||||
window.removeEventListener("resize", this.resizeEvent);
|
||||
window.removeEventListener("scroll", this.scrollEvent);
|
||||
window.removeEventListener("resize", this.windowsResize);
|
||||
|
||||
if (this.user && !this.user.perm.create) return;
|
||||
document.removeEventListener("dragover", this.preventDefault);
|
||||
document.removeEventListener("dragenter", this.dragEnter);
|
||||
document.removeEventListener("dragleave", this.dragLeave);
|
||||
@@ -460,7 +505,7 @@ export default {
|
||||
for (let i of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[i].url,
|
||||
name: encodeURIComponent(this.req.items[i].name),
|
||||
name: this.req.items[i].name,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -485,7 +530,7 @@ export default {
|
||||
const from = item.from.endsWith("/")
|
||||
? item.from.slice(0, -1)
|
||||
: item.from;
|
||||
const to = this.$route.path + item.name;
|
||||
const to = this.$route.path + encodeURIComponent(item.name);
|
||||
items.push({ from, to, name: item.name });
|
||||
}
|
||||
|
||||
@@ -543,7 +588,7 @@ export default {
|
||||
|
||||
action(overwrite, rename);
|
||||
},
|
||||
resizeEvent() {
|
||||
colunmsResize() {
|
||||
// Update the columns size based on the window width.
|
||||
let columns = Math.floor(
|
||||
document.querySelector("main").offsetWidth / 300
|
||||
@@ -552,11 +597,27 @@ export default {
|
||||
if (columns === 0) columns = 1;
|
||||
items.style.width = `calc(${100 / columns}% - 1em)`;
|
||||
},
|
||||
scrollEvent() {
|
||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
|
||||
this.showLimit += 50;
|
||||
scrollEvent: throttle(function () {
|
||||
const totalItems = this.req.numDirs + this.req.numFiles;
|
||||
|
||||
// All items are displayed
|
||||
if (this.showLimit >= totalItems) return;
|
||||
|
||||
const currentPos = window.innerHeight + window.scrollY;
|
||||
|
||||
// Trigger at the 75% of the window height
|
||||
const triggerPos = document.body.offsetHeight - window.innerHeight * 0.25;
|
||||
|
||||
if (currentPos > triggerPos) {
|
||||
// Quantity of items needed to fill 2x of the window height
|
||||
const showQuantity = Math.ceil(
|
||||
(window.innerHeight * 2) / this.itemWeight
|
||||
);
|
||||
|
||||
// Increase the number of displayed items
|
||||
this.showLimit += showQuantity;
|
||||
}
|
||||
},
|
||||
}, 100),
|
||||
dragEnter() {
|
||||
this.dragCounter++;
|
||||
|
||||
@@ -591,22 +652,20 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
let base = "";
|
||||
let files = await upload.scanFiles(dt);
|
||||
let items = this.req.items;
|
||||
let path = this.$route.path.endsWith("/")
|
||||
? this.$route.path
|
||||
: this.$route.path + "/";
|
||||
|
||||
if (
|
||||
el !== null &&
|
||||
el.classList.contains("item") &&
|
||||
el.dataset.dir === "true"
|
||||
) {
|
||||
base = el.querySelector(".name").innerHTML + "/";
|
||||
}
|
||||
// Get url from ListingItem instance
|
||||
path = el.__vue__.url;
|
||||
|
||||
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 !== "") {
|
||||
try {
|
||||
items = (await api.fetch(path)).items;
|
||||
} catch (error) {
|
||||
@@ -707,9 +766,19 @@ export default {
|
||||
this.$store.commit("multiple", !this.multiple);
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
windowsResize() {
|
||||
windowsResize: throttle(function () {
|
||||
this.colunmsResize();
|
||||
this.width = window.innerWidth;
|
||||
},
|
||||
|
||||
// Listing element is not displayed
|
||||
if (this.$refs.listing == null) return;
|
||||
|
||||
// How much every listing item affects the window height
|
||||
this.setItemWeight();
|
||||
|
||||
// Fill but not fit the window
|
||||
this.fillWindow();
|
||||
}, 100),
|
||||
download() {
|
||||
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
||||
api.download(null, this.req.items[this.selected[0]].url);
|
||||
@@ -743,20 +812,53 @@ export default {
|
||||
viewMode: this.user.viewMode === "mosaic" ? "list" : "mosaic",
|
||||
};
|
||||
|
||||
try {
|
||||
await users.update(data, ["viewMode"]);
|
||||
this.$store.commit("updateUser", data);
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
}
|
||||
users.update(data, ["viewMode"]).catch(this.$showError);
|
||||
|
||||
// Await ensures correct value for setItemWeight()
|
||||
await this.$store.commit("updateUser", data);
|
||||
|
||||
this.setItemWeight();
|
||||
this.fillWindow();
|
||||
},
|
||||
upload: function () {
|
||||
if (typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined") {
|
||||
if (
|
||||
typeof window.DataTransferItem !== "undefined" &&
|
||||
typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined"
|
||||
) {
|
||||
this.$store.commit("showHover", "upload");
|
||||
} else {
|
||||
document.getElementById("upload-input").click();
|
||||
}
|
||||
},
|
||||
setItemWeight() {
|
||||
// Listing element is not displayed
|
||||
if (this.$refs.listing == null) return;
|
||||
|
||||
let itemQuantity = this.req.numDirs + this.req.numFiles;
|
||||
if (itemQuantity > this.showLimit) itemQuantity = this.showLimit;
|
||||
|
||||
// How much every listing item affects the window height
|
||||
this.itemWeight = this.$refs.listing.offsetHeight / itemQuantity;
|
||||
},
|
||||
fillWindow(fit = false) {
|
||||
const totalItems = this.req.numDirs + this.req.numFiles;
|
||||
|
||||
// More items are displayed than the total
|
||||
if (this.showLimit >= totalItems && !fit) return;
|
||||
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
// Quantity of items needed to fill 2x of the window height
|
||||
const showQuantity = Math.ceil(
|
||||
(windowHeight + windowHeight * 2) / this.itemWeight
|
||||
);
|
||||
|
||||
// Less items to display than current
|
||||
if (this.showLimit > showQuantity && !fit) return;
|
||||
|
||||
// Set the number of displayed items
|
||||
this.showLimit = showQuantity > totalItems ? totalItems : showQuantity;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
<template #actions>
|
||||
<action
|
||||
:disabled="loading"
|
||||
v-if="user.perm.rename"
|
||||
icon="mode_edit"
|
||||
:label="$t('buttons.rename')"
|
||||
show="rename"
|
||||
/>
|
||||
<action
|
||||
:disabled="loading"
|
||||
v-if="user.perm.delete"
|
||||
icon="delete"
|
||||
:label="$t('buttons.delete')"
|
||||
@action="deleteFile"
|
||||
@@ -30,6 +32,7 @@
|
||||
/>
|
||||
<action
|
||||
:disabled="loading"
|
||||
v-if="user.perm.download"
|
||||
icon="file_download"
|
||||
:label="$t('buttons.download')"
|
||||
@action="download"
|
||||
@@ -43,19 +46,32 @@
|
||||
</template>
|
||||
</header-bar>
|
||||
|
||||
<div class="loading" v-if="loading">
|
||||
<div class="loading delayed" v-if="loading">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
<div class="bounce3"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="!loading">
|
||||
<template v-else>
|
||||
<div class="preview">
|
||||
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
|
||||
<audio v-else-if="req.type == 'audio'" :src="raw" controls></audio>
|
||||
<video v-else-if="req.type == 'video'" :src="raw" controls>
|
||||
<audio
|
||||
v-else-if="req.type == 'audio'"
|
||||
ref="player"
|
||||
:src="raw"
|
||||
controls
|
||||
:autoplay="autoPlay"
|
||||
@play="autoPlay = true"
|
||||
></audio>
|
||||
<video
|
||||
v-else-if="req.type == 'video'"
|
||||
ref="player"
|
||||
:src="raw"
|
||||
controls
|
||||
:autoplay="autoPlay"
|
||||
@play="autoPlay = true"
|
||||
>
|
||||
<track
|
||||
kind="captions"
|
||||
v-for="(sub, index) in subtitles"
|
||||
@@ -73,12 +89,31 @@
|
||||
class="pdf"
|
||||
:data="raw"
|
||||
></object>
|
||||
<a v-else-if="req.type == 'blob'" :href="downloadUrl">
|
||||
<h2 class="message">
|
||||
{{ $t("buttons.download") }}
|
||||
<i class="material-icons">file_download</i>
|
||||
</h2>
|
||||
</a>
|
||||
<div v-else-if="req.type == 'blob'" class="info">
|
||||
<div class="title">
|
||||
<i class="material-icons">feedback</i>
|
||||
{{ $t("files.noPreview") }}
|
||||
</div>
|
||||
<div>
|
||||
<a target="_blank" :href="downloadUrl" class="button button--flat">
|
||||
<div>
|
||||
<i class="material-icons">file_download</i
|
||||
>{{ $t("buttons.download") }}
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
:href="downloadUrl + '&inline=true'"
|
||||
class="button button--flat"
|
||||
v-if="!req.isDir"
|
||||
>
|
||||
<div>
|
||||
<i class="material-icons">open_in_new</i
|
||||
>{{ $t("buttons.openFile") }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -136,6 +171,7 @@ export default {
|
||||
showNav: true,
|
||||
navTimeout: null,
|
||||
hoverNav: false,
|
||||
autoPlay: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -158,11 +194,9 @@ export default {
|
||||
if (this.req.type === "image" && !this.fullSize) {
|
||||
return `${baseURL}/api/preview/big${url.encodePath(
|
||||
this.req.path
|
||||
)}?auth=${this.jwt}&k=${key}`;
|
||||
)}?k=${key}`;
|
||||
}
|
||||
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${
|
||||
this.jwt
|
||||
}&k=${key}`;
|
||||
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?k=${key}`;
|
||||
},
|
||||
raw() {
|
||||
return `${this.previewUrl}&inline=true`;
|
||||
@@ -207,11 +241,11 @@ export default {
|
||||
},
|
||||
prev() {
|
||||
this.hoverNav = false;
|
||||
this.$router.push({ path: this.previousLink });
|
||||
this.$router.replace({ path: this.previousLink });
|
||||
},
|
||||
next() {
|
||||
this.hoverNav = false;
|
||||
this.$router.push({ path: this.nextLink });
|
||||
this.$router.replace({ path: this.nextLink });
|
||||
},
|
||||
key(event) {
|
||||
if (this.show !== null) {
|
||||
@@ -230,9 +264,17 @@ export default {
|
||||
}
|
||||
},
|
||||
async updatePreview() {
|
||||
if (
|
||||
this.$refs.player &&
|
||||
this.$refs.player.paused &&
|
||||
!this.$refs.player.ended
|
||||
) {
|
||||
this.autoPlay = false;
|
||||
}
|
||||
|
||||
if (this.req.subtitles) {
|
||||
this.subtitles = this.req.subtitles.map(
|
||||
(sub) => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`
|
||||
(sub) => `${baseURL}/api/raw${sub}?inline=true`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="row" v-if="settings !== null">
|
||||
<errors v-if="error" :errorCode="error.message" />
|
||||
<div class="row" v-else-if="!loading">
|
||||
<div class="column">
|
||||
<form class="card" @submit.prevent="save">
|
||||
<div class="card-title">
|
||||
@@ -170,12 +171,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
import { settings as api } from "@/api";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
import UserForm from "@/components/settings/UserForm";
|
||||
import Rules from "@/components/settings/Rules";
|
||||
import Themes from "@/components/settings/Themes";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
import Errors from "@/views/Errors";
|
||||
|
||||
export default {
|
||||
name: "settings",
|
||||
@@ -183,19 +185,23 @@ export default {
|
||||
Themes,
|
||||
UserForm,
|
||||
Rules,
|
||||
Errors,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
error: null,
|
||||
originalSettings: null,
|
||||
settings: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["user"]),
|
||||
...mapState(["user", "loading"]),
|
||||
isExecEnabled: () => enableExec,
|
||||
},
|
||||
async created() {
|
||||
try {
|
||||
this.setLoading(true);
|
||||
|
||||
const original = await api.get();
|
||||
let settings = { ...original, commands: [] };
|
||||
|
||||
@@ -211,10 +217,13 @@ export default {
|
||||
this.originalSettings = original;
|
||||
this.settings = settings;
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
this.error = e;
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["setLoading"]),
|
||||
capitalize(name, where = "_") {
|
||||
if (where === "caps") where = /(?=[A-Z])/;
|
||||
let splitted = name.split(where);
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
<input type="checkbox" v-model="singleClick" />
|
||||
{{ $t("settings.singleClick") }}
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" v-model="dateFormat" />
|
||||
{{ $t("settings.setDateFormat") }}
|
||||
</p>
|
||||
<h3>{{ $t("settings.language") }}</h3>
|
||||
<languages
|
||||
class="input input--block"
|
||||
@@ -83,6 +87,7 @@ export default {
|
||||
passwordConf: "",
|
||||
hideDotfiles: false,
|
||||
singleClick: false,
|
||||
dateFormat: false,
|
||||
locale: "",
|
||||
};
|
||||
},
|
||||
@@ -103,12 +108,14 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.setLoading(false);
|
||||
this.locale = this.user.locale;
|
||||
this.hideDotfiles = this.user.hideDotfiles;
|
||||
this.singleClick = this.user.singleClick;
|
||||
this.dateFormat = this.user.dateFormat;
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["updateUser"]),
|
||||
...mapMutations(["updateUser", "setLoading"]),
|
||||
async updatePassword(event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -134,8 +141,9 @@ export default {
|
||||
locale: this.locale,
|
||||
hideDotfiles: this.hideDotfiles,
|
||||
singleClick: this.singleClick,
|
||||
dateFormat: this.dateFormat,
|
||||
};
|
||||
await api.update(data, ["locale", "hideDotfiles", "singleClick"]);
|
||||
await api.update(data, ["locale", "hideDotfiles", "singleClick", "dateFormat"]);
|
||||
this.updateUser(data);
|
||||
this.$showSuccess(this.$t("settings.settingsUpdated"));
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<errors v-if="error" :errorCode="error.message" />
|
||||
<div class="row" v-else-if="!loading">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("settings.shareManagement") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content full">
|
||||
<div class="card-content full" v-if="links.length > 0">
|
||||
<table>
|
||||
<tr>
|
||||
<th>{{ $t("settings.path") }}</th>
|
||||
@@ -52,6 +53,10 @@
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<h2 class="message" v-else>
|
||||
<i class="material-icons">sentiment_dissatisfied</i>
|
||||
<span>{{ $t("files.lonely") }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,21 +64,28 @@
|
||||
|
||||
<script>
|
||||
import { share as api, users } from "@/api";
|
||||
import moment from "moment";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
import moment from "moment";
|
||||
import Clipboard from "clipboard";
|
||||
import { mapState } from "vuex";
|
||||
import Errors from "@/views/Errors";
|
||||
|
||||
export default {
|
||||
name: "shares",
|
||||
computed: mapState(["user"]),
|
||||
components: {
|
||||
Errors,
|
||||
},
|
||||
computed: mapState(["user", "loading"]),
|
||||
data: function () {
|
||||
return {
|
||||
error: null,
|
||||
links: [],
|
||||
clip: null,
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.setLoading(true);
|
||||
|
||||
try {
|
||||
let links = await api.list();
|
||||
if (this.user.perm.admin) {
|
||||
@@ -87,7 +99,9 @@ export default {
|
||||
}
|
||||
this.links = links;
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
this.error = e;
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -100,6 +114,7 @@ export default {
|
||||
this.clip.destroy();
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["setLoading"]),
|
||||
deleteLink: async function (event, link) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -108,8 +123,13 @@ export default {
|
||||
confirm: () => {
|
||||
this.$store.commit("closeHovers");
|
||||
|
||||
api.remove(link.hash);
|
||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||
try {
|
||||
api.remove(link.hash);
|
||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||
this.$showSuccess(this.$t("settings.shareDeleted"));
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<errors v-if="error" :errorCode="error.message" />
|
||||
<div class="row" v-else-if="!loading">
|
||||
<div class="column">
|
||||
<form v-if="loaded" @submit="save" class="card">
|
||||
<form @submit="save" class="card">
|
||||
<div class="card-title">
|
||||
<h2 v-if="user.id === 0">{{ $t("settings.newUser") }}</h2>
|
||||
<h2 v-else>{{ $t("settings.user") }} {{ user.username }}</h2>
|
||||
@@ -55,21 +56,23 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from "vuex";
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
import { users as api, settings } from "@/api";
|
||||
import UserForm from "@/components/settings/UserForm";
|
||||
import Errors from "@/views/Errors";
|
||||
import deepClone from "lodash.clonedeep";
|
||||
|
||||
export default {
|
||||
name: "user",
|
||||
components: {
|
||||
UserForm,
|
||||
Errors,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
error: null,
|
||||
originalUser: null,
|
||||
user: {},
|
||||
loaded: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -79,6 +82,7 @@ export default {
|
||||
isNew() {
|
||||
return this.$route.path === "/settings/users/new";
|
||||
},
|
||||
...mapState(["loading"]),
|
||||
},
|
||||
watch: {
|
||||
$route: "fetchData",
|
||||
@@ -88,8 +92,10 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["closeHovers", "showHover", "setUser"]),
|
||||
...mapMutations(["closeHovers", "showHover", "setUser", "setLoading"]),
|
||||
async fetchData() {
|
||||
this.setLoading(true);
|
||||
|
||||
try {
|
||||
if (this.isNew) {
|
||||
let { defaults } = await settings.get();
|
||||
@@ -105,10 +111,10 @@ export default {
|
||||
const id = this.$route.params.pathMatch;
|
||||
this.user = { ...(await api.get(id)) };
|
||||
}
|
||||
|
||||
this.loaded = true;
|
||||
} catch (e) {
|
||||
this.$router.push({ path: "/settings/users/new" });
|
||||
this.error = e;
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
},
|
||||
deletePrompt() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<errors v-if="error" :errorCode="error.message" />
|
||||
<div class="row" v-else-if="!loading">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
@@ -41,21 +42,37 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
import { users as api } from "@/api";
|
||||
import Errors from "@/views/Errors";
|
||||
|
||||
export default {
|
||||
name: "users",
|
||||
components: {
|
||||
Errors,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
error: null,
|
||||
users: [],
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.setLoading(true);
|
||||
|
||||
try {
|
||||
this.users = await api.getAll();
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
this.error = e;
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(["loading"]),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["setLoading"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
runtimeCompiler: true,
|
||||
publicPath: "[{[ .StaticURL ]}]",
|
||||
parallel: 2,
|
||||
configureWebpack: {
|
||||
plugins: [
|
||||
new CompressionPlugin({
|
||||
include: /\.js$/,
|
||||
deleteOriginalAssets: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
3
go.mod
3
go.mod
@@ -10,6 +10,7 @@ require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
@@ -37,5 +38,5 @@ require (
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
google.golang.org/appengine v1.5.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
||||
26
go.sum
26
go.sum
@@ -39,6 +39,17 @@ github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44am
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483 h1:rz9dPf+Unge2D5RNIRNFvCc2OrGfhAPuxx4L6412jdI=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d h1:F/7L5wr/fP/SKeO5HuMlNEX9Ipyx2MbH2rV9G4zJRpk=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e h1:IxIbA7VbCNrwumIYjDoMOdf4KOSkMC6NJE4s8oRbE7E=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
@@ -47,12 +58,19 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-acme/lego v2.5.0+incompatible h1:5fNN9yRQfv8ymH3DSsxla+4aYeQt2IgfZqHKVnK8f0s=
|
||||
github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg=
|
||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -83,6 +101,8 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
@@ -225,6 +245,10 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b h1:IYiJPiJfzktmDAO1HQiwjMjwjlYKHAL7KzeD544RJPs=
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -280,6 +304,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
13
http/auth.go
13
http/auth.go
@@ -28,6 +28,7 @@ type userInfo struct {
|
||||
Commands []string `json:"commands"`
|
||||
LockPassword bool `json:"lockPassword"`
|
||||
HideDotfiles bool `json:"hideDotfiles"`
|
||||
DateFormat bool `json:"dateFormat"`
|
||||
}
|
||||
|
||||
type authToken struct {
|
||||
@@ -48,11 +49,16 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
|
||||
}
|
||||
|
||||
auth := r.URL.Query().Get("auth")
|
||||
if auth == "" {
|
||||
return "", request.ErrNoTokenInRequest
|
||||
if auth != "" && strings.Count(auth, ".") == 2 {
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
return auth, nil
|
||||
cookie, _ := r.Cookie("auth")
|
||||
if cookie != nil && strings.Count(cookie.Value, ".") == 2 {
|
||||
return cookie.Value, nil
|
||||
}
|
||||
|
||||
return "", request.ErrNoTokenInRequest
|
||||
}
|
||||
|
||||
func withUser(fn handleFunc) handleFunc {
|
||||
@@ -179,6 +185,7 @@ func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.Use
|
||||
LockPassword: user.LockPassword,
|
||||
Commands: user.Commands,
|
||||
HideDotfiles: user.HideDotfiles,
|
||||
DateFormat: user.DateFormat,
|
||||
},
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
IssuedAt: time.Now().Unix(),
|
||||
|
||||
@@ -59,14 +59,6 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
||||
}
|
||||
}
|
||||
|
||||
if !d.server.EnableExec || !d.user.CanExecute(strings.Split(raw, " ")[0]) {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
command, err := runner.ParseCommand(d.settings, raw)
|
||||
if err != nil {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:govet
|
||||
@@ -75,6 +67,14 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if !d.server.EnableExec || !d.user.CanExecute(command[0]) {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
|
||||
cmd.Dir = d.user.FullPath(r.URL.Path)
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ func (d *data) Check(path string) bool {
|
||||
|
||||
func handle(fn handleFunc, prefix string, store *storage.Storage, server *settings.Server) http.Handler {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
|
||||
settings, err := store.Settings.Get()
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: couldn't get settings: %v\n", err)
|
||||
|
||||
@@ -25,6 +25,12 @@ func NewHandler(
|
||||
server.Clean()
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.Use(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Security-Policy", `default-src 'self'`)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
index, static := getStaticHandlers(store, server, assetsFs)
|
||||
|
||||
// NOTE: This fixes the issue where it would redirect if people did not put a
|
||||
@@ -57,7 +63,7 @@ func NewHandler(
|
||||
api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE")
|
||||
api.PathPrefix("/resources").Handler(monkey(resourcePostHandler(fileCache), "/api/resources")).Methods("POST")
|
||||
api.PathPrefix("/resources").Handler(monkey(resourcePutHandler, "/api/resources")).Methods("PUT")
|
||||
api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler, "/api/resources")).Methods("PATCH")
|
||||
api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler(fileCache), "/api/resources")).Methods("PATCH")
|
||||
|
||||
api.Path("/shares").Handler(monkey(shareListHandler, "/api/shares")).Methods("GET")
|
||||
api.PathPrefix("/share").Handler(monkey(shareGetsHandler, "/api/share")).Methods("GET")
|
||||
|
||||
@@ -68,30 +68,52 @@ func previewHandler(imgSvc ImgService, fileCache FileCache, enableThumbnails, re
|
||||
})
|
||||
}
|
||||
|
||||
func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgService, fileCache FileCache,
|
||||
file *files.FileInfo, previewSize PreviewSize, enableThumbnails, resizePreview bool) (int, error) {
|
||||
func handleImagePreview(
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
imgSvc ImgService,
|
||||
fileCache FileCache,
|
||||
file *files.FileInfo,
|
||||
previewSize PreviewSize,
|
||||
enableThumbnails, resizePreview bool,
|
||||
) (int, error) {
|
||||
if (previewSize == PreviewSizeBig && !resizePreview) ||
|
||||
(previewSize == PreviewSizeThumb && !enableThumbnails) {
|
||||
return rawFileHandler(w, r, file)
|
||||
}
|
||||
|
||||
format, err := imgSvc.FormatFromExtension(file.Extension)
|
||||
// Unsupported extensions directly return the raw data
|
||||
if err == img.ErrUnsupportedFormat || format == img.FormatGif {
|
||||
return rawFileHandler(w, r, file)
|
||||
}
|
||||
if err != nil {
|
||||
// Unsupported extensions directly return the raw data
|
||||
if err == img.ErrUnsupportedFormat {
|
||||
return rawFileHandler(w, r, file)
|
||||
}
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
cacheKey := previewCacheKey(file.Path, file.ModTime.Unix(), previewSize)
|
||||
cachedFile, ok, err := fileCache.Load(r.Context(), cacheKey)
|
||||
resizedImage, ok, err := fileCache.Load(r.Context(), cacheKey)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
if ok {
|
||||
_, _ = w.Write(cachedFile)
|
||||
return 0, nil
|
||||
if !ok {
|
||||
resizedImage, err = createPreview(imgSvc, fileCache, file, previewSize)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", "private")
|
||||
http.ServeContent(w, r, file.Name, file.ModTime, bytes.NewReader(resizedImage))
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func createPreview(imgSvc ImgService, fileCache FileCache,
|
||||
file *files.FileInfo, previewSize PreviewSize) ([]byte, error) {
|
||||
fd, err := file.Fs.Open(file.Path)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
@@ -102,35 +124,31 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic
|
||||
)
|
||||
|
||||
switch {
|
||||
case previewSize == PreviewSizeBig && resizePreview && format != img.FormatGif:
|
||||
case previewSize == PreviewSizeBig:
|
||||
width = 1080
|
||||
height = 1080
|
||||
options = append(options, img.WithMode(img.ResizeModeFit), img.WithQuality(img.QualityMedium))
|
||||
case previewSize == PreviewSizeThumb && enableThumbnails:
|
||||
case previewSize == PreviewSizeThumb:
|
||||
width = 128
|
||||
height = 128
|
||||
options = append(options, img.WithMode(img.ResizeModeFill), img.WithQuality(img.QualityLow), img.WithFormat(img.FormatJpeg))
|
||||
default:
|
||||
if _, err := rawFileHandler(w, r, file); err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
return 0, nil
|
||||
return nil, img.ErrUnsupportedFormat
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
if err := imgSvc.Resize(context.Background(), fd, width, height, buf, options...); err != nil {
|
||||
return 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
cacheKey := previewCacheKey(file.Path, file.ModTime.Unix(), previewSize)
|
||||
if err := fileCache.Store(context.Background(), cacheKey, buf.Bytes()); err != nil {
|
||||
fmt.Printf("failed to cache resized image: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
_, _ = w.Write(buf.Bytes())
|
||||
|
||||
return 0, nil
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func previewCacheKey(fPath string, fTime int64, previewSize PreviewSize) string {
|
||||
|
||||
@@ -38,7 +38,7 @@ var withHashFile = func(fn handleFunc) handleFunc {
|
||||
Fs: d.user.Fs,
|
||||
Path: link.Path,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: true,
|
||||
Expand: false,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
Token: link.Token,
|
||||
|
||||
45
http/raw.go
45
http/raw.go
@@ -1,9 +1,8 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
gopath "path"
|
||||
@@ -11,7 +10,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/archiver"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/files"
|
||||
"github.com/filebrowser/filebrowser/v2/fileutils"
|
||||
@@ -117,19 +115,16 @@ func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
file afero.File
|
||||
arcReadCloser = ioutil.NopCloser(&bytes.Buffer{})
|
||||
)
|
||||
if !files.IsNamedPipe(info.Mode()) {
|
||||
file, err = d.user.Fs.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
arcReadCloser = file
|
||||
if !info.IsDir() && !info.Mode().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := d.user.Fs.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if path != commonPath {
|
||||
filename := strings.TrimPrefix(path, commonPath)
|
||||
filename = strings.TrimPrefix(filename, string(filepath.Separator))
|
||||
@@ -138,7 +133,7 @@ func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
|
||||
FileInfo: info,
|
||||
CustomName: filename,
|
||||
},
|
||||
ReadCloser: arcReadCloser,
|
||||
ReadCloser: file,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -152,9 +147,10 @@ func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
err = addFile(ar, d, filepath.Join(path, name), commonPath)
|
||||
fPath := filepath.Join(path, name)
|
||||
err = addFile(ar, d, fPath, commonPath)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Printf("Failed to archive %s: %v", fPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,14 +177,14 @@ func rawDirHandler(w http.ResponseWriter, r *http.Request, d *data, file *files.
|
||||
|
||||
commonDir := fileutils.CommonPrefix(filepath.Separator, filenames...)
|
||||
|
||||
var name string
|
||||
if len(filenames) > 1 {
|
||||
name = "_" + filepath.Base(commonDir)
|
||||
} else {
|
||||
name := filepath.Base(commonDir)
|
||||
if name == "." || name == "" || name == string(filepath.Separator) {
|
||||
name = file.Name
|
||||
}
|
||||
if name == "." || name == "" {
|
||||
name = "archive"
|
||||
// Prefix used to distinguish a filelist generated
|
||||
// archive from the full directory archive
|
||||
if len(filenames) > 1 {
|
||||
name = "_" + name
|
||||
}
|
||||
name += extension
|
||||
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
|
||||
@@ -196,7 +192,7 @@ func rawDirHandler(w http.ResponseWriter, r *http.Request, d *data, file *files.
|
||||
for _, fname := range filenames {
|
||||
err = addFile(ar, d, fname, commonDir)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
log.Printf("Failed to archive %s: %v", fname, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +208,7 @@ func rawFileHandler(w http.ResponseWriter, r *http.Request, file *files.FileInfo
|
||||
|
||||
setContentDisposition(w, r, file)
|
||||
|
||||
w.Header().Set("Cache-Control", "private")
|
||||
http.ServeContent(w, r, file.Name, file.ModTime, fd)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
184
http/resource.go
184
http/resource.go
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -27,6 +26,7 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
||||
Expand: true,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
Content: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
@@ -63,7 +63,7 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
|
||||
Fs: d.user.Fs,
|
||||
Path: r.URL.Path,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: true,
|
||||
Expand: false,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
})
|
||||
@@ -95,13 +95,9 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_, _ = io.Copy(ioutil.Discard, r.Body)
|
||||
}()
|
||||
|
||||
// Directories creation on POST.
|
||||
if strings.HasSuffix(r.URL.Path, "/") {
|
||||
err := d.user.Fs.MkdirAll(r.URL.Path, 0775)
|
||||
err := d.user.Fs.MkdirAll(r.URL.Path, 0775) //nolint:gomnd
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
@@ -109,7 +105,7 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
||||
Fs: d.user.Fs,
|
||||
Path: r.URL.Path,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: true,
|
||||
Expand: false,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
})
|
||||
@@ -118,6 +114,11 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
||||
return http.StatusConflict, nil
|
||||
}
|
||||
|
||||
// Permission for overwriting the file
|
||||
if !d.user.Perm.Modify {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
err = delThumbs(r.Context(), fileCache, file)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
@@ -125,7 +126,10 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
||||
}
|
||||
|
||||
err = d.RunHook(func() error {
|
||||
info, _ := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||
info, writeErr := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||
if writeErr != nil {
|
||||
return writeErr
|
||||
}
|
||||
|
||||
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
|
||||
w.Header().Set("ETag", etag)
|
||||
@@ -145,83 +149,77 @@ var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_, _ = io.Copy(ioutil.Discard, r.Body)
|
||||
}()
|
||||
|
||||
// Only allow PUT for files.
|
||||
if strings.HasSuffix(r.URL.Path, "/") {
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
err := d.RunHook(func() error {
|
||||
info, _ := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||
exists, err := afero.Exists(d.user.Fs, r.URL.Path)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
if !exists {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
err = d.RunHook(func() error {
|
||||
info, writeErr := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||
if writeErr != nil {
|
||||
return writeErr
|
||||
}
|
||||
|
||||
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
|
||||
w.Header().Set("ETag", etag)
|
||||
return nil
|
||||
}, "save", r.URL.Path, "", d.user)
|
||||
|
||||
if err != nil {
|
||||
_ = d.user.Fs.RemoveAll(r.URL.Path)
|
||||
}
|
||||
|
||||
return errToStatus(err), err
|
||||
})
|
||||
|
||||
var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
src := r.URL.Path
|
||||
dst := r.URL.Query().Get("destination")
|
||||
action := r.URL.Query().Get("action")
|
||||
dst, err := url.QueryUnescape(dst)
|
||||
if !d.Check(src) || !d.Check(dst) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
if err != nil {
|
||||
func resourcePatchHandler(fileCache FileCache) handleFunc {
|
||||
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
src := r.URL.Path
|
||||
dst := r.URL.Query().Get("destination")
|
||||
action := r.URL.Query().Get("action")
|
||||
dst, err := url.QueryUnescape(dst)
|
||||
if !d.Check(src) || !d.Check(dst) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
if dst == "/" || src == "/" {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
err = checkParent(src, dst)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
override := r.URL.Query().Get("override") == "true"
|
||||
rename := r.URL.Query().Get("rename") == "true"
|
||||
if !override && !rename {
|
||||
if _, err = d.user.Fs.Stat(dst); err == nil {
|
||||
return http.StatusConflict, nil
|
||||
}
|
||||
}
|
||||
if rename {
|
||||
dst = addVersionSuffix(dst, d.user.Fs)
|
||||
}
|
||||
|
||||
// Permission for overwriting the file
|
||||
if override && !d.user.Perm.Modify {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
err = d.RunHook(func() error {
|
||||
return patchAction(r.Context(), action, src, dst, d, fileCache)
|
||||
}, action, src, dst, d.user)
|
||||
|
||||
return errToStatus(err), err
|
||||
}
|
||||
if dst == "/" || src == "/" {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
if err = checkParent(src, dst); err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
override := r.URL.Query().Get("override") == "true"
|
||||
rename := r.URL.Query().Get("rename") == "true"
|
||||
if !override && !rename {
|
||||
if _, err = d.user.Fs.Stat(dst); err == nil {
|
||||
return http.StatusConflict, nil
|
||||
}
|
||||
}
|
||||
if rename {
|
||||
dst = addVersionSuffix(dst, d.user.Fs)
|
||||
}
|
||||
|
||||
err = d.RunHook(func() error {
|
||||
switch action {
|
||||
// TODO: use enum
|
||||
case "copy":
|
||||
if !d.user.Perm.Create {
|
||||
return errors.ErrPermissionDenied
|
||||
}
|
||||
|
||||
return fileutils.Copy(d.user.Fs, src, dst)
|
||||
case "rename":
|
||||
if !d.user.Perm.Rename {
|
||||
return errors.ErrPermissionDenied
|
||||
}
|
||||
src = path.Clean("/" + src)
|
||||
dst = path.Clean("/" + dst)
|
||||
|
||||
return fileutils.MoveFile(d.user.Fs, src, dst)
|
||||
default:
|
||||
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
|
||||
}
|
||||
}, action, src, dst, d.user)
|
||||
|
||||
return errToStatus(err), err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func checkParent(src, dst string) error {
|
||||
rel, err := filepath.Rel(src, dst)
|
||||
@@ -257,12 +255,12 @@ func addVersionSuffix(source string, fs afero.Fs) string {
|
||||
|
||||
func writeFile(fs afero.Fs, dst string, in io.Reader) (os.FileInfo, error) {
|
||||
dir, _ := path.Split(dst)
|
||||
err := fs.MkdirAll(dir, 0775)
|
||||
err := fs.MkdirAll(dir, 0775) //nolint:gomnd
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
||||
file, err := fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) //nolint:gomnd
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -292,3 +290,43 @@ func delThumbs(ctx context.Context, fileCache FileCache, file *files.FileInfo) e
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func patchAction(ctx context.Context, action, src, dst string, d *data, fileCache FileCache) error {
|
||||
switch action {
|
||||
// TODO: use enum
|
||||
case "copy":
|
||||
if !d.user.Perm.Create {
|
||||
return errors.ErrPermissionDenied
|
||||
}
|
||||
|
||||
return fileutils.Copy(d.user.Fs, src, dst)
|
||||
case "rename":
|
||||
if !d.user.Perm.Rename {
|
||||
return errors.ErrPermissionDenied
|
||||
}
|
||||
src = path.Clean("/" + src)
|
||||
dst = path.Clean("/" + dst)
|
||||
|
||||
file, err := files.NewFileInfo(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: src,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: false,
|
||||
ReadHeader: false,
|
||||
Checker: d,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete thumbnails
|
||||
err = delThumbs(ctx, fileCache, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fileutils.MoveFile(d.user.Fs, src, dst)
|
||||
default:
|
||||
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
bytes := make([]byte, 6)
|
||||
bytes := make([]byte, 6) //nolint:gomnd
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
@@ -129,7 +129,7 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
||||
|
||||
var token string
|
||||
if len(hash) > 0 {
|
||||
tokenBuffer := make([]byte, 96)
|
||||
tokenBuffer := make([]byte, 96) //nolint:gomnd
|
||||
if _, err := rand.Read(tokenBuffer); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -28,6 +29,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
||||
data := map[string]interface{}{
|
||||
"Name": d.settings.Branding.Name,
|
||||
"DisableExternal": d.settings.Branding.DisableExternal,
|
||||
"Color": d.settings.Branding.Color,
|
||||
"BaseURL": d.server.BaseURL,
|
||||
"Version": version.Version,
|
||||
"StaticURL": path.Join(d.server.BaseURL, "/static"),
|
||||
@@ -71,12 +73,12 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(data, "", " ")
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
data["Json"] = string(b)
|
||||
data["Json"] = strings.ReplaceAll(string(b), `'`, `\'`)
|
||||
|
||||
fileContents, err := fs.ReadFile(fSys, file)
|
||||
if err != nil {
|
||||
@@ -109,6 +111,9 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
const maxAge = 86400 // 1 day
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%v", maxAge))
|
||||
|
||||
if d.settings.Branding.Files != "" {
|
||||
if strings.HasPrefix(r.URL.Path, "img/") {
|
||||
fPath := filepath.Join(d.settings.Branding.Files, r.URL.Path)
|
||||
@@ -127,7 +132,19 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return handleWithStaticData(w, r, d, assetsFs, r.URL.Path, "application/javascript; charset=utf-8")
|
||||
fileContents, err := fs.ReadFile(assetsFs, r.URL.Path+".gz")
|
||||
if err != nil {
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
|
||||
|
||||
if _, err := w.Write(fileContents); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}, "/static/", store, server)
|
||||
|
||||
return index, static
|
||||
|
||||
@@ -25,7 +25,7 @@ type modifyUserRequest struct {
|
||||
|
||||
func getUserID(r *http.Request) (uint, error) {
|
||||
vars := mux.Vars(r)
|
||||
i, err := strconv.ParseUint(vars["id"], 10, 0)
|
||||
i, err := strconv.ParseUint(vars["id"], 10, 0) //nolint:gomnd
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -138,7 +138,7 @@ var userPostHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
w.Header().Set("Location", "/settings/users/"+strconv.FormatUint(uint64(req.Data.ID), 10))
|
||||
w.Header().Set("Location", "/settings/users/"+strconv.FormatUint(uint64(req.Data.ID), 10)) //nolint:gomnd
|
||||
return http.StatusCreated, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/dsoprea/go-exif/v3"
|
||||
"github.com/marusama/semaphore/v2"
|
||||
|
||||
exifcommon "github.com/dsoprea/go-exif/v3/common"
|
||||
)
|
||||
|
||||
// ErrUnsupportedFormat means the given image format is not supported.
|
||||
@@ -152,6 +155,17 @@ func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, o
|
||||
option(&config)
|
||||
}
|
||||
|
||||
if config.quality == QualityLow && format == FormatJpeg {
|
||||
thm, newWrappedReader, errThm := getEmbeddedThumbnail(wrappedReader)
|
||||
wrappedReader = newWrappedReader
|
||||
if errThm == nil {
|
||||
_, err = out.Write(thm)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img, err := imaging.Decode(wrappedReader, imaging.AutoOrientation(true))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -160,6 +174,8 @@ func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, o
|
||||
switch config.resizeMode {
|
||||
case ResizeModeFill:
|
||||
img = imaging.Fill(img, width, height, imaging.Center, config.quality.resampleFilter())
|
||||
case ResizeModeFit:
|
||||
fallthrough //nolint:gocritic
|
||||
default:
|
||||
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
|
||||
}
|
||||
@@ -183,3 +199,46 @@ func (s *Service) detectFormat(in io.Reader) (Format, io.Reader, error) {
|
||||
|
||||
return format, io.MultiReader(buf, in), nil
|
||||
}
|
||||
|
||||
func getEmbeddedThumbnail(in io.Reader) ([]byte, io.Reader, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
r := io.TeeReader(in, buf)
|
||||
wrappedReader := io.MultiReader(buf, in)
|
||||
|
||||
offset := 0
|
||||
offsets := []int{12, 30}
|
||||
head := make([]byte, 0xffff) //nolint:gomnd
|
||||
|
||||
_, err := r.Read(head)
|
||||
if err != nil {
|
||||
return nil, wrappedReader, err
|
||||
}
|
||||
|
||||
for _, offset = range offsets {
|
||||
if _, err = exif.ParseExifHeader(head[offset:]); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, wrappedReader, err
|
||||
}
|
||||
|
||||
im, err := exifcommon.NewIfdMappingWithStandard()
|
||||
if err != nil {
|
||||
return nil, wrappedReader, err
|
||||
}
|
||||
|
||||
_, index, err := exif.Collect(im, exif.NewTagIndex(), head[offset:])
|
||||
if err != nil {
|
||||
return nil, wrappedReader, err
|
||||
}
|
||||
|
||||
ifd := index.RootIfd.NextIfd()
|
||||
if ifd == nil {
|
||||
return nil, wrappedReader, exif.ErrNoThumbnail
|
||||
}
|
||||
|
||||
thm, err := ifd.Thumbnail()
|
||||
return thm, wrappedReader, err
|
||||
}
|
||||
|
||||
@@ -216,6 +216,46 @@ func TestService_Resize(t *testing.T) {
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"get thumbnail from file with APP0 JFIF": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return openFile(t, "testdata/gray-sample.jpg")
|
||||
},
|
||||
matcher: sizeMatcher(125, 128),
|
||||
},
|
||||
"get thumbnail from file without APP0 JFIF": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return openFile(t, "testdata/20130612_142406.jpg")
|
||||
},
|
||||
matcher: sizeMatcher(320, 240),
|
||||
},
|
||||
"resize from file without IFD1 thumbnail": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return openFile(t, "testdata/IMG_2578.JPG")
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"resize for higher quality levels": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityMedium)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return openFile(t, "testdata/gray-sample.jpg")
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"broken file": {
|
||||
options: []Option{WithMode(ResizeModeFit)},
|
||||
width: 100,
|
||||
@@ -348,6 +388,15 @@ func newGrayBmp(t *testing.T, width, height int) afero.File {
|
||||
return file
|
||||
}
|
||||
|
||||
func openFile(t *testing.T, name string) afero.File {
|
||||
appfs := afero.NewOsFs()
|
||||
file, err := appfs.Open(name)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func TestService_FormatFromExtension(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
ext string
|
||||
|
||||
BIN
img/testdata/20130612_142406.jpg
vendored
Normal file
BIN
img/testdata/20130612_142406.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
BIN
img/testdata/IMG_2578.JPG
vendored
Normal file
BIN
img/testdata/IMG_2578.JPG
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
img/testdata/gray-sample.jpg
vendored
Normal file
BIN
img/testdata/gray-sample.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -6,4 +6,5 @@ type Branding struct {
|
||||
DisableExternal bool `json:"disableExternal"`
|
||||
Files string `json:"files"`
|
||||
Theme string `json:"theme"`
|
||||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ type UserDefaults struct {
|
||||
Perm users.Permissions `json:"perm"`
|
||||
Commands []string `json:"commands"`
|
||||
HideDotfiles bool `json:"hideDotfiles"`
|
||||
DateFormat bool `json:"dateFormat"`
|
||||
}
|
||||
|
||||
// Apply applies the default options to a user.
|
||||
@@ -28,4 +29,5 @@ func (d *UserDefaults) Apply(u *users.User) {
|
||||
u.Sorting = d.Sorting
|
||||
u.Commands = d.Commands
|
||||
u.HideDotfiles = d.HideDotfiles
|
||||
u.DateFormat = d.DateFormat
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ func (s *Server) Clean() {
|
||||
s.BaseURL = strings.TrimSuffix(s.BaseURL, "/")
|
||||
}
|
||||
|
||||
// GenerateKey generates a key of 256 bits.
|
||||
// GenerateKey generates a key of 512 bits.
|
||||
func GenerateKey() ([]byte, error) {
|
||||
b := make([]byte, 64)
|
||||
b := make([]byte, 64) //nolint:gomnd
|
||||
_, err := rand.Read(b)
|
||||
// Note that err == nil only if we read len(b) bytes.
|
||||
if err != nil {
|
||||
|
||||
@@ -17,7 +17,7 @@ func NewStorage(db *storm.DB) (*storage.Storage, error) {
|
||||
settingsStore := settings.NewStorage(settingsBackend{db: db})
|
||||
authStore := auth.NewStorage(authBackend{db: db}, userStore)
|
||||
|
||||
err := save(db, "version", 2)
|
||||
err := save(db, "version", 2) //nolint:gomnd
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ type User struct {
|
||||
Fs afero.Fs `json:"-" yaml:"-"`
|
||||
Rules []rules.Rule `json:"rules"`
|
||||
HideDotfiles bool `json:"hideDotfiles"`
|
||||
DateFormat bool `json:"dateFormat"`
|
||||
}
|
||||
|
||||
// GetRules implements rules.Provider.
|
||||
|
||||
Reference in New Issue
Block a user