Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
0d179eca4d | ||
|
|
dacd511d24 | ||
|
|
c44b37c50c | ||
|
|
a721dc1f31 | ||
|
|
d2e6d23741 | ||
|
|
5f4a0317ab | ||
|
|
22f4be8f54 | ||
|
|
eeadc532fe | ||
|
|
93a35ad251 | ||
|
|
99787287bb | ||
|
|
bdd523190e | ||
|
|
4c1dd5c097 |
@@ -1,2 +1,3 @@
|
||||
*
|
||||
!.docker.json
|
||||
!filebrowser
|
||||
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!
|
||||
-->
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ _old
|
||||
rice-box.go
|
||||
.idea/
|
||||
filebrowser
|
||||
filebrowser.exe
|
||||
|
||||
.DS_Store
|
||||
node_modules
|
||||
|
||||
@@ -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:
|
||||
|
||||
83
CHANGELOG.md
83
CHANGELOG.md
@@ -2,6 +2,89 @@
|
||||
|
||||
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.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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add health check handler ([a721dc1](https://github.com/filebrowser/filebrowser/commit/a721dc1f314732e60d331a1a7da97d06e0e8b613))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* hide dotfile error on share ([5f4a031](https://github.com/filebrowser/filebrowser/commit/5f4a0317ab5685fe4a558df74e604c12e04a1c10))
|
||||
* prefix handling on http router ([93a35ad](https://github.com/filebrowser/filebrowser/commit/93a35ad2516accdcb9735db509550979d01de2c3))
|
||||
* qr code url on share ([22f4be8](https://github.com/filebrowser/filebrowser/commit/22f4be8f54162b7cf494177705ffb8b09117bd01))
|
||||
* text file detection on editor ([eeadc53](https://github.com/filebrowser/filebrowser/commit/eeadc532fe6057969b3c1a4726f236851b154cfa))
|
||||
|
||||
## [2.13.0](https://github.com/filebrowser/filebrowser/compare/v2.12.1...v2.13.0) (2021-03-14)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
FROM alpine:latest
|
||||
RUN apk --update add ca-certificates
|
||||
RUN apk --update add mailcap
|
||||
RUN apk --update add ca-certificates \
|
||||
mailcap \
|
||||
curl
|
||||
|
||||
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
|
||||
CMD curl -f http://localhost/health || exit 1
|
||||
|
||||
VOLUME /srv
|
||||
EXPOSE 80
|
||||
|
||||
12
Makefile
12
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
|
||||
@@ -49,7 +49,7 @@ build-frontend: | ; $(info $(M) building frontend…)
|
||||
## build-backend: Build backend
|
||||
.PHONY: build-backend
|
||||
build-backend: | ; $(info $(M) building backend…)
|
||||
$Q $(GO) build -ldflags '$(LDFLAGS)' -o filebrowser
|
||||
$Q $(GO) build -ldflags '$(LDFLAGS)' -o .
|
||||
|
||||
## test: Run all tests
|
||||
.PHONY: test
|
||||
@@ -61,7 +61,7 @@ test-frontend: | ; $(info $(M) running frontend tests…)
|
||||
|
||||
## test-backend: Run backend tests
|
||||
.PHONY: test-backend
|
||||
test-backend: | $(RICE) ; $(info $(M) running backend tests…)
|
||||
test-backend: | ; $(info $(M) running backend tests…)
|
||||
$Q $(GO) test -v ./...
|
||||
|
||||
## lint: Lint
|
||||
|
||||
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.
|
||||
@@ -121,7 +121,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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
101
files/file.go
101
files/file.go
@@ -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
|
||||
@@ -148,12 +197,15 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
|
||||
// of files couldn't be opened: we'd have immediately
|
||||
// a 500 even though it doesn't matter. So we just log it.
|
||||
|
||||
var buffer []byte
|
||||
|
||||
mimetype := mime.TypeByExtension(i.Extension)
|
||||
if mimetype == "" && readHeader {
|
||||
|
||||
var buffer []byte
|
||||
if readHeader {
|
||||
buffer = i.readFirstBytes()
|
||||
mimetype = http.DetectContentType(buffer)
|
||||
|
||||
if mimetype == "" {
|
||||
mimetype = http.DetectContentType(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -167,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 {
|
||||
@@ -200,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)
|
||||
@@ -248,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)
|
||||
@@ -264,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
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !dev
|
||||
|
||||
package frontend
|
||||
|
||||
import "embed"
|
||||
|
||||
14
frontend/assets_dev.go
Normal file
14
frontend/assets_dev.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build dev
|
||||
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
var assets fs.FS = os.DirFS("frontend")
|
||||
|
||||
func Assets() fs.FS {
|
||||
return assets
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
||||
presets: ["@vue/app"],
|
||||
};
|
||||
|
||||
25759
frontend/package-lock.json
generated
25759
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,15 @@
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean",
|
||||
"watch": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --watch --no-clean",
|
||||
"lint": "vue-cli-service lint --fix"
|
||||
"lint": "npx vue-cli-service lint --no-fix",
|
||||
"fix": "npx vue-cli-service lint",
|
||||
"watch": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --watch --no-clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"ace-builds": "^1.4.7",
|
||||
"clipboard": "^2.0.4",
|
||||
"core-js": "^3.9.1",
|
||||
"css-vars-ponyfill": "^2.4.3",
|
||||
"js-base64": "^2.5.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
@@ -25,15 +28,20 @@
|
||||
"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",
|
||||
"@vue/cli-plugin-eslint": "^4.1.1",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "^4.1.2",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"compression-webpack-plugin": "^6.0.3",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.1.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"prettier": "^2.2.1",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"eslintConfig": {
|
||||
@@ -43,7 +51,8 @@
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
"eslint:recommended",
|
||||
"@vue/prettier"
|
||||
],
|
||||
"rules": {},
|
||||
"parserOptions": {
|
||||
@@ -58,6 +67,6 @@
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
"not ie < 11"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<!-- 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 = {
|
||||
@@ -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,19 +5,22 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// eslint-disable-next-line no-undef
|
||||
__webpack_public_path__ = window.FileBrowser.StaticURL + "/";
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
mounted () {
|
||||
const loading = document.getElementById('loading')
|
||||
loading.classList.add('done')
|
||||
name: "app",
|
||||
mounted() {
|
||||
const loading = document.getElementById("loading");
|
||||
loading.classList.add("done");
|
||||
|
||||
setTimeout(function () {
|
||||
loading.parentNode.removeChild(loading)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
loading.parentNode.removeChild(loading);
|
||||
}, 200);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import './css/styles.css';
|
||||
@import "./css/styles.css";
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { removePrefix } from './utils'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import store from '@/store'
|
||||
import { removePrefix } from "./utils";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import store from "@/store";
|
||||
|
||||
const ssl = (window.location.protocol === 'https:')
|
||||
const protocol = (ssl ? 'wss:' : 'ws:')
|
||||
const ssl = window.location.protocol === "https:";
|
||||
const protocol = ssl ? "wss:" : "ws:";
|
||||
|
||||
export default function command(url, command, onmessage, onclose) {
|
||||
url = removePrefix(url)
|
||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}`
|
||||
url = removePrefix(url);
|
||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}`;
|
||||
|
||||
let conn = new window.WebSocket(url)
|
||||
conn.onopen = () => conn.send(command)
|
||||
conn.onmessage = onmessage
|
||||
conn.onclose = onclose
|
||||
let conn = new window.WebSocket(url);
|
||||
conn.onopen = () => conn.send(command);
|
||||
conn.onmessage = onmessage;
|
||||
conn.onclose = onclose;
|
||||
}
|
||||
|
||||
@@ -1,147 +1,156 @@
|
||||
import { fetchURL, removePrefix } from './utils'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import store from '@/store'
|
||||
import { fetchURL, removePrefix } from "./utils";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import store from "@/store";
|
||||
|
||||
export async function fetch (url) {
|
||||
url = removePrefix(url)
|
||||
export async function fetch(url) {
|
||||
url = removePrefix(url);
|
||||
|
||||
const res = await fetchURL(`/api/resources${url}`, {})
|
||||
const res = await fetchURL(`/api/resources${url}`, {});
|
||||
|
||||
if (res.status === 200) {
|
||||
let data = await res.json()
|
||||
data.url = `/files${url}`
|
||||
let data = await res.json();
|
||||
data.url = `/files${url}`;
|
||||
|
||||
if (data.isDir) {
|
||||
if (!data.url.endsWith('/')) data.url += '/'
|
||||
if (!data.url.endsWith("/")) data.url += "/";
|
||||
data.items = data.items.map((item, index) => {
|
||||
item.index = index
|
||||
item.url = `${data.url}${encodeURIComponent(item.name)}`
|
||||
item.index = index;
|
||||
item.url = `${data.url}${encodeURIComponent(item.name)}`;
|
||||
|
||||
if (item.isDir) {
|
||||
item.url += '/'
|
||||
item.url += "/";
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
return data
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(res.status)
|
||||
throw new Error(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
async function resourceAction (url, method, content) {
|
||||
url = removePrefix(url)
|
||||
async function resourceAction(url, method, content) {
|
||||
url = removePrefix(url);
|
||||
|
||||
let opts = { method }
|
||||
let opts = { method };
|
||||
|
||||
if (content) {
|
||||
opts.body = content
|
||||
opts.body = content;
|
||||
}
|
||||
|
||||
const res = await fetchURL(`/api/resources${url}`, opts)
|
||||
const res = await fetchURL(`/api/resources${url}`, opts);
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(await res.text())
|
||||
throw new Error(await res.text());
|
||||
} else {
|
||||
return res
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export async function remove (url) {
|
||||
return resourceAction(url, 'DELETE')
|
||||
export async function remove(url) {
|
||||
return resourceAction(url, "DELETE");
|
||||
}
|
||||
|
||||
export async function put (url, content = '') {
|
||||
return resourceAction(url, 'PUT', content)
|
||||
export async function put(url, content = "") {
|
||||
return resourceAction(url, "PUT", content);
|
||||
}
|
||||
|
||||
export function download (format, ...files) {
|
||||
let url = `${baseURL}/api/raw`
|
||||
export function download(format, ...files) {
|
||||
let url = `${baseURL}/api/raw`;
|
||||
|
||||
if (files.length === 1) {
|
||||
url += removePrefix(files[0]) + '?'
|
||||
url += removePrefix(files[0]) + "?";
|
||||
} else {
|
||||
let arg = ''
|
||||
let arg = "";
|
||||
|
||||
for (let file of files) {
|
||||
arg += removePrefix(file) + ','
|
||||
arg += removePrefix(file) + ",";
|
||||
}
|
||||
|
||||
arg = arg.substring(0, arg.length - 1)
|
||||
arg = encodeURIComponent(arg)
|
||||
url += `/?files=${arg}&`
|
||||
arg = arg.substring(0, arg.length - 1);
|
||||
arg = encodeURIComponent(arg);
|
||||
url += `/?files=${arg}&`;
|
||||
}
|
||||
|
||||
if (format) {
|
||||
url += `algo=${format}&`
|
||||
url += `algo=${format}&`;
|
||||
}
|
||||
|
||||
if (store.state.jwt){
|
||||
url += `auth=${store.state.jwt}&`
|
||||
if (store.state.jwt) {
|
||||
url += `auth=${store.state.jwt}&`;
|
||||
}
|
||||
|
||||
window.open(url)
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
export async function post (url, content = '', overwrite = false, onupload) {
|
||||
url = removePrefix(url)
|
||||
export async function post(url, content = "", overwrite = false, onupload) {
|
||||
url = removePrefix(url);
|
||||
|
||||
let bufferContent
|
||||
if (content instanceof Blob && !['http:', 'https:'].includes(window.location.protocol)) {
|
||||
bufferContent = await new Response(content).arrayBuffer()
|
||||
let bufferContent;
|
||||
if (
|
||||
content instanceof Blob &&
|
||||
!["http:", "https:"].includes(window.location.protocol)
|
||||
) {
|
||||
bufferContent = await new Response(content).arrayBuffer();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new XMLHttpRequest()
|
||||
request.open('POST', `${baseURL}/api/resources${url}?override=${overwrite}`, true)
|
||||
request.setRequestHeader('X-Auth', store.state.jwt)
|
||||
let request = new XMLHttpRequest();
|
||||
request.open(
|
||||
"POST",
|
||||
`${baseURL}/api/resources${url}?override=${overwrite}`,
|
||||
true
|
||||
);
|
||||
request.setRequestHeader("X-Auth", store.state.jwt);
|
||||
|
||||
if (typeof onupload === 'function') {
|
||||
request.upload.onprogress = onupload
|
||||
if (typeof onupload === "function") {
|
||||
request.upload.onprogress = onupload;
|
||||
}
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 200) {
|
||||
resolve(request.responseText)
|
||||
resolve(request.responseText);
|
||||
} else if (request.status === 409) {
|
||||
reject(request.status)
|
||||
reject(request.status);
|
||||
} else {
|
||||
reject(request.responseText)
|
||||
reject(request.responseText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = (error) => {
|
||||
reject(error)
|
||||
}
|
||||
reject(error);
|
||||
};
|
||||
|
||||
request.send(bufferContent || content)
|
||||
})
|
||||
request.send(bufferContent || content);
|
||||
});
|
||||
}
|
||||
|
||||
function moveCopy (items, copy = false, overwrite = false, rename = false) {
|
||||
let promises = []
|
||||
function moveCopy(items, copy = false, overwrite = false, rename = false) {
|
||||
let promises = [];
|
||||
|
||||
for (let item of items) {
|
||||
const from = item.from
|
||||
const to = encodeURIComponent(removePrefix(item.to))
|
||||
const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}&override=${overwrite}&rename=${rename}`
|
||||
promises.push(resourceAction(url, 'PATCH'))
|
||||
const from = item.from;
|
||||
const to = encodeURIComponent(removePrefix(item.to));
|
||||
const url = `${from}?action=${
|
||||
copy ? "copy" : "rename"
|
||||
}&destination=${to}&override=${overwrite}&rename=${rename}`;
|
||||
promises.push(resourceAction(url, "PATCH"));
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
export function move (items, overwrite = false, rename = false) {
|
||||
return moveCopy(items, false, overwrite, rename)
|
||||
export function move(items, overwrite = false, rename = false) {
|
||||
return moveCopy(items, false, overwrite, rename);
|
||||
}
|
||||
|
||||
export function copy (items, overwrite = false, rename = false) {
|
||||
return moveCopy(items, true, overwrite, rename)
|
||||
export function copy(items, overwrite = false, rename = false) {
|
||||
return moveCopy(items, true, overwrite, rename);
|
||||
}
|
||||
|
||||
export async function checksum (url, algo) {
|
||||
const data = await resourceAction(`${url}?checksum=${algo}`, 'GET')
|
||||
return (await data.json()).checksums[algo]
|
||||
export async function checksum(url, algo) {
|
||||
const data = await resourceAction(`${url}?checksum=${algo}`, "GET");
|
||||
return (await data.json()).checksums[algo];
|
||||
}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import * as files from './files'
|
||||
import * as share from './share'
|
||||
import * as users from './users'
|
||||
import * as settings from './settings'
|
||||
import * as pub from './pub'
|
||||
import search from './search'
|
||||
import commands from './commands'
|
||||
import * as files from "./files";
|
||||
import * as share from "./share";
|
||||
import * as users from "./users";
|
||||
import * as settings from "./settings";
|
||||
import * as pub from "./pub";
|
||||
import search from "./search";
|
||||
import commands from "./commands";
|
||||
|
||||
export {
|
||||
files,
|
||||
share,
|
||||
users,
|
||||
settings,
|
||||
pub,
|
||||
commands,
|
||||
search
|
||||
}
|
||||
export { files, share, users, settings, pub, commands, search };
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
import { fetchURL, removePrefix } from './utils'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import { fetchURL, removePrefix } from "./utils";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
|
||||
export async function fetch (url, password = "") {
|
||||
url = removePrefix(url)
|
||||
export async function fetch(url, password = "") {
|
||||
url = removePrefix(url);
|
||||
|
||||
const res = await fetchURL(`/api/public/share${url}`, {
|
||||
headers: {'X-SHARE-PASSWORD': password},
|
||||
})
|
||||
headers: { "X-SHARE-PASSWORD": password },
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
let data = await res.json()
|
||||
data.url = `/share${url}`
|
||||
let data = await res.json();
|
||||
data.url = `/share${url}`;
|
||||
|
||||
if (data.isDir) {
|
||||
if (!data.url.endsWith('/')) data.url += '/'
|
||||
if (!data.url.endsWith("/")) data.url += "/";
|
||||
data.items = data.items.map((item, index) => {
|
||||
item.index = index
|
||||
item.url = `${data.url}${encodeURIComponent(item.name)}`
|
||||
item.index = index;
|
||||
item.url = `${data.url}${encodeURIComponent(item.name)}`;
|
||||
|
||||
if (item.isDir) {
|
||||
item.url += '/'
|
||||
item.url += "/";
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
return data
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(res.status)
|
||||
throw new Error(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
export function download(format, hash, token, ...files) {
|
||||
let url = `${baseURL}/api/public/dl/${hash}`
|
||||
let url = `${baseURL}/api/public/dl/${hash}`;
|
||||
|
||||
if (files.length === 1) {
|
||||
url += encodeURIComponent(files[0]) + '?'
|
||||
url += encodeURIComponent(files[0]) + "?";
|
||||
} else {
|
||||
let arg = ''
|
||||
let arg = "";
|
||||
|
||||
for (let file of files) {
|
||||
arg += encodeURIComponent(file) + ','
|
||||
arg += encodeURIComponent(file) + ",";
|
||||
}
|
||||
|
||||
arg = arg.substring(0, arg.length - 1)
|
||||
arg = encodeURIComponent(arg)
|
||||
url += `/?files=${arg}&`
|
||||
arg = arg.substring(0, arg.length - 1);
|
||||
arg = encodeURIComponent(arg);
|
||||
url += `/?files=${arg}&`;
|
||||
}
|
||||
|
||||
if (format) {
|
||||
url += `algo=${format}&`
|
||||
url += `algo=${format}&`;
|
||||
}
|
||||
|
||||
if (token) {
|
||||
url += `token=${token}&`
|
||||
url += `token=${token}&`;
|
||||
}
|
||||
|
||||
window.open(url)
|
||||
}
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { fetchURL, removePrefix } from './utils'
|
||||
import url from '../utils/url'
|
||||
import { fetchURL, removePrefix } from "./utils";
|
||||
import url from "../utils/url";
|
||||
|
||||
export default async function search (base, query) {
|
||||
base = removePrefix(base)
|
||||
query = encodeURIComponent(query)
|
||||
export default async function search(base, query) {
|
||||
base = removePrefix(base);
|
||||
query = encodeURIComponent(query);
|
||||
|
||||
if (!base.endsWith('/')) {
|
||||
base += '/'
|
||||
if (!base.endsWith("/")) {
|
||||
base += "/";
|
||||
}
|
||||
|
||||
let res = await fetchURL(`/api/search${base}?query=${query}`, {})
|
||||
let res = await fetchURL(`/api/search${base}?query=${query}`, {});
|
||||
|
||||
if (res.status === 200) {
|
||||
let data = await res.json()
|
||||
let data = await res.json();
|
||||
|
||||
data = data.map((item) => {
|
||||
item.url = `/files${base}` + url.encodePath(item.path)
|
||||
item.url = `/files${base}` + url.encodePath(item.path);
|
||||
|
||||
if (item.dir) {
|
||||
item.url += '/'
|
||||
item.url += "/";
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
return item;
|
||||
});
|
||||
|
||||
return data
|
||||
return data;
|
||||
} else {
|
||||
throw Error(res.status)
|
||||
throw Error(res.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { fetchURL, fetchJSON } from './utils'
|
||||
import { fetchURL, fetchJSON } from "./utils";
|
||||
|
||||
export function get () {
|
||||
return fetchJSON(`/api/settings`, {})
|
||||
export function get() {
|
||||
return fetchJSON(`/api/settings`, {});
|
||||
}
|
||||
|
||||
export async function update (settings) {
|
||||
export async function update(settings) {
|
||||
const res = await fetchURL(`/api/settings`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(settings)
|
||||
})
|
||||
method: "PUT",
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(res.status)
|
||||
throw new Error(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import { fetchURL, fetchJSON, removePrefix } from './utils'
|
||||
import { fetchURL, fetchJSON, removePrefix } from "./utils";
|
||||
|
||||
export async function list() {
|
||||
return fetchJSON('/api/shares')
|
||||
return fetchJSON("/api/shares");
|
||||
}
|
||||
|
||||
export async function get(url) {
|
||||
url = removePrefix(url)
|
||||
return fetchJSON(`/api/share${url}`)
|
||||
url = removePrefix(url);
|
||||
return fetchJSON(`/api/share${url}`);
|
||||
}
|
||||
|
||||
export async function remove(hash) {
|
||||
const res = await fetchURL(`/api/share/${hash}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(res.status)
|
||||
throw new Error(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
export async function create(url, password = '', expires = '', unit = 'hours') {
|
||||
url = removePrefix(url)
|
||||
url = `/api/share${url}`
|
||||
if (expires !== '') {
|
||||
url += `?expires=${expires}&unit=${unit}`
|
||||
export async function create(url, password = "", expires = "", unit = "hours") {
|
||||
url = removePrefix(url);
|
||||
url = `/api/share${url}`;
|
||||
if (expires !== "") {
|
||||
url += `?expires=${expires}&unit=${unit}`;
|
||||
}
|
||||
let body = '{}';
|
||||
if (password != '' || expires !== '' || unit !== 'hours') {
|
||||
body = JSON.stringify({password: password, expires: expires, unit: unit})
|
||||
let body = "{}";
|
||||
if (password != "" || expires !== "" || unit !== "hours") {
|
||||
body = JSON.stringify({ password: password, expires: expires, unit: unit });
|
||||
}
|
||||
return fetchJSON(url, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
body: body,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,52 +1,51 @@
|
||||
import { fetchURL, fetchJSON } from './utils'
|
||||
import { fetchURL, fetchJSON } from "./utils";
|
||||
|
||||
export async function getAll () {
|
||||
return fetchJSON(`/api/users`, {})
|
||||
export async function getAll() {
|
||||
return fetchJSON(`/api/users`, {});
|
||||
}
|
||||
|
||||
export async function get (id) {
|
||||
return fetchJSON(`/api/users/${id}`, {})
|
||||
export async function get(id) {
|
||||
return fetchJSON(`/api/users/${id}`, {});
|
||||
}
|
||||
|
||||
export async function create (user) {
|
||||
export async function create(user) {
|
||||
const res = await fetchURL(`/api/users`, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
what: 'user',
|
||||
what: "user",
|
||||
which: [],
|
||||
data: user
|
||||
})
|
||||
})
|
||||
data: user,
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
return res.headers.get('Location')
|
||||
return res.headers.get("Location");
|
||||
} else {
|
||||
throw new Error(res.status)
|
||||
throw new Error(res.status);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function update (user, which = ['all']) {
|
||||
export async function update(user, which = ["all"]) {
|
||||
const res = await fetchURL(`/api/users/${user.id}`, {
|
||||
method: 'PUT',
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
what: 'user',
|
||||
what: "user",
|
||||
which: which,
|
||||
data: user
|
||||
})
|
||||
})
|
||||
data: user,
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(res.status)
|
||||
throw new Error(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
export async function remove (id) {
|
||||
export async function remove(id) {
|
||||
const res = await fetchURL(`/api/users/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(res.status)
|
||||
throw new Error(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,47 @@
|
||||
import store from '@/store'
|
||||
import { renew } from '@/utils/auth'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import store from "@/store";
|
||||
import { renew } from "@/utils/auth";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
|
||||
export async function fetchURL (url, opts) {
|
||||
opts = opts || {}
|
||||
opts.headers = opts.headers || {}
|
||||
export async function fetchURL(url, opts) {
|
||||
opts = opts || {};
|
||||
opts.headers = opts.headers || {};
|
||||
|
||||
let { headers, ...rest } = opts
|
||||
let { headers, ...rest } = opts;
|
||||
|
||||
const res = await fetch(`${baseURL}${url}`, {
|
||||
headers: {
|
||||
'X-Auth': store.state.jwt,
|
||||
...headers
|
||||
},
|
||||
...rest
|
||||
})
|
||||
|
||||
if (res.headers.get('X-Renew-Token') === 'true') {
|
||||
await renew(store.state.jwt)
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(`${baseURL}${url}`, {
|
||||
headers: {
|
||||
"X-Auth": store.state.jwt,
|
||||
...headers,
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
} catch (error) {
|
||||
return { status: 0 };
|
||||
}
|
||||
|
||||
return res
|
||||
if (res.headers.get("X-Renew-Token") === "true") {
|
||||
await renew(store.state.jwt);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function fetchJSON (url, opts) {
|
||||
const res = await fetchURL(url, opts)
|
||||
export async function fetchJSON(url, opts) {
|
||||
const res = await fetchURL(url, opts);
|
||||
|
||||
if (res.status === 200) {
|
||||
return res.json()
|
||||
return res.json();
|
||||
} else {
|
||||
throw new Error(res.status)
|
||||
throw new Error(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
export function removePrefix (url) {
|
||||
url = url.split('/').splice(2).join('/')
|
||||
export function removePrefix(url) {
|
||||
url = url.split("/").splice(2).join("/");
|
||||
|
||||
if (url === '') url = '/'
|
||||
if (url[0] !== '/') url = '/' + url
|
||||
return url
|
||||
if (url === "") url = "/";
|
||||
if (url[0] !== "/") url = "/" + url;
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
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,11 +1,18 @@
|
||||
<template>
|
||||
<div class="breadcrumbs">
|
||||
<component :is="element" :to="base || ''" :aria-label="$t('files.home')" :title="$t('files.home')">
|
||||
<component
|
||||
:is="element"
|
||||
:to="base || ''"
|
||||
:aria-label="$t('files.home')"
|
||||
:title="$t('files.home')"
|
||||
>
|
||||
<i class="material-icons">home</i>
|
||||
</component>
|
||||
|
||||
<span v-for="(link, index) in items" :key="index">
|
||||
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
||||
<span class="chevron"
|
||||
><i class="material-icons">keyboard_arrow_right</i></span
|
||||
>
|
||||
<component :is="element" :to="link.url">{{ link.name }}</component>
|
||||
</span>
|
||||
</div>
|
||||
@@ -13,55 +20,56 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'breadcrumbs',
|
||||
props: [
|
||||
'base',
|
||||
'noLink'
|
||||
],
|
||||
name: "breadcrumbs",
|
||||
props: ["base", "noLink"],
|
||||
computed: {
|
||||
items () {
|
||||
const relativePath = this.$route.path.replace(this.base, '')
|
||||
let parts = relativePath.split('/')
|
||||
items() {
|
||||
const relativePath = this.$route.path.replace(this.base, "");
|
||||
let parts = relativePath.split("/");
|
||||
|
||||
if (parts[0] === '') {
|
||||
parts.shift()
|
||||
if (parts[0] === "") {
|
||||
parts.shift();
|
||||
}
|
||||
|
||||
if (parts[parts.length - 1] === '') {
|
||||
parts.pop()
|
||||
if (parts[parts.length - 1] === "") {
|
||||
parts.pop();
|
||||
}
|
||||
|
||||
let breadcrumbs = []
|
||||
let breadcrumbs = [];
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (i === 0) {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: this.base + '/' + parts[i] + '/' })
|
||||
} else {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' })
|
||||
breadcrumbs.push({
|
||||
name: decodeURIComponent(parts[i]),
|
||||
url: this.base + "/" + parts[i] + "/",
|
||||
});
|
||||
} else {
|
||||
breadcrumbs.push({
|
||||
name: decodeURIComponent(parts[i]),
|
||||
url: breadcrumbs[i - 1].url + parts[i] + "/",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (breadcrumbs.length > 3) {
|
||||
while (breadcrumbs.length !== 4) {
|
||||
breadcrumbs.shift()
|
||||
breadcrumbs.shift();
|
||||
}
|
||||
|
||||
breadcrumbs[0].name = '...'
|
||||
breadcrumbs[0].name = "...";
|
||||
}
|
||||
|
||||
return breadcrumbs
|
||||
return breadcrumbs;
|
||||
},
|
||||
element () {
|
||||
element() {
|
||||
if (this.noLink !== undefined) {
|
||||
return 'span'
|
||||
return "span";
|
||||
}
|
||||
|
||||
return 'router-link'
|
||||
}
|
||||
}
|
||||
}
|
||||
return "router-link";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
|
||||
<div id="search" @click="open" v-bind:class="{ active, ongoing }">
|
||||
<div id="input">
|
||||
<button
|
||||
v-if="active"
|
||||
@@ -20,7 +20,7 @@
|
||||
v-model.trim="value"
|
||||
:aria-label="$t('search.search')"
|
||||
:placeholder="$t('search.search')"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="result" ref="result">
|
||||
@@ -30,25 +30,25 @@
|
||||
|
||||
<template v-if="value.length === 0">
|
||||
<div class="boxes">
|
||||
<h3>{{ $t('search.types') }}</h3>
|
||||
<h3>{{ $t("search.types") }}</h3>
|
||||
<div>
|
||||
<div
|
||||
tabindex="0"
|
||||
v-for="(v,k) in boxes"
|
||||
v-for="(v, k) in boxes"
|
||||
:key="k"
|
||||
role="button"
|
||||
@click="init('type:'+k)"
|
||||
:aria-label="$t('search.'+v.label)"
|
||||
@click="init('type:' + k)"
|
||||
:aria-label="$t('search.' + v.label)"
|
||||
>
|
||||
<i class="material-icons">{{v.icon}}</i>
|
||||
<p>{{ $t('search.'+v.label) }}</p>
|
||||
<i class="material-icons">{{ v.icon }}</i>
|
||||
<p>{{ $t("search." + v.label) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<ul v-show="results.length > 0">
|
||||
<li v-for="(s,k) in filteredResults" :key="k">
|
||||
<li v-for="(s, k) in filteredResults" :key="k">
|
||||
<router-link @click.native="close" :to="s.url">
|
||||
<i v-if="s.dir" class="material-icons">folder</i>
|
||||
<i v-else class="material-icons">insert_drive_file</i>
|
||||
@@ -65,20 +65,20 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters, mapMutations } from "vuex"
|
||||
import url from "@/utils/url"
|
||||
import { search } from "@/api"
|
||||
import { mapState, mapGetters, mapMutations } from "vuex";
|
||||
import url from "@/utils/url";
|
||||
import { search } from "@/api";
|
||||
|
||||
var boxes = {
|
||||
image: { label: "images", icon: "insert_photo" },
|
||||
audio: { label: "music", icon: "volume_up" },
|
||||
video: { label: "video", icon: "movie" },
|
||||
pdf: { label: "pdf", icon: "picture_as_pdf" }
|
||||
}
|
||||
pdf: { label: "pdf", icon: "picture_as_pdf" },
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "search",
|
||||
data: function() {
|
||||
data: function () {
|
||||
return {
|
||||
value: "",
|
||||
active: false,
|
||||
@@ -86,111 +86,116 @@ export default {
|
||||
results: [],
|
||||
reload: false,
|
||||
resultsCount: 50,
|
||||
scrollable: null
|
||||
}
|
||||
scrollable: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
show (val, old) {
|
||||
this.active = val === "search"
|
||||
show(val, old) {
|
||||
this.active = val === "search";
|
||||
|
||||
if (old === "search" && !this.active) {
|
||||
if (this.reload) {
|
||||
this.setReload(true)
|
||||
this.setReload(true);
|
||||
}
|
||||
|
||||
document.body.style.overflow = "auto"
|
||||
this.reset()
|
||||
this.value = ''
|
||||
this.active = false
|
||||
this.$refs.input.blur()
|
||||
document.body.style.overflow = "auto";
|
||||
this.reset();
|
||||
this.value = "";
|
||||
this.active = false;
|
||||
this.$refs.input.blur();
|
||||
} else if (this.active) {
|
||||
this.reload = false
|
||||
this.$refs.input.focus()
|
||||
document.body.style.overflow = "hidden"
|
||||
this.reload = false;
|
||||
this.$refs.input.focus();
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
},
|
||||
value () {
|
||||
value() {
|
||||
if (this.results.length) {
|
||||
this.reset()
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(["user", "show"]),
|
||||
...mapGetters(["isListing"]),
|
||||
boxes() {
|
||||
return boxes
|
||||
return boxes;
|
||||
},
|
||||
isEmpty() {
|
||||
return this.results.length === 0
|
||||
return this.results.length === 0;
|
||||
},
|
||||
text() {
|
||||
if (this.ongoing) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
|
||||
return this.value === '' ? this.$t("search.typeToSearch") : this.$t("search.pressToSearch")
|
||||
return this.value === ""
|
||||
? this.$t("search.typeToSearch")
|
||||
: this.$t("search.pressToSearch");
|
||||
},
|
||||
filteredResults() {
|
||||
return this.results.slice(0, this.resultsCount);
|
||||
},
|
||||
filteredResults () {
|
||||
return this.results.slice(0, this.resultsCount)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.result.addEventListener('scroll', event => {
|
||||
if (event.target.offsetHeight + event.target.scrollTop >= event.target.scrollHeight - 100) {
|
||||
this.resultsCount += 50
|
||||
this.$refs.result.addEventListener("scroll", (event) => {
|
||||
if (
|
||||
event.target.offsetHeight + event.target.scrollTop >=
|
||||
event.target.scrollHeight - 100
|
||||
) {
|
||||
this.resultsCount += 50;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["showHover", "closeHovers", "setReload"]),
|
||||
open() {
|
||||
this.showHover("search")
|
||||
this.showHover("search");
|
||||
},
|
||||
close(event) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
this.closeHovers()
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.closeHovers();
|
||||
},
|
||||
keyup(event) {
|
||||
if (event.keyCode === 27) {
|
||||
this.close(event)
|
||||
return
|
||||
this.close(event);
|
||||
return;
|
||||
}
|
||||
|
||||
this.results.length = 0
|
||||
this.results.length = 0;
|
||||
},
|
||||
init (string) {
|
||||
this.value = `${string} `
|
||||
this.$refs.input.focus()
|
||||
init(string) {
|
||||
this.value = `${string} `;
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
reset () {
|
||||
this.ongoing = false
|
||||
this.resultsCount = 50
|
||||
this.results = []
|
||||
reset() {
|
||||
this.ongoing = false;
|
||||
this.resultsCount = 50;
|
||||
this.results = [];
|
||||
},
|
||||
async submit(event) {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
if (this.value === '') {
|
||||
return
|
||||
if (this.value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
let path = this.$route.path
|
||||
let path = this.$route.path;
|
||||
if (!this.isListing) {
|
||||
path = url.removeLastDir(path) + "/"
|
||||
path = url.removeLastDir(path) + "/";
|
||||
}
|
||||
|
||||
this.ongoing = true
|
||||
this.ongoing = true;
|
||||
|
||||
try {
|
||||
this.results = await search(path, this.value)
|
||||
this.results = await search(path, this.value);
|
||||
} catch (error) {
|
||||
this.$showError(error)
|
||||
this.$showError(error);
|
||||
}
|
||||
|
||||
this.ongoing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
this.ongoing = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
<template>
|
||||
<div @click="focus" class="shell" ref="scrollable" :class="{ ['shell--hidden']: !showShell}">
|
||||
<div v-for="(c, index) in content" :key="index" class="shell__result" >
|
||||
<div class="shell__prompt"><i class="material-icons">chevron_right</i></div>
|
||||
<div
|
||||
@click="focus"
|
||||
class="shell"
|
||||
ref="scrollable"
|
||||
:class="{ ['shell--hidden']: !showShell }"
|
||||
>
|
||||
<div v-for="(c, index) in content" :key="index" class="shell__result">
|
||||
<div class="shell__prompt">
|
||||
<i class="material-icons">chevron_right</i>
|
||||
</div>
|
||||
<pre class="shell__text">{{ c.text }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="shell__result" :class="{ 'shell__result--hidden': !canInput }" >
|
||||
<div class="shell__prompt"><i class="material-icons">chevron_right</i></div>
|
||||
<div class="shell__result" :class="{ 'shell__result--hidden': !canInput }">
|
||||
<div class="shell__prompt">
|
||||
<i class="material-icons">chevron_right</i>
|
||||
</div>
|
||||
<pre
|
||||
tabindex="0"
|
||||
ref="input"
|
||||
@@ -14,102 +23,103 @@
|
||||
contenteditable="true"
|
||||
@keydown.prevent.38="historyUp"
|
||||
@keydown.prevent.40="historyDown"
|
||||
@keypress.prevent.enter="submit" />
|
||||
@keypress.prevent.enter="submit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations, mapState, mapGetters } from 'vuex'
|
||||
import { commands } from '@/api'
|
||||
import { mapMutations, mapState, mapGetters } from "vuex";
|
||||
import { commands } from "@/api";
|
||||
|
||||
export default {
|
||||
name: 'shell',
|
||||
name: "shell",
|
||||
computed: {
|
||||
...mapState([ 'user', 'showShell' ]),
|
||||
...mapGetters([ 'isFiles', 'isLogged' ]),
|
||||
...mapState(["user", "showShell"]),
|
||||
...mapGetters(["isFiles", "isLogged"]),
|
||||
path: function () {
|
||||
if (this.isFiles) {
|
||||
return this.$route.path
|
||||
return this.$route.path;
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
return "";
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
content: [],
|
||||
history: [],
|
||||
historyPos: 0,
|
||||
canInput: true
|
||||
canInput: true,
|
||||
}),
|
||||
methods: {
|
||||
...mapMutations([ 'toggleShell' ]),
|
||||
...mapMutations(["toggleShell"]),
|
||||
scroll: function () {
|
||||
this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight
|
||||
this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight;
|
||||
},
|
||||
focus: function () {
|
||||
this.$refs.input.focus()
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
historyUp () {
|
||||
historyUp() {
|
||||
if (this.historyPos > 0) {
|
||||
this.$refs.input.innerText = this.history[--this.historyPos]
|
||||
this.focus()
|
||||
this.$refs.input.innerText = this.history[--this.historyPos];
|
||||
this.focus();
|
||||
}
|
||||
},
|
||||
historyDown () {
|
||||
historyDown() {
|
||||
if (this.historyPos >= 0 && this.historyPos < this.history.length - 1) {
|
||||
this.$refs.input.innerText = this.history[++this.historyPos]
|
||||
this.focus()
|
||||
this.$refs.input.innerText = this.history[++this.historyPos];
|
||||
this.focus();
|
||||
} else {
|
||||
this.historyPos = this.history.length
|
||||
this.$refs.input.innerText = ''
|
||||
this.historyPos = this.history.length;
|
||||
this.$refs.input.innerText = "";
|
||||
}
|
||||
},
|
||||
submit: function (event) {
|
||||
const cmd = event.target.innerText.trim()
|
||||
const cmd = event.target.innerText.trim();
|
||||
|
||||
if (cmd === '') {
|
||||
return
|
||||
if (cmd === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd === 'clear') {
|
||||
this.content = []
|
||||
event.target.innerHTML = ''
|
||||
return
|
||||
if (cmd === "clear") {
|
||||
this.content = [];
|
||||
event.target.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd === 'exit') {
|
||||
event.target.innerHTML = ''
|
||||
this.toggleShell()
|
||||
return
|
||||
if (cmd === "exit") {
|
||||
event.target.innerHTML = "";
|
||||
this.toggleShell();
|
||||
return;
|
||||
}
|
||||
|
||||
this.canInput = false
|
||||
event.target.innerHTML = ''
|
||||
this.canInput = false;
|
||||
event.target.innerHTML = "";
|
||||
|
||||
let results = {
|
||||
text: `${cmd}\n\n`
|
||||
}
|
||||
|
||||
this.history.push(cmd)
|
||||
this.historyPos = this.history.length
|
||||
this.content.push(results)
|
||||
text: `${cmd}\n\n`,
|
||||
};
|
||||
|
||||
this.history.push(cmd);
|
||||
this.historyPos = this.history.length;
|
||||
this.content.push(results);
|
||||
|
||||
commands(
|
||||
this.path,
|
||||
cmd,
|
||||
event => {
|
||||
results.text += `${event.data}\n`
|
||||
this.scroll()
|
||||
(event) => {
|
||||
results.text += `${event.data}\n`;
|
||||
this.scroll();
|
||||
},
|
||||
() => {
|
||||
results.text = results.text.trimEnd()
|
||||
this.canInput = true
|
||||
this.$refs.input.focus()
|
||||
this.scroll()
|
||||
results.text = results.text.trimEnd();
|
||||
this.canInput = true;
|
||||
this.$refs.input.focus();
|
||||
this.scroll();
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,82 +1,134 @@
|
||||
<template>
|
||||
<nav :class="{active}">
|
||||
<nav :class="{ active }">
|
||||
<template v-if="isLogged">
|
||||
<router-link class="action" to="/files/" :aria-label="$t('sidebar.myFiles')" :title="$t('sidebar.myFiles')">
|
||||
<router-link
|
||||
class="action"
|
||||
to="/files/"
|
||||
:aria-label="$t('sidebar.myFiles')"
|
||||
:title="$t('sidebar.myFiles')"
|
||||
>
|
||||
<i class="material-icons">folder</i>
|
||||
<span>{{ $t('sidebar.myFiles') }}</span>
|
||||
<span>{{ $t("sidebar.myFiles") }}</span>
|
||||
</router-link>
|
||||
|
||||
<div v-if="user.perm.create">
|
||||
<button @click="$store.commit('showHover', 'newDir')" class="action" :aria-label="$t('sidebar.newFolder')" :title="$t('sidebar.newFolder')">
|
||||
<button
|
||||
@click="$store.commit('showHover', 'newDir')"
|
||||
class="action"
|
||||
:aria-label="$t('sidebar.newFolder')"
|
||||
:title="$t('sidebar.newFolder')"
|
||||
>
|
||||
<i class="material-icons">create_new_folder</i>
|
||||
<span>{{ $t('sidebar.newFolder') }}</span>
|
||||
<span>{{ $t("sidebar.newFolder") }}</span>
|
||||
</button>
|
||||
|
||||
<button @click="$store.commit('showHover', 'newFile')" class="action" :aria-label="$t('sidebar.newFile')" :title="$t('sidebar.newFile')">
|
||||
<button
|
||||
@click="$store.commit('showHover', 'newFile')"
|
||||
class="action"
|
||||
:aria-label="$t('sidebar.newFile')"
|
||||
:title="$t('sidebar.newFile')"
|
||||
>
|
||||
<i class="material-icons">note_add</i>
|
||||
<span>{{ $t('sidebar.newFile') }}</span>
|
||||
<span>{{ $t("sidebar.newFile") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<router-link class="action" to="/settings" :aria-label="$t('sidebar.settings')" :title="$t('sidebar.settings')">
|
||||
<router-link
|
||||
class="action"
|
||||
to="/settings"
|
||||
:aria-label="$t('sidebar.settings')"
|
||||
:title="$t('sidebar.settings')"
|
||||
>
|
||||
<i class="material-icons">settings_applications</i>
|
||||
<span>{{ $t('sidebar.settings') }}</span>
|
||||
<span>{{ $t("sidebar.settings") }}</span>
|
||||
</router-link>
|
||||
|
||||
<button v-if="authMethod == 'json'" @click="logout" class="action" id="logout" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')">
|
||||
<button
|
||||
v-if="authMethod == 'json'"
|
||||
@click="logout"
|
||||
class="action"
|
||||
id="logout"
|
||||
:aria-label="$t('sidebar.logout')"
|
||||
:title="$t('sidebar.logout')"
|
||||
>
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
<span>{{ $t('sidebar.logout') }}</span>
|
||||
<span>{{ $t("sidebar.logout") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link class="action" to="/login" :aria-label="$t('sidebar.login')" :title="$t('sidebar.login')">
|
||||
<router-link
|
||||
class="action"
|
||||
to="/login"
|
||||
:aria-label="$t('sidebar.login')"
|
||||
:title="$t('sidebar.login')"
|
||||
>
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
<span>{{ $t('sidebar.login') }}</span>
|
||||
<span>{{ $t("sidebar.login") }}</span>
|
||||
</router-link>
|
||||
|
||||
<router-link v-if="signup" class="action" to="/login" :aria-label="$t('sidebar.signup')" :title="$t('sidebar.signup')">
|
||||
<router-link
|
||||
v-if="signup"
|
||||
class="action"
|
||||
to="/login"
|
||||
:aria-label="$t('sidebar.signup')"
|
||||
:title="$t('sidebar.signup')"
|
||||
>
|
||||
<i class="material-icons">person_add</i>
|
||||
<span>{{ $t('sidebar.signup') }}</span>
|
||||
<span>{{ $t("sidebar.signup") }}</span>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<p class="credits">
|
||||
<span>
|
||||
<span v-if="disableExternal">File Browser</span>
|
||||
<a v-else rel="noopener noreferrer" target="_blank" href="https://github.com/filebrowser/filebrowser">File Browser</a>
|
||||
<a
|
||||
v-else
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
href="https://github.com/filebrowser/filebrowser"
|
||||
>File Browser</a
|
||||
>
|
||||
<span> {{ version }}</span>
|
||||
</span>
|
||||
<span><a @click="help">{{ $t('sidebar.help') }}</a></span>
|
||||
<span
|
||||
><a @click="help">{{ $t("sidebar.help") }}</a></span
|
||||
>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import * as auth from '@/utils/auth'
|
||||
import { version, signup, disableExternal, noAuth, authMethod } from '@/utils/constants'
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import * as auth from "@/utils/auth";
|
||||
import {
|
||||
version,
|
||||
signup,
|
||||
disableExternal,
|
||||
noAuth,
|
||||
authMethod,
|
||||
} from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
name: 'sidebar',
|
||||
name: "sidebar",
|
||||
computed: {
|
||||
...mapState([ 'user' ]),
|
||||
...mapGetters([ 'isLogged' ]),
|
||||
active () {
|
||||
return this.$store.state.show === 'sidebar'
|
||||
...mapState(["user"]),
|
||||
...mapGetters(["isLogged"]),
|
||||
active() {
|
||||
return this.$store.state.show === "sidebar";
|
||||
},
|
||||
signup: () => signup,
|
||||
version: () => version,
|
||||
disableExternal: () => disableExternal,
|
||||
noAuth: () => noAuth,
|
||||
authMethod: () => authMethod
|
||||
authMethod: () => authMethod,
|
||||
},
|
||||
methods: {
|
||||
help () {
|
||||
this.$store.commit('showHover', 'help')
|
||||
help() {
|
||||
this.$store.commit("showHover", "help");
|
||||
},
|
||||
logout: auth.logout
|
||||
}
|
||||
}
|
||||
logout: auth.logout,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -10,40 +10,33 @@
|
||||
@mouseup="mouseUp"
|
||||
@wheel="wheelMove"
|
||||
>
|
||||
<img src="" class="image-ex-img image-ex-img-center" ref="imgex" @load="onLoad">
|
||||
<img
|
||||
src=""
|
||||
class="image-ex-img image-ex-img-center"
|
||||
ref="imgex"
|
||||
@load="onLoad"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import throttle from 'lodash.throttle'
|
||||
import UTIF from 'utif'
|
||||
import throttle from "lodash.throttle";
|
||||
import UTIF from "utif";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
src: String,
|
||||
moveDisabledTime: {
|
||||
type: Number,
|
||||
default: () => 200
|
||||
},
|
||||
maxScale: {
|
||||
type: Number,
|
||||
default: () => 4
|
||||
},
|
||||
minScale: {
|
||||
type: Number,
|
||||
default: () => 0.25
|
||||
default: () => 200,
|
||||
},
|
||||
classList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
zoomStep: {
|
||||
type: Number,
|
||||
default: () => 0.25
|
||||
default: () => 0.25,
|
||||
},
|
||||
autofill: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -57,204 +50,229 @@ export default {
|
||||
imageLoaded: false,
|
||||
position: {
|
||||
center: { x: 0, y: 0 },
|
||||
relative: { x: 0, y: 0 }
|
||||
}
|
||||
}
|
||||
relative: { x: 0, y: 0 },
|
||||
},
|
||||
maxScale: 4,
|
||||
minScale: 0.25,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (!this.decodeUTIF()) {
|
||||
this.$refs.imgex.src = this.src
|
||||
this.$refs.imgex.src = this.src;
|
||||
}
|
||||
let container = this.$refs.container
|
||||
this.classList.forEach(className => container.classList.add(className))
|
||||
let container = this.$refs.container;
|
||||
this.classList.forEach((className) => container.classList.add(className));
|
||||
// set width and height if they are zero
|
||||
if (getComputedStyle(container).width === "0px") {
|
||||
container.style.width = "100%"
|
||||
container.style.width = "100%";
|
||||
}
|
||||
if (getComputedStyle(container).height === "0px") {
|
||||
container.style.height = "100%"
|
||||
container.style.height = "100%";
|
||||
}
|
||||
|
||||
window.addEventListener('resize', this.onResize)
|
||||
window.addEventListener("resize", this.onResize);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('resize', this.onResize)
|
||||
document.removeEventListener('mouseup', this.onMouseUp)
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("resize", this.onResize);
|
||||
document.removeEventListener("mouseup", this.onMouseUp);
|
||||
},
|
||||
watch: {
|
||||
src: function () {
|
||||
this.scale = 1
|
||||
this.setZoom()
|
||||
this.setCenter()
|
||||
}
|
||||
if (!this.decodeUTIF()) {
|
||||
this.$refs.imgex.src = this.src;
|
||||
}
|
||||
|
||||
this.scale = 1;
|
||||
this.setZoom();
|
||||
this.setCenter();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// Modified from UTIF.replaceIMG
|
||||
decodeUTIF() {
|
||||
const sufs = ["tif", "tiff", "dng", "cr2", "nef"]
|
||||
let suff = document.location.pathname.split(".").pop().toLowerCase()
|
||||
if (sufs.indexOf(suff) == -1) return false
|
||||
let xhr = new XMLHttpRequest()
|
||||
UTIF._xhrs.push(xhr)
|
||||
UTIF._imgs.push(this.$refs.imgex)
|
||||
xhr.open("GET", this.src)
|
||||
xhr.responseType = "arraybuffer"
|
||||
xhr.onload = UTIF._imgLoaded
|
||||
xhr.send()
|
||||
return true
|
||||
const sufs = ["tif", "tiff", "dng", "cr2", "nef"];
|
||||
let suff = document.location.pathname.split(".").pop().toLowerCase();
|
||||
if (sufs.indexOf(suff) == -1) return false;
|
||||
let xhr = new XMLHttpRequest();
|
||||
UTIF._xhrs.push(xhr);
|
||||
UTIF._imgs.push(this.$refs.imgex);
|
||||
xhr.open("GET", this.src);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onload = UTIF._imgLoaded;
|
||||
xhr.send();
|
||||
return true;
|
||||
},
|
||||
onLoad() {
|
||||
let img = this.$refs.imgex
|
||||
let img = this.$refs.imgex;
|
||||
|
||||
this.imageLoaded = true
|
||||
this.imageLoaded = true;
|
||||
|
||||
if (img === undefined) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
img.classList.remove('image-ex-img-center')
|
||||
this.setCenter()
|
||||
img.classList.add('image-ex-img-ready')
|
||||
img.classList.remove("image-ex-img-center");
|
||||
this.setCenter();
|
||||
img.classList.add("image-ex-img-ready");
|
||||
|
||||
document.addEventListener('mouseup', this.onMouseUp)
|
||||
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
|
||||
this.inDrag = false;
|
||||
},
|
||||
onResize: throttle(function() {
|
||||
onResize: throttle(function () {
|
||||
if (this.imageLoaded) {
|
||||
this.setCenter()
|
||||
this.doMove(this.position.relative.x, this.position.relative.y)
|
||||
this.setCenter();
|
||||
this.doMove(this.position.relative.x, this.position.relative.y);
|
||||
}
|
||||
}, 100),
|
||||
setCenter() {
|
||||
let container = this.$refs.container
|
||||
let img = this.$refs.imgex
|
||||
let container = this.$refs.container;
|
||||
let img = this.$refs.imgex;
|
||||
|
||||
this.position.center.x = Math.floor((container.clientWidth - img.clientWidth) / 2)
|
||||
this.position.center.y = Math.floor((container.clientHeight - img.clientHeight) / 2)
|
||||
this.position.center.x = Math.floor(
|
||||
(container.clientWidth - img.clientWidth) / 2
|
||||
);
|
||||
this.position.center.y = Math.floor(
|
||||
(container.clientHeight - img.clientHeight) / 2
|
||||
);
|
||||
|
||||
img.style.left = this.position.center.x + 'px'
|
||||
img.style.top = this.position.center.y + 'px'
|
||||
img.style.left = this.position.center.x + "px";
|
||||
img.style.top = this.position.center.y + "px";
|
||||
},
|
||||
mousedownStart(event) {
|
||||
this.lastX = null
|
||||
this.lastY = null
|
||||
this.inDrag = true
|
||||
event.preventDefault()
|
||||
this.lastX = null;
|
||||
this.lastY = null;
|
||||
this.inDrag = true;
|
||||
event.preventDefault();
|
||||
},
|
||||
mouseMove(event) {
|
||||
if (!this.inDrag) return
|
||||
this.doMove(event.movementX, event.movementY)
|
||||
event.preventDefault()
|
||||
if (!this.inDrag) return;
|
||||
this.doMove(event.movementX, event.movementY);
|
||||
event.preventDefault();
|
||||
},
|
||||
mouseUp(event) {
|
||||
this.inDrag = false
|
||||
event.preventDefault()
|
||||
this.inDrag = false;
|
||||
event.preventDefault();
|
||||
},
|
||||
touchStart(event) {
|
||||
this.lastX = null
|
||||
this.lastY = null
|
||||
this.lastTouchDistance = null
|
||||
this.lastX = null;
|
||||
this.lastY = null;
|
||||
this.lastTouchDistance = null;
|
||||
if (event.targetTouches.length < 2) {
|
||||
setTimeout(() => {
|
||||
this.touches = 0
|
||||
}, 300)
|
||||
this.touches++
|
||||
this.touches = 0;
|
||||
}, 300);
|
||||
this.touches++;
|
||||
if (this.touches > 1) {
|
||||
this.zoomAuto(event)
|
||||
this.zoomAuto(event);
|
||||
}
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
event.preventDefault();
|
||||
},
|
||||
zoomAuto(event) {
|
||||
switch (this.scale) {
|
||||
case 1:
|
||||
this.scale = 2
|
||||
break
|
||||
this.scale = 2;
|
||||
break;
|
||||
case 2:
|
||||
this.scale = 4
|
||||
break
|
||||
this.scale = 4;
|
||||
break;
|
||||
default:
|
||||
case 4:
|
||||
this.scale = 1
|
||||
this.setCenter()
|
||||
break
|
||||
this.scale = 1;
|
||||
this.setCenter();
|
||||
break;
|
||||
}
|
||||
this.setZoom()
|
||||
event.preventDefault()
|
||||
this.setZoom();
|
||||
event.preventDefault();
|
||||
},
|
||||
touchMove(event) {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
if (this.lastX === null) {
|
||||
this.lastX = event.targetTouches[0].pageX
|
||||
this.lastY = event.targetTouches[0].pageY
|
||||
return
|
||||
this.lastX = event.targetTouches[0].pageX;
|
||||
this.lastY = event.targetTouches[0].pageY;
|
||||
return;
|
||||
}
|
||||
let step = this.$refs.imgex.width / 5
|
||||
let step = this.$refs.imgex.width / 5;
|
||||
if (event.targetTouches.length === 2) {
|
||||
this.moveDisabled = true
|
||||
clearTimeout(this.disabledTimer)
|
||||
this.moveDisabled = true;
|
||||
clearTimeout(this.disabledTimer);
|
||||
this.disabledTimer = setTimeout(
|
||||
() => (this.moveDisabled = false),
|
||||
this.moveDisabledTime
|
||||
)
|
||||
);
|
||||
|
||||
let p1 = event.targetTouches[0]
|
||||
let p2 = event.targetTouches[1]
|
||||
let p1 = event.targetTouches[0];
|
||||
let p2 = event.targetTouches[1];
|
||||
let touchDistance = Math.sqrt(
|
||||
Math.pow(p2.pageX - p1.pageX, 2) + Math.pow(p2.pageY - p1.pageY, 2)
|
||||
)
|
||||
);
|
||||
if (!this.lastTouchDistance) {
|
||||
this.lastTouchDistance = touchDistance
|
||||
return
|
||||
this.lastTouchDistance = touchDistance;
|
||||
return;
|
||||
}
|
||||
this.scale += (touchDistance - this.lastTouchDistance) / step
|
||||
this.lastTouchDistance = touchDistance
|
||||
this.setZoom()
|
||||
this.scale += (touchDistance - this.lastTouchDistance) / step;
|
||||
this.lastTouchDistance = touchDistance;
|
||||
this.setZoom();
|
||||
} else if (event.targetTouches.length === 1) {
|
||||
if (this.moveDisabled) return
|
||||
let x = event.targetTouches[0].pageX - this.lastX
|
||||
let y = event.targetTouches[0].pageY - this.lastY
|
||||
if (Math.abs(x) >= step && Math.abs(y) >= step) return
|
||||
this.lastX = event.targetTouches[0].pageX
|
||||
this.lastY = event.targetTouches[0].pageY
|
||||
this.doMove(x, y)
|
||||
if (this.moveDisabled) return;
|
||||
let x = event.targetTouches[0].pageX - this.lastX;
|
||||
let y = event.targetTouches[0].pageY - this.lastY;
|
||||
if (Math.abs(x) >= step && Math.abs(y) >= step) return;
|
||||
this.lastX = event.targetTouches[0].pageX;
|
||||
this.lastY = event.targetTouches[0].pageY;
|
||||
this.doMove(x, y);
|
||||
}
|
||||
},
|
||||
doMove(x, y) {
|
||||
let style = this.$refs.imgex.style
|
||||
let posX = this.pxStringToNumber(style.left) + x
|
||||
let posY = this.pxStringToNumber(style.top) + y
|
||||
let style = this.$refs.imgex.style;
|
||||
let posX = this.pxStringToNumber(style.left) + x;
|
||||
let posY = this.pxStringToNumber(style.top) + y;
|
||||
|
||||
style.left = posX + 'px'
|
||||
style.top = posY + 'px'
|
||||
style.left = posX + "px";
|
||||
style.top = posY + "px";
|
||||
|
||||
this.position.relative.x = Math.abs(this.position.center.x - posX)
|
||||
this.position.relative.y = Math.abs(this.position.center.y - posY)
|
||||
this.position.relative.x = Math.abs(this.position.center.x - posX);
|
||||
this.position.relative.y = Math.abs(this.position.center.y - posY);
|
||||
|
||||
if (posX < this.position.center.x) {
|
||||
this.position.relative.x = this.position.relative.x * -1
|
||||
this.position.relative.x = this.position.relative.x * -1;
|
||||
}
|
||||
|
||||
if (posY < this.position.center.y) {
|
||||
this.position.relative.y = this.position.relative.y * -1
|
||||
this.position.relative.y = this.position.relative.y * -1;
|
||||
}
|
||||
},
|
||||
wheelMove(event) {
|
||||
this.scale += (event.wheelDeltaY / 100) * this.zoomStep
|
||||
this.setZoom()
|
||||
this.scale += -Math.sign(event.deltaY) * this.zoomStep;
|
||||
this.setZoom();
|
||||
},
|
||||
setZoom() {
|
||||
this.scale = this.scale < this.minScale ? this.minScale : this.scale
|
||||
this.scale = this.scale > this.maxScale ? this.maxScale : this.scale
|
||||
this.$refs.imgex.style.transform = `scale(${this.scale})`
|
||||
this.scale = this.scale < this.minScale ? this.minScale : this.scale;
|
||||
this.scale = this.scale > this.maxScale ? this.maxScale : this.scale;
|
||||
this.$refs.imgex.style.transform = `scale(${this.scale})`;
|
||||
},
|
||||
pxStringToNumber(style) {
|
||||
return +style.replace("px", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
return +style.replace("px", "");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.image-ex-container {
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
<template>
|
||||
<div class="item"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
:draggable="isDraggable"
|
||||
@dragstart="dragStart"
|
||||
@dragover="dragOver"
|
||||
@drop="drop"
|
||||
@click="itemClick"
|
||||
@dblclick="dblclick"
|
||||
@touchstart="touchstart"
|
||||
:data-dir="isDir"
|
||||
:aria-label="name"
|
||||
:aria-selected="isSelected">
|
||||
<div
|
||||
class="item"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
:draggable="isDraggable"
|
||||
@dragstart="dragStart"
|
||||
@dragover="dragOver"
|
||||
@drop="drop"
|
||||
@click="itemClick"
|
||||
:data-dir="isDir"
|
||||
:aria-label="name"
|
||||
:aria-selected="isSelected"
|
||||
>
|
||||
<div>
|
||||
<img v-if="readOnly == undefined && type==='image' && isThumbsEnabled" v-lazy="thumbnailUrl">
|
||||
<img
|
||||
v-if="readOnly == undefined && type === 'image' && isThumbsEnabled"
|
||||
v-lazy="thumbnailUrl"
|
||||
/>
|
||||
<i v-else class="material-icons">{{ icon }}</i>
|
||||
</div>
|
||||
|
||||
@@ -31,203 +34,218 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { baseURL, enableThumbs } from '@/utils/constants'
|
||||
import { mapMutations, mapGetters, mapState } from 'vuex'
|
||||
import filesize from 'filesize'
|
||||
import moment from 'moment'
|
||||
import { files as api } from '@/api'
|
||||
import * as upload from '@/utils/upload'
|
||||
import { baseURL, enableThumbs } from "@/utils/constants";
|
||||
import { mapMutations, mapGetters, mapState } from "vuex";
|
||||
import filesize from "filesize";
|
||||
import moment from "moment";
|
||||
import { files as api } from "@/api";
|
||||
import * as upload from "@/utils/upload";
|
||||
|
||||
export default {
|
||||
name: 'item',
|
||||
name: "item",
|
||||
data: function () {
|
||||
return {
|
||||
touches: 0
|
||||
}
|
||||
touches: 0,
|
||||
};
|
||||
},
|
||||
props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index', 'readOnly'],
|
||||
props: [
|
||||
"name",
|
||||
"isDir",
|
||||
"url",
|
||||
"type",
|
||||
"size",
|
||||
"modified",
|
||||
"index",
|
||||
"readOnly",
|
||||
],
|
||||
computed: {
|
||||
...mapState(['user', 'selected', 'req', 'jwt']),
|
||||
...mapGetters(['selectedCount']),
|
||||
singleClick () {
|
||||
return this.readOnly == undefined && this.user.singleClick
|
||||
...mapState(["user", "selected", "req", "jwt"]),
|
||||
...mapGetters(["selectedCount"]),
|
||||
singleClick() {
|
||||
return this.readOnly == undefined && this.user.singleClick;
|
||||
},
|
||||
isSelected () {
|
||||
return (this.selected.indexOf(this.index) !== -1)
|
||||
isSelected() {
|
||||
return this.selected.indexOf(this.index) !== -1;
|
||||
},
|
||||
icon () {
|
||||
if (this.isDir) return 'folder'
|
||||
if (this.type === 'image') return 'insert_photo'
|
||||
if (this.type === 'audio') return 'volume_up'
|
||||
if (this.type === 'video') return 'movie'
|
||||
return 'insert_drive_file'
|
||||
icon() {
|
||||
if (this.isDir) return "folder";
|
||||
if (this.type === "image") return "insert_photo";
|
||||
if (this.type === "audio") return "volume_up";
|
||||
if (this.type === "video") return "movie";
|
||||
return "insert_drive_file";
|
||||
},
|
||||
isDraggable () {
|
||||
return this.readOnly == undefined && this.user.perm.rename
|
||||
isDraggable() {
|
||||
return this.readOnly == undefined && this.user.perm.rename;
|
||||
},
|
||||
canDrop () {
|
||||
if (!this.isDir || this.readOnly !== undefined) return false
|
||||
canDrop() {
|
||||
if (!this.isDir || this.readOnly !== undefined) return false;
|
||||
|
||||
for (let i of this.selected) {
|
||||
if (this.req.items[i].url === this.url) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
},
|
||||
thumbnailUrl () {
|
||||
const path = this.url.replace(/^\/files\//, '')
|
||||
thumbnailUrl() {
|
||||
const path = this.url.replace(/^\/files\//, "");
|
||||
|
||||
// reload the image when the file is replaced
|
||||
const key = Date.parse(this.modified)
|
||||
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;
|
||||
},
|
||||
isThumbsEnabled () {
|
||||
return enableThumbs
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['addSelected', 'removeSelected', 'resetSelected']),
|
||||
...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
|
||||
humanSize: function () {
|
||||
return filesize(this.size)
|
||||
return filesize(this.size);
|
||||
},
|
||||
humanTime: function () {
|
||||
return moment(this.modified).fromNow()
|
||||
return moment(this.modified).fromNow();
|
||||
},
|
||||
dragStart: function () {
|
||||
if (this.selectedCount === 0) {
|
||||
this.addSelected(this.index)
|
||||
return
|
||||
this.addSelected(this.index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isSelected) {
|
||||
this.resetSelected()
|
||||
this.addSelected(this.index)
|
||||
this.resetSelected();
|
||||
this.addSelected(this.index);
|
||||
}
|
||||
},
|
||||
dragOver: function (event) {
|
||||
if (!this.canDrop) return
|
||||
if (!this.canDrop) return;
|
||||
|
||||
event.preventDefault()
|
||||
let el = event.target
|
||||
event.preventDefault();
|
||||
let el = event.target;
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (!el.classList.contains('item')) {
|
||||
el = el.parentElement
|
||||
if (!el.classList.contains("item")) {
|
||||
el = el.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
el.style.opacity = 1
|
||||
el.style.opacity = 1;
|
||||
},
|
||||
drop: async function (event) {
|
||||
if (!this.canDrop) return
|
||||
event.preventDefault()
|
||||
if (!this.canDrop) return;
|
||||
event.preventDefault();
|
||||
|
||||
if (this.selectedCount === 0) return
|
||||
if (this.selectedCount === 0) return;
|
||||
|
||||
let el = event.target
|
||||
let el = event.target;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (el !== null && !el.classList.contains('item')) {
|
||||
el = el.parentElement
|
||||
if (el !== null && !el.classList.contains("item")) {
|
||||
el = el.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
let items = []
|
||||
let items = [];
|
||||
|
||||
for (let i of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[i].url,
|
||||
to: this.url + this.req.items[i].name,
|
||||
name: 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
|
||||
let baseItems = (await api.fetch(path)).items
|
||||
// Get url from ListingItem instance
|
||||
let path = el.__vue__.url;
|
||||
let baseItems = (await api.fetch(path)).items;
|
||||
|
||||
let action = (overwrite, rename) => {
|
||||
api.move(items, overwrite, rename).then(() => {
|
||||
this.$store.commit('setReload', true)
|
||||
}).catch(this.$showError)
|
||||
}
|
||||
api
|
||||
.move(items, overwrite, rename)
|
||||
.then(() => {
|
||||
this.$store.commit("setReload", true);
|
||||
})
|
||||
.catch(this.$showError);
|
||||
};
|
||||
|
||||
let conflict = upload.checkConflict(items, baseItems)
|
||||
let conflict = upload.checkConflict(items, baseItems);
|
||||
|
||||
let overwrite = false
|
||||
let rename = false
|
||||
let overwrite = false;
|
||||
let rename = false;
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'replace-rename',
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace-rename",
|
||||
confirm: (event, option) => {
|
||||
overwrite = option == 'overwrite'
|
||||
rename = option == 'rename'
|
||||
overwrite = option == "overwrite";
|
||||
rename = option == "rename";
|
||||
|
||||
event.preventDefault()
|
||||
this.$store.commit('closeHovers')
|
||||
action(overwrite, rename)
|
||||
}
|
||||
})
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
action(overwrite, rename);
|
||||
},
|
||||
});
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
action(overwrite, rename)
|
||||
action(overwrite, rename);
|
||||
},
|
||||
itemClick: function(event) {
|
||||
if (this.singleClick && !this.$store.state.multiple) this.open()
|
||||
else this.click(event)
|
||||
itemClick: function (event) {
|
||||
if (this.singleClick && !this.$store.state.multiple) this.open();
|
||||
else this.click(event);
|
||||
},
|
||||
click: function (event) {
|
||||
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault()
|
||||
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
|
||||
this.removeSelected(this.index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.shiftKey && this.selected.length > 0) {
|
||||
let fi = 0
|
||||
let la = 0
|
||||
let fi = 0;
|
||||
let la = 0;
|
||||
|
||||
if (this.index > this.selected[0]) {
|
||||
fi = this.selected[0] + 1
|
||||
la = this.index
|
||||
fi = this.selected[0] + 1;
|
||||
la = this.index;
|
||||
} else {
|
||||
fi = this.index
|
||||
la = this.selected[0] - 1
|
||||
fi = this.index;
|
||||
la = this.selected[0] - 1;
|
||||
}
|
||||
|
||||
for (; fi <= la; fi++) {
|
||||
if (this.$store.state.selected.indexOf(fi) == -1) {
|
||||
this.addSelected(fi)
|
||||
this.addSelected(fi);
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !this.$store.state.multiple) this.resetSelected()
|
||||
this.addSelected(this.index)
|
||||
},
|
||||
dblclick: function () {
|
||||
if (!this.singleClick) this.open()
|
||||
},
|
||||
touchstart () {
|
||||
setTimeout(() => {
|
||||
this.touches = 0
|
||||
}, 300)
|
||||
|
||||
this.touches++
|
||||
if (this.touches > 1) {
|
||||
this.open()
|
||||
}
|
||||
if (
|
||||
!this.singleClick &&
|
||||
!event.ctrlKey &&
|
||||
!event.metaKey &&
|
||||
!this.$store.state.multiple
|
||||
)
|
||||
this.resetSelected();
|
||||
this.addSelected(this.index);
|
||||
},
|
||||
open: function () {
|
||||
this.$router.push({path: this.url})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
this.$router.push({ path: this.url });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="action" :aria-label="label" :title="label" class="action">
|
||||
<button @click="action" :aria-label="label" :title="label" class="action">
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
<span>{{ label }}</span>
|
||||
<span v-if="counter > 0" class="counter">{{ counter }}</span>
|
||||
@@ -8,25 +8,18 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'action',
|
||||
props: [
|
||||
'icon',
|
||||
'label',
|
||||
'counter',
|
||||
'show'
|
||||
],
|
||||
name: "action",
|
||||
props: ["icon", "label", "counter", "show"],
|
||||
methods: {
|
||||
action: function () {
|
||||
if (this.show) {
|
||||
this.$store.commit('showHover', this.show)
|
||||
this.$store.commit("showHover", this.show);
|
||||
}
|
||||
|
||||
this.$emit('action')
|
||||
}
|
||||
}
|
||||
}
|
||||
this.$emit("action");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<header>
|
||||
<img v-if="showLogo !== undefined" :src="logoURL" />
|
||||
<action v-if="showMenu !== undefined" class="menu-button" icon="menu" :label="$t('buttons.toggleSidebar')" @action="openSidebar()" />
|
||||
<action
|
||||
v-if="showMenu !== undefined"
|
||||
class="menu-button"
|
||||
icon="menu"
|
||||
:label="$t('buttons.toggleSidebar')"
|
||||
@action="openSidebar()"
|
||||
/>
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -9,39 +15,44 @@
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
|
||||
<action v-if="this.$slots.actions" id="more" icon="more_vert" :label="$t('buttons.more')" @action="$store.commit('showHover', 'more')" />
|
||||
<action
|
||||
v-if="this.$slots.actions"
|
||||
id="more"
|
||||
icon="more_vert"
|
||||
:label="$t('buttons.more')"
|
||||
@action="$store.commit('showHover', 'more')"
|
||||
/>
|
||||
|
||||
<div class="overlay" v-show="this.$store.state.show == 'more'" @click="$store.commit('closeHovers')" />
|
||||
<div
|
||||
class="overlay"
|
||||
v-show="this.$store.state.show == 'more'"
|
||||
@click="$store.commit('closeHovers')"
|
||||
/>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { logoURL } from '@/utils/constants'
|
||||
import { logoURL } from "@/utils/constants";
|
||||
|
||||
import Action from '@/components/header/Action'
|
||||
import Action from "@/components/header/Action";
|
||||
|
||||
export default {
|
||||
name: 'header-bar',
|
||||
props: [
|
||||
'showLogo',
|
||||
'showMenu',
|
||||
],
|
||||
name: "header-bar",
|
||||
props: ["showLogo", "showMenu"],
|
||||
components: {
|
||||
Action
|
||||
Action,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
logoURL
|
||||
}
|
||||
logoURL,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openSidebar () {
|
||||
this.$store.commit('showHover', 'sidebar')
|
||||
}
|
||||
}
|
||||
}
|
||||
openSidebar() {
|
||||
this.$store.commit("showHover", "sidebar");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,108 +1,119 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.copy') }}</h2>
|
||||
<h2>{{ $t("prompts.copy") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.copyMessage') }}</p>
|
||||
<file-list @update:selected="val => dest = val"></file-list>
|
||||
<p>{{ $t("prompts.copyMessage") }}</p>
|
||||
<file-list @update:selected="(val) => (dest = val)"></file-list>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="button button--flat"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="copy"
|
||||
:aria-label="$t('buttons.copy')"
|
||||
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
|
||||
:title="$t('buttons.copy')"
|
||||
>
|
||||
{{ $t("buttons.copy") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import FileList from './FileList'
|
||||
import { files as api } from '@/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
import * as upload from '@/utils/upload'
|
||||
import { mapState } from "vuex";
|
||||
import FileList from "./FileList";
|
||||
import { files as api } from "@/api";
|
||||
import buttons from "@/utils/buttons";
|
||||
import * as upload from "@/utils/upload";
|
||||
|
||||
export default {
|
||||
name: 'copy',
|
||||
name: "copy",
|
||||
components: { FileList },
|
||||
data: function () {
|
||||
return {
|
||||
current: window.location.pathname,
|
||||
dest: null
|
||||
}
|
||||
dest: null,
|
||||
};
|
||||
},
|
||||
computed: mapState(['req', 'selected']),
|
||||
computed: mapState(["req", "selected"]),
|
||||
methods: {
|
||||
copy: async function (event) {
|
||||
event.preventDefault()
|
||||
let items = []
|
||||
event.preventDefault();
|
||||
let items = [];
|
||||
|
||||
// Create a new promise for each file.
|
||||
for (let item of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
||||
name: this.req.items[item].name
|
||||
})
|
||||
name: this.req.items[item].name,
|
||||
});
|
||||
}
|
||||
|
||||
let action = async (overwrite, rename) => {
|
||||
buttons.loading('copy')
|
||||
buttons.loading("copy");
|
||||
|
||||
await api.copy(items, overwrite, rename).then(() => {
|
||||
buttons.success('copy')
|
||||
await api
|
||||
.copy(items, overwrite, rename)
|
||||
.then(() => {
|
||||
buttons.success("copy");
|
||||
|
||||
if (this.$route.path === this.dest) {
|
||||
this.$store.commit('setReload', true)
|
||||
if (this.$route.path === this.dest) {
|
||||
this.$store.commit("setReload", true);
|
||||
|
||||
return
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.$router.push({ path: this.dest })
|
||||
}).catch((e) => {
|
||||
buttons.done('copy')
|
||||
this.$showError(e)
|
||||
})
|
||||
}
|
||||
this.$router.push({ path: this.dest });
|
||||
})
|
||||
.catch((e) => {
|
||||
buttons.done("copy");
|
||||
this.$showError(e);
|
||||
});
|
||||
};
|
||||
|
||||
if (this.$route.path === this.dest) {
|
||||
this.$store.commit('closeHovers')
|
||||
action(false, true)
|
||||
this.$store.commit("closeHovers");
|
||||
action(false, true);
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
let dstItems = (await api.fetch(this.dest)).items
|
||||
let conflict = upload.checkConflict(items, dstItems)
|
||||
let dstItems = (await api.fetch(this.dest)).items;
|
||||
let conflict = upload.checkConflict(items, dstItems);
|
||||
|
||||
let overwrite = false
|
||||
let rename = false
|
||||
let overwrite = false;
|
||||
let rename = false;
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'replace-rename',
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace-rename",
|
||||
confirm: (event, option) => {
|
||||
overwrite = option == 'overwrite'
|
||||
rename = option == 'rename'
|
||||
overwrite = option == "overwrite";
|
||||
rename = option == "rename";
|
||||
|
||||
event.preventDefault()
|
||||
this.$store.commit('closeHovers')
|
||||
action(overwrite, rename)
|
||||
}
|
||||
})
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
action(overwrite, rename);
|
||||
},
|
||||
});
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
action(overwrite, rename)
|
||||
}
|
||||
}
|
||||
}
|
||||
action(overwrite, rename);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,68 +1,80 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-content">
|
||||
<p v-if="req.kind !== 'listing'">{{ $t('prompts.deleteMessageSingle') }}</p>
|
||||
<p v-else>{{ $t('prompts.deleteMessageMultiple', { count: selectedCount}) }}</p>
|
||||
<p v-if="req.kind !== 'listing'">
|
||||
{{ $t("prompts.deleteMessageSingle") }}
|
||||
</p>
|
||||
<p v-else>
|
||||
{{ $t("prompts.deleteMessageMultiple", { count: selectedCount }) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button @click="$store.commit('closeHovers')"
|
||||
<button
|
||||
@click="$store.commit('closeHovers')"
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button @click="submit"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
@click="submit"
|
||||
class="button button--flat button--red"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
||||
:title="$t('buttons.delete')"
|
||||
>
|
||||
{{ $t("buttons.delete") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapMutations, mapState} from 'vuex'
|
||||
import { files as api } from '@/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
import { mapGetters, mapMutations, mapState } from "vuex";
|
||||
import { files as api } from "@/api";
|
||||
import buttons from "@/utils/buttons";
|
||||
|
||||
export default {
|
||||
name: 'delete',
|
||||
name: "delete",
|
||||
computed: {
|
||||
...mapGetters(['isListing', 'selectedCount']),
|
||||
...mapState(['req', 'selected', 'showConfirm'])
|
||||
...mapGetters(["isListing", "selectedCount"]),
|
||||
...mapState(["req", "selected", "showConfirm"]),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['closeHovers']),
|
||||
...mapMutations(["closeHovers"]),
|
||||
submit: async function () {
|
||||
buttons.loading('delete')
|
||||
buttons.loading("delete");
|
||||
|
||||
try {
|
||||
if (!this.isListing) {
|
||||
await api.remove(this.$route.path)
|
||||
buttons.success('delete')
|
||||
await api.remove(this.$route.path);
|
||||
buttons.success("delete");
|
||||
|
||||
this.showConfirm()
|
||||
this.closeHovers()
|
||||
return
|
||||
this.showConfirm();
|
||||
this.closeHovers();
|
||||
return;
|
||||
}
|
||||
|
||||
this.closeHovers()
|
||||
this.closeHovers();
|
||||
|
||||
if (this.selectedCount === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
let promises = []
|
||||
let promises = [];
|
||||
for (let index of this.selected) {
|
||||
promises.push(api.remove(this.req.items[index].url))
|
||||
promises.push(api.remove(this.req.items[index].url));
|
||||
}
|
||||
|
||||
await Promise.all(promises)
|
||||
buttons.success('delete')
|
||||
this.$store.commit('setReload', true)
|
||||
await Promise.all(promises);
|
||||
buttons.success("delete");
|
||||
this.$store.commit("setReload", true);
|
||||
} catch (e) {
|
||||
buttons.done('delete')
|
||||
this.$showError(e)
|
||||
if (this.isListing) this.$store.commit('setReload', true)
|
||||
buttons.done("delete");
|
||||
this.$showError(e);
|
||||
if (this.isListing) this.$store.commit("setReload", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,35 +1,43 @@
|
||||
<template>
|
||||
<div class="card floating" id="download">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.download') }}</h2>
|
||||
<h2>{{ $t("prompts.download") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.downloadMessage') }}</p>
|
||||
<p>{{ $t("prompts.downloadMessage") }}</p>
|
||||
|
||||
<button v-for="(ext, format) in formats" :key="format" class="button button--block" @click="showConfirm(format)" v-focus>{{ ext }}</button>
|
||||
<button
|
||||
v-for="(ext, format) in formats"
|
||||
:key="format"
|
||||
class="button button--block"
|
||||
@click="showConfirm(format)"
|
||||
v-focus
|
||||
>
|
||||
{{ ext }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'download',
|
||||
name: "download",
|
||||
data: function () {
|
||||
return {
|
||||
formats: {
|
||||
zip: 'zip',
|
||||
tar: 'tar',
|
||||
targz: 'tar.gz',
|
||||
tarbz2: 'tar.bz2',
|
||||
tarxz: 'tar.xz',
|
||||
tarlz4: 'tar.lz4',
|
||||
tarsz: 'tar.sz'
|
||||
}
|
||||
}
|
||||
zip: "zip",
|
||||
tar: "tar",
|
||||
targz: "tar.gz",
|
||||
tarbz2: "tar.bz2",
|
||||
tarxz: "tar.xz",
|
||||
tarlz4: "tar.lz4",
|
||||
tarsz: "tar.sz",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: mapState(['showConfirm'])
|
||||
}
|
||||
computed: mapState(["showConfirm"]),
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,132 +1,138 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="file-list">
|
||||
<li @click="itemClick"
|
||||
<li
|
||||
@click="itemClick"
|
||||
@touchstart="touchstart"
|
||||
@dblclick="next"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
:aria-label="item.name"
|
||||
:aria-selected="selected == item.url"
|
||||
:key="item.name" v-for="item in items"
|
||||
:data-url="item.url">{{ item.name }}</li>
|
||||
:key="item.name"
|
||||
v-for="item in items"
|
||||
:data-url="item.url"
|
||||
>
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>{{ $t('prompts.currentlyNavigating') }} <code>{{ nav }}</code>.</p>
|
||||
<p>
|
||||
{{ $t("prompts.currentlyNavigating") }} <code>{{ nav }}</code
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import url from '@/utils/url'
|
||||
import { files } from '@/api'
|
||||
import { mapState } from "vuex";
|
||||
import url from "@/utils/url";
|
||||
import { files } from "@/api";
|
||||
|
||||
export default {
|
||||
name: 'file-list',
|
||||
name: "file-list",
|
||||
data: function () {
|
||||
return {
|
||||
items: [],
|
||||
touches: {
|
||||
id: '',
|
||||
count: 0
|
||||
id: "",
|
||||
count: 0,
|
||||
},
|
||||
selected: null,
|
||||
current: window.location.pathname
|
||||
}
|
||||
current: window.location.pathname,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'req', 'user' ]),
|
||||
nav () {
|
||||
return decodeURIComponent(this.current)
|
||||
}
|
||||
...mapState(["req", "user"]),
|
||||
nav() {
|
||||
return decodeURIComponent(this.current);
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.fillOptions(this.req)
|
||||
mounted() {
|
||||
this.fillOptions(this.req);
|
||||
},
|
||||
methods: {
|
||||
fillOptions (req) {
|
||||
fillOptions(req) {
|
||||
// Sets the current path and resets
|
||||
// the current items.
|
||||
this.current = req.url
|
||||
this.items = []
|
||||
this.current = req.url;
|
||||
this.items = [];
|
||||
|
||||
this.$emit('update:selected', this.current)
|
||||
this.$emit("update:selected", this.current);
|
||||
|
||||
// If the path isn't the root path,
|
||||
// show a button to navigate to the previous
|
||||
// directory.
|
||||
if (req.url !== '/files/') {
|
||||
if (req.url !== "/files/") {
|
||||
this.items.push({
|
||||
name: '..',
|
||||
url: url.removeLastDir(req.url) + '/'
|
||||
})
|
||||
name: "..",
|
||||
url: url.removeLastDir(req.url) + "/",
|
||||
});
|
||||
}
|
||||
|
||||
// If this folder is empty, finish here.
|
||||
if (req.items === null) return
|
||||
if (req.items === null) return;
|
||||
|
||||
// Otherwise we add every directory to the
|
||||
// move options.
|
||||
for (let item of req.items) {
|
||||
if (!item.isDir) continue
|
||||
if (!item.isDir) continue;
|
||||
|
||||
this.items.push({
|
||||
name: item.name,
|
||||
url: item.url
|
||||
})
|
||||
url: item.url,
|
||||
});
|
||||
}
|
||||
},
|
||||
next: function (event) {
|
||||
// Retrieves the URL of the directory the user
|
||||
// just clicked in and fill the options with its
|
||||
// content.
|
||||
let uri = event.currentTarget.dataset.url
|
||||
let uri = event.currentTarget.dataset.url;
|
||||
|
||||
files.fetch(uri)
|
||||
.then(this.fillOptions)
|
||||
.catch(this.$showError)
|
||||
files.fetch(uri).then(this.fillOptions).catch(this.$showError);
|
||||
},
|
||||
touchstart (event) {
|
||||
let url = event.currentTarget.dataset.url
|
||||
touchstart(event) {
|
||||
let url = event.currentTarget.dataset.url;
|
||||
|
||||
// In 300 milliseconds, we shall reset the count.
|
||||
setTimeout(() => {
|
||||
this.touches.count = 0
|
||||
}, 300)
|
||||
this.touches.count = 0;
|
||||
}, 300);
|
||||
|
||||
// If the element the user is touching
|
||||
// is different from the last one he touched,
|
||||
// reset the count.
|
||||
if (this.touches.id !== url) {
|
||||
this.touches.id = url
|
||||
this.touches.count = 1
|
||||
return
|
||||
this.touches.id = url;
|
||||
this.touches.count = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
this.touches.count++
|
||||
this.touches.count++;
|
||||
|
||||
// If there is more than one touch already,
|
||||
// open the next screen.
|
||||
if (this.touches.count > 1) {
|
||||
this.next(event)
|
||||
this.next(event);
|
||||
}
|
||||
},
|
||||
itemClick: function (event) {
|
||||
if (this.user.singleClick) this.next(event)
|
||||
else this.select(event)
|
||||
if (this.user.singleClick) this.next(event);
|
||||
else this.select(event);
|
||||
},
|
||||
select: function (event) {
|
||||
// If the element is already selected, unselect it.
|
||||
if (this.selected === event.currentTarget.dataset.url) {
|
||||
this.selected = null
|
||||
this.$emit('update:selected', this.current)
|
||||
return
|
||||
this.selected = null;
|
||||
this.$emit("update:selected", this.current);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise select the element.
|
||||
this.selected = event.currentTarget.dataset.url
|
||||
this.$emit('update:selected', this.selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.selected = event.currentTarget.dataset.url;
|
||||
this.$emit("update:selected", this.selected);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
<template>
|
||||
<div class="card floating help">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('help.help') }}</h2>
|
||||
<h2>{{ $t("help.help") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<ul>
|
||||
<li><strong>F1</strong> - {{ $t('help.f1') }}</li>
|
||||
<li><strong>F2</strong> - {{ $t('help.f2') }}</li>
|
||||
<li><strong>DEL</strong> - {{ $t('help.del') }}</li>
|
||||
<li><strong>ESC</strong> - {{ $t('help.esc') }}</li>
|
||||
<li><strong>CTRL + S</strong> - {{ $t('help.ctrl.s') }}</li>
|
||||
<li><strong>CTRL + F</strong> - {{ $t('help.ctrl.f') }}</li>
|
||||
<li><strong>CTRL + Click</strong> - {{ $t('help.ctrl.click') }}</li>
|
||||
<li><strong>Click</strong> - {{ $t('help.click') }}</li>
|
||||
<li><strong>Double click</strong> - {{ $t('help.doubleClick') }}</li>
|
||||
<li><strong>F1</strong> - {{ $t("help.f1") }}</li>
|
||||
<li><strong>F2</strong> - {{ $t("help.f2") }}</li>
|
||||
<li><strong>DEL</strong> - {{ $t("help.del") }}</li>
|
||||
<li><strong>ESC</strong> - {{ $t("help.esc") }}</li>
|
||||
<li><strong>CTRL + S</strong> - {{ $t("help.ctrl.s") }}</li>
|
||||
<li><strong>CTRL + F</strong> - {{ $t("help.ctrl.f") }}</li>
|
||||
<li><strong>CTRL + Click</strong> - {{ $t("help.ctrl.click") }}</li>
|
||||
<li><strong>Click</strong> - {{ $t("help.click") }}</li>
|
||||
<li><strong>Double click</strong> - {{ $t("help.doubleClick") }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button type="submit"
|
||||
<button
|
||||
type="submit"
|
||||
@click="$store.commit('closeHovers')"
|
||||
class="button button--flat"
|
||||
:aria-label="$t('buttons.ok')"
|
||||
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
|
||||
:title="$t('buttons.ok')"
|
||||
>
|
||||
{{ $t("buttons.ok") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default { name: 'help' }
|
||||
export default { name: "help" };
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,99 +1,152 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.fileInfo') }}</h2>
|
||||
<h2>{{ $t("prompts.fileInfo") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p v-if="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p>
|
||||
<p v-if="selected.length > 1">
|
||||
{{ $t("prompts.filesSelected", { count: selected.length }) }}
|
||||
</p>
|
||||
|
||||
<p class="break-word" v-if="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name }}</p>
|
||||
<p v-if="!dir || selected.length > 1"><strong>{{ $t('prompts.size') }}:</strong> <span id="content_length"></span> {{ humanSize }}</p>
|
||||
<p v-if="selected.length < 2"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime }}</p>
|
||||
<p class="break-word" v-if="selected.length < 2">
|
||||
<strong>{{ $t("prompts.displayName") }}</strong> {{ name }}
|
||||
</p>
|
||||
<p v-if="!dir || selected.length > 1">
|
||||
<strong>{{ $t("prompts.size") }}:</strong>
|
||||
<span id="content_length"></span> {{ humanSize }}
|
||||
</p>
|
||||
<p v-if="selected.length < 2" :title="modTime">
|
||||
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
|
||||
</p>
|
||||
|
||||
<template v-if="dir && selected.length === 0">
|
||||
<p><strong>{{ $t('prompts.numberFiles') }}:</strong> {{ req.numFiles }}</p>
|
||||
<p><strong>{{ $t('prompts.numberDirs') }}:</strong> {{ req.numDirs }}</p>
|
||||
<p>
|
||||
<strong>{{ $t("prompts.numberFiles") }}:</strong> {{ req.numFiles }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ $t("prompts.numberDirs") }}:</strong> {{ req.numDirs }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template v-if="!dir">
|
||||
<p><strong>MD5: </strong><code><a @click="checksum($event, 'md5')">{{ $t('prompts.show') }}</a></code></p>
|
||||
<p><strong>SHA1: </strong><code><a @click="checksum($event, 'sha1')">{{ $t('prompts.show') }}</a></code></p>
|
||||
<p><strong>SHA256: </strong><code><a @click="checksum($event, 'sha256')">{{ $t('prompts.show') }}</a></code></p>
|
||||
<p><strong>SHA512: </strong><code><a @click="checksum($event, 'sha512')">{{ $t('prompts.show') }}</a></code></p>
|
||||
<p>
|
||||
<strong>MD5: </strong
|
||||
><code
|
||||
><a @click="checksum($event, 'md5')">{{
|
||||
$t("prompts.show")
|
||||
}}</a></code
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<strong>SHA1: </strong
|
||||
><code
|
||||
><a @click="checksum($event, 'sha1')">{{
|
||||
$t("prompts.show")
|
||||
}}</a></code
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<strong>SHA256: </strong
|
||||
><code
|
||||
><a @click="checksum($event, 'sha256')">{{
|
||||
$t("prompts.show")
|
||||
}}</a></code
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<strong>SHA512: </strong
|
||||
><code
|
||||
><a @click="checksum($event, 'sha512')">{{
|
||||
$t("prompts.show")
|
||||
}}</a></code
|
||||
>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button type="submit"
|
||||
<button
|
||||
type="submit"
|
||||
@click="$store.commit('closeHovers')"
|
||||
class="button button--flat"
|
||||
:aria-label="$t('buttons.ok')"
|
||||
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
|
||||
:title="$t('buttons.ok')"
|
||||
>
|
||||
{{ $t("buttons.ok") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState, mapGetters} from 'vuex'
|
||||
import filesize from 'filesize'
|
||||
import moment from 'moment'
|
||||
import { files as api } from '@/api'
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import filesize from "filesize";
|
||||
import moment from "moment";
|
||||
import { files as api } from "@/api";
|
||||
|
||||
export default {
|
||||
name: 'info',
|
||||
name: "info",
|
||||
computed: {
|
||||
...mapState(['req', 'selected']),
|
||||
...mapGetters(['selectedCount', 'isListing']),
|
||||
...mapState(["req", "selected"]),
|
||||
...mapGetters(["selectedCount", "isListing"]),
|
||||
humanSize: function () {
|
||||
if (this.selectedCount === 0 || !this.isListing) {
|
||||
return filesize(this.req.size)
|
||||
return filesize(this.req.size);
|
||||
}
|
||||
|
||||
let sum = 0
|
||||
let sum = 0;
|
||||
|
||||
for (let selected of this.selected) {
|
||||
sum += this.req.items[selected].size
|
||||
sum += this.req.items[selected].size;
|
||||
}
|
||||
|
||||
return filesize(sum)
|
||||
return filesize(sum);
|
||||
},
|
||||
humanTime: function () {
|
||||
if (this.selectedCount === 0) {
|
||||
return moment(this.req.modified).fromNow()
|
||||
return moment(this.req.modified).fromNow();
|
||||
}
|
||||
|
||||
return moment(this.req.items[this.selected[0]].modified).fromNow()
|
||||
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 : this.req.items[this.selected[0]].name
|
||||
return this.selectedCount === 0
|
||||
? this.req.name
|
||||
: this.req.items[this.selected[0]].name;
|
||||
},
|
||||
dir: function () {
|
||||
return this.selectedCount > 1 || (this.selectedCount === 0
|
||||
? this.req.isDir
|
||||
: this.req.items[this.selected[0]].isDir)
|
||||
}
|
||||
return (
|
||||
this.selectedCount > 1 ||
|
||||
(this.selectedCount === 0
|
||||
? this.req.isDir
|
||||
: this.req.items[this.selected[0]].isDir)
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
checksum: async function (event, algo) {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
let link
|
||||
let link;
|
||||
|
||||
if (this.selectedCount) {
|
||||
link = this.req.items[this.selected[0]].url
|
||||
link = this.req.items[this.selected[0]].url;
|
||||
} else {
|
||||
link = this.$route.path
|
||||
link = this.$route.path;
|
||||
}
|
||||
|
||||
try {
|
||||
const hash = await api.checksum(link, algo)
|
||||
const hash = await api.checksum(link, algo);
|
||||
// eslint-disable-next-line
|
||||
event.target.innerHTML = hash
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
this.$showError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,93 +1,104 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.move') }}</h2>
|
||||
<h2>{{ $t("prompts.move") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<file-list @update:selected="val => dest = val"></file-list>
|
||||
<file-list @update:selected="(val) => (dest = val)"></file-list>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="button button--flat"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="move"
|
||||
:disabled="$route.path === dest"
|
||||
:aria-label="$t('buttons.move')"
|
||||
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
|
||||
:title="$t('buttons.move')"
|
||||
>
|
||||
{{ $t("buttons.move") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import FileList from './FileList'
|
||||
import { files as api } from '@/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
import * as upload from '@/utils/upload'
|
||||
import { mapState } from "vuex";
|
||||
import FileList from "./FileList";
|
||||
import { files as api } from "@/api";
|
||||
import buttons from "@/utils/buttons";
|
||||
import * as upload from "@/utils/upload";
|
||||
|
||||
export default {
|
||||
name: 'move',
|
||||
name: "move",
|
||||
components: { FileList },
|
||||
data: function () {
|
||||
return {
|
||||
current: window.location.pathname,
|
||||
dest: null
|
||||
}
|
||||
dest: null,
|
||||
};
|
||||
},
|
||||
computed: mapState(['req', 'selected']),
|
||||
computed: mapState(["req", "selected"]),
|
||||
methods: {
|
||||
move: async function (event) {
|
||||
event.preventDefault()
|
||||
let items = []
|
||||
event.preventDefault();
|
||||
let items = [];
|
||||
|
||||
for (let item of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
||||
name: this.req.items[item].name
|
||||
})
|
||||
name: this.req.items[item].name,
|
||||
});
|
||||
}
|
||||
|
||||
let action = async (overwrite, rename) => {
|
||||
buttons.loading('move')
|
||||
buttons.loading("move");
|
||||
|
||||
await api.move(items, overwrite, rename).then(() => {
|
||||
buttons.success('move')
|
||||
this.$router.push({ path: this.dest })
|
||||
}).catch((e) => {
|
||||
buttons.done('move')
|
||||
this.$showError(e)
|
||||
})
|
||||
}
|
||||
await api
|
||||
.move(items, overwrite, rename)
|
||||
.then(() => {
|
||||
buttons.success("move");
|
||||
this.$router.push({ path: this.dest });
|
||||
})
|
||||
.catch((e) => {
|
||||
buttons.done("move");
|
||||
this.$showError(e);
|
||||
});
|
||||
};
|
||||
|
||||
let dstItems = (await api.fetch(this.dest)).items
|
||||
let conflict = upload.checkConflict(items, dstItems)
|
||||
let dstItems = (await api.fetch(this.dest)).items;
|
||||
let conflict = upload.checkConflict(items, dstItems);
|
||||
|
||||
let overwrite = false
|
||||
let rename = false
|
||||
let overwrite = false;
|
||||
let rename = false;
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'replace-rename',
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace-rename",
|
||||
confirm: (event, option) => {
|
||||
overwrite = option == 'overwrite'
|
||||
rename = option == 'rename'
|
||||
overwrite = option == "overwrite";
|
||||
rename = option == "rename";
|
||||
|
||||
event.preventDefault()
|
||||
this.$store.commit('closeHovers')
|
||||
action(overwrite, rename)
|
||||
}
|
||||
})
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
action(overwrite, rename);
|
||||
},
|
||||
});
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
action(overwrite, rename)
|
||||
}
|
||||
}
|
||||
}
|
||||
action(overwrite, rename);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.newDir') }}</h2>
|
||||
<h2>{{ $t("prompts.newDir") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.newDirMessage') }}</p>
|
||||
<input class="input input--block" type="text" @keyup.enter="submit" v-model.trim="name" v-focus>
|
||||
<p>{{ $t("prompts.newDirMessage") }}</p>
|
||||
<input
|
||||
class="input input--block"
|
||||
type="text"
|
||||
@keyup.enter="submit"
|
||||
v-model.trim="name"
|
||||
v-focus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
@@ -15,57 +21,60 @@
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>{{ $t('buttons.cancel') }}</button>
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
:aria-label="$t('buttons.create')"
|
||||
:title="$t('buttons.create')"
|
||||
@click="submit"
|
||||
>{{ $t('buttons.create') }}</button>
|
||||
>
|
||||
{{ $t("buttons.create") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { files as api } from '@/api'
|
||||
import url from '@/utils/url'
|
||||
import { mapGetters } from "vuex";
|
||||
import { files as api } from "@/api";
|
||||
import url from "@/utils/url";
|
||||
|
||||
export default {
|
||||
name: 'new-dir',
|
||||
data: function() {
|
||||
name: "new-dir",
|
||||
data: function () {
|
||||
return {
|
||||
name: ''
|
||||
name: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([ 'isFiles', 'isListing' ])
|
||||
...mapGetters(["isFiles", "isListing"]),
|
||||
},
|
||||
methods: {
|
||||
submit: async function(event) {
|
||||
event.preventDefault()
|
||||
if (this.new === '') return
|
||||
submit: async function (event) {
|
||||
event.preventDefault();
|
||||
if (this.new === "") return;
|
||||
|
||||
// Build the path of the new directory.
|
||||
let uri = this.isFiles ? this.$route.path + '/' : '/'
|
||||
let uri = this.isFiles ? this.$route.path + "/" : "/";
|
||||
|
||||
if (!this.isListing) {
|
||||
uri = url.removeLastDir(uri) + '/'
|
||||
uri = url.removeLastDir(uri) + "/";
|
||||
}
|
||||
|
||||
uri += encodeURIComponent(this.name) + '/'
|
||||
uri = uri.replace('//', '/')
|
||||
uri += encodeURIComponent(this.name) + "/";
|
||||
uri = uri.replace("//", "/");
|
||||
|
||||
try {
|
||||
await api.post(uri)
|
||||
this.$router.push({ path: uri })
|
||||
await api.post(uri);
|
||||
this.$router.push({ path: uri });
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
this.$showError(e);
|
||||
}
|
||||
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.newFile') }}</h2>
|
||||
<h2>{{ $t("prompts.newFile") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.newFileMessage') }}</p>
|
||||
<input class="input input--block" v-focus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||
<p>{{ $t("prompts.newFileMessage") }}</p>
|
||||
<input
|
||||
class="input input--block"
|
||||
v-focus
|
||||
type="text"
|
||||
@keyup.enter="submit"
|
||||
v-model.trim="name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
@@ -15,57 +21,60 @@
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>{{ $t('buttons.cancel') }}</button>
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="submit"
|
||||
:aria-label="$t('buttons.create')"
|
||||
:title="$t('buttons.create')"
|
||||
>{{ $t('buttons.create') }}</button>
|
||||
>
|
||||
{{ $t("buttons.create") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { files as api } from '@/api'
|
||||
import url from '@/utils/url'
|
||||
import { mapGetters } from "vuex";
|
||||
import { files as api } from "@/api";
|
||||
import url from "@/utils/url";
|
||||
|
||||
export default {
|
||||
name: 'new-file',
|
||||
data: function() {
|
||||
name: "new-file",
|
||||
data: function () {
|
||||
return {
|
||||
name: ''
|
||||
name: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([ 'isFiles', 'isListing' ])
|
||||
...mapGetters(["isFiles", "isListing"]),
|
||||
},
|
||||
methods: {
|
||||
submit: async function(event) {
|
||||
event.preventDefault()
|
||||
if (this.new === '') return
|
||||
submit: async function (event) {
|
||||
event.preventDefault();
|
||||
if (this.new === "") return;
|
||||
|
||||
// Build the path of the new directory.
|
||||
let uri = this.isFiles ? this.$route.path + '/' : '/'
|
||||
let uri = this.isFiles ? this.$route.path + "/" : "/";
|
||||
|
||||
if (!this.isListing) {
|
||||
uri = url.removeLastDir(uri) + '/'
|
||||
uri = url.removeLastDir(uri) + "/";
|
||||
}
|
||||
|
||||
uri += encodeURIComponent(this.name)
|
||||
uri = uri.replace('//', '/')
|
||||
uri += encodeURIComponent(this.name);
|
||||
uri = uri.replace("//", "/");
|
||||
|
||||
try {
|
||||
await api.post(uri)
|
||||
this.$router.push({ path: uri })
|
||||
await api.post(uri);
|
||||
this.$router.push({ path: uri });
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
this.$showError(e);
|
||||
}
|
||||
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,25 +6,25 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Help from './Help'
|
||||
import Info from './Info'
|
||||
import Delete from './Delete'
|
||||
import Rename from './Rename'
|
||||
import Download from './Download'
|
||||
import Move from './Move'
|
||||
import Copy from './Copy'
|
||||
import NewFile from './NewFile'
|
||||
import NewDir from './NewDir'
|
||||
import Replace from './Replace'
|
||||
import ReplaceRename from './ReplaceRename'
|
||||
import Share from './Share'
|
||||
import Upload from './Upload'
|
||||
import ShareDelete from './ShareDelete'
|
||||
import { mapState } from 'vuex'
|
||||
import buttons from '@/utils/buttons'
|
||||
import Help from "./Help";
|
||||
import Info from "./Info";
|
||||
import Delete from "./Delete";
|
||||
import Rename from "./Rename";
|
||||
import Download from "./Download";
|
||||
import Move from "./Move";
|
||||
import Copy from "./Copy";
|
||||
import NewFile from "./NewFile";
|
||||
import NewDir from "./NewDir";
|
||||
import Replace from "./Replace";
|
||||
import ReplaceRename from "./ReplaceRename";
|
||||
import Share from "./Share";
|
||||
import Upload from "./Upload";
|
||||
import ShareDelete from "./ShareDelete";
|
||||
import { mapState } from "vuex";
|
||||
import buttons from "@/utils/buttons";
|
||||
|
||||
export default {
|
||||
name: 'prompts',
|
||||
name: "prompts",
|
||||
components: {
|
||||
Info,
|
||||
Delete,
|
||||
@@ -39,74 +39,81 @@ export default {
|
||||
Replace,
|
||||
ReplaceRename,
|
||||
Upload,
|
||||
ShareDelete
|
||||
ShareDelete,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
pluginData: {
|
||||
buttons,
|
||||
'store': this.$store,
|
||||
'router': this.$router
|
||||
}
|
||||
}
|
||||
store: this.$store,
|
||||
router: this.$router,
|
||||
},
|
||||
};
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', (event) => {
|
||||
if (this.show == null)
|
||||
return
|
||||
created() {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (this.show == null) return;
|
||||
|
||||
let prompt = this.$refs.currentComponent;
|
||||
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
event.stopImmediatePropagation();
|
||||
this.$store.commit("closeHovers");
|
||||
}
|
||||
|
||||
// Enter
|
||||
if (event.keyCode == 13) {
|
||||
switch (this.show) {
|
||||
case 'delete':
|
||||
prompt.submit()
|
||||
case "delete":
|
||||
prompt.submit();
|
||||
break;
|
||||
case 'copy':
|
||||
prompt.copy(event)
|
||||
case "copy":
|
||||
prompt.copy(event);
|
||||
break;
|
||||
case 'move':
|
||||
prompt.move(event)
|
||||
case "move":
|
||||
prompt.move(event);
|
||||
break;
|
||||
case 'replace':
|
||||
prompt.showConfirm(event)
|
||||
case "replace":
|
||||
prompt.showConfirm(event);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
...mapState(['show', 'plugins']),
|
||||
...mapState(["show", "plugins"]),
|
||||
currentComponent: function () {
|
||||
const matched = [
|
||||
'info',
|
||||
'help',
|
||||
'delete',
|
||||
'rename',
|
||||
'move',
|
||||
'copy',
|
||||
'newFile',
|
||||
'newDir',
|
||||
'download',
|
||||
'replace',
|
||||
'replace-rename',
|
||||
'share',
|
||||
'upload',
|
||||
'share-delete'
|
||||
].indexOf(this.show) >= 0;
|
||||
const matched =
|
||||
[
|
||||
"info",
|
||||
"help",
|
||||
"delete",
|
||||
"rename",
|
||||
"move",
|
||||
"copy",
|
||||
"newFile",
|
||||
"newDir",
|
||||
"download",
|
||||
"replace",
|
||||
"replace-rename",
|
||||
"share",
|
||||
"upload",
|
||||
"share-delete",
|
||||
].indexOf(this.show) >= 0;
|
||||
|
||||
return matched && this.show || null;
|
||||
return (matched && this.show) || null;
|
||||
},
|
||||
showOverlay: function () {
|
||||
return (this.show !== null && this.show !== 'search' && this.show !== 'more')
|
||||
}
|
||||
return (
|
||||
this.show !== null && this.show !== "search" && this.show !== "more"
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetPrompts () {
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
}
|
||||
resetPrompts() {
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,89 +1,107 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.rename') }}</h2>
|
||||
<h2>{{ $t("prompts.rename") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
|
||||
<input class="input input--block" v-focus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||
<p>
|
||||
{{ $t("prompts.renameMessage") }} <code>{{ oldName() }}</code
|
||||
>:
|
||||
</p>
|
||||
<input
|
||||
class="input input--block"
|
||||
v-focus
|
||||
type="text"
|
||||
@keyup.enter="submit"
|
||||
v-model.trim="name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button @click="submit"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
@click="submit"
|
||||
class="button button--flat"
|
||||
type="submit"
|
||||
:aria-label="$t('buttons.rename')"
|
||||
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
|
||||
:title="$t('buttons.rename')"
|
||||
>
|
||||
{{ $t("buttons.rename") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import url from '@/utils/url'
|
||||
import { files as api } from '@/api'
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import url from "@/utils/url";
|
||||
import { files as api } from "@/api";
|
||||
|
||||
export default {
|
||||
name: 'rename',
|
||||
name: "rename",
|
||||
data: function () {
|
||||
return {
|
||||
name: ''
|
||||
}
|
||||
name: "",
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.name = this.oldName()
|
||||
created() {
|
||||
this.name = this.oldName();
|
||||
},
|
||||
computed: {
|
||||
...mapState(['req', 'selected', 'selectedCount']),
|
||||
...mapGetters(['isListing'])
|
||||
...mapState(["req", "selected", "selectedCount"]),
|
||||
...mapGetters(["isListing"]),
|
||||
},
|
||||
methods: {
|
||||
cancel: function () {
|
||||
this.$store.commit('closeHovers')
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
oldName: function () {
|
||||
if (!this.isListing) {
|
||||
return this.req.name
|
||||
return this.req.name;
|
||||
}
|
||||
|
||||
if (this.selectedCount === 0 || this.selectedCount > 1) {
|
||||
// This shouldn't happen.
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
return this.req.items[this.selected[0]].name
|
||||
return this.req.items[this.selected[0]].name;
|
||||
},
|
||||
submit: async function () {
|
||||
let oldLink = ''
|
||||
let newLink = ''
|
||||
let oldLink = "";
|
||||
let newLink = "";
|
||||
|
||||
if (!this.isListing) {
|
||||
oldLink = this.req.url
|
||||
oldLink = this.req.url;
|
||||
} else {
|
||||
oldLink = this.req.items[this.selected[0]].url
|
||||
oldLink = this.req.items[this.selected[0]].url;
|
||||
}
|
||||
|
||||
newLink = url.removeLastDir(oldLink) + '/' + encodeURIComponent(this.name)
|
||||
newLink =
|
||||
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
||||
|
||||
try {
|
||||
await api.move([{ from: oldLink, to: newLink }])
|
||||
await api.move([{ from: oldLink, to: newLink }]);
|
||||
if (!this.isListing) {
|
||||
this.$router.push({ path: newLink })
|
||||
return
|
||||
this.$router.push({ path: newLink });
|
||||
return;
|
||||
}
|
||||
|
||||
this.$store.commit('setReload', true)
|
||||
this.$store.commit("setReload", true);
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
this.$showError(e);
|
||||
}
|
||||
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
}
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.replace') }}</h2>
|
||||
<h2>{{ $t("prompts.replace") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.replaceMessage') }}</p>
|
||||
<p>{{ $t("prompts.replaceMessage") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="button button--flat button--red"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--red"
|
||||
@click="showConfirm"
|
||||
:aria-label="$t('buttons.replace')"
|
||||
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button>
|
||||
:title="$t('buttons.replace')"
|
||||
>
|
||||
{{ $t("buttons.replace") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'replace',
|
||||
computed: mapState(['showConfirm'])
|
||||
}
|
||||
name: "replace",
|
||||
computed: mapState(["showConfirm"]),
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,35 +1,47 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.replace') }}</h2>
|
||||
<h2>{{ $t("prompts.replace") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.replaceMessage') }}</p>
|
||||
<p>{{ $t("prompts.replaceMessage") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="button button--flat button--blue"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--blue"
|
||||
@click="(event) => showConfirm(event, 'rename')"
|
||||
:aria-label="$t('buttons.rename')"
|
||||
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
|
||||
<button class="button button--flat button--red"
|
||||
:title="$t('buttons.rename')"
|
||||
>
|
||||
{{ $t("buttons.rename") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--red"
|
||||
@click="(event) => showConfirm(event, 'overwrite')"
|
||||
:aria-label="$t('buttons.replace')"
|
||||
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button>
|
||||
:title="$t('buttons.replace')"
|
||||
>
|
||||
{{ $t("buttons.replace") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'replace-rename',
|
||||
computed: mapState(['showConfirm'])
|
||||
}
|
||||
name: "replace-rename",
|
||||
computed: mapState(["showConfirm"]),
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="card floating share__promt__card" id="share">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('buttons.share') }}</h2>
|
||||
<h2>{{ $t("buttons.share") }}</h2>
|
||||
</div>
|
||||
|
||||
<template v-if="listing">
|
||||
@@ -9,7 +9,7 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>{{ $t('settings.shareDuration') }}</th>
|
||||
<th>{{ $t("settings.shareDuration") }}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -17,188 +17,219 @@
|
||||
<tr v-for="link in links" :key="link.hash">
|
||||
<td>{{ link.hash }}</td>
|
||||
<td>
|
||||
<template v-if="link.expire !== 0">{{ humanTime(link.expire) }}</template>
|
||||
<template v-else>{{ $t('permanent') }}</template>
|
||||
<template v-if="link.expire !== 0">{{
|
||||
humanTime(link.expire)
|
||||
}}</template>
|
||||
<template v-else>{{ $t("permanent") }}</template>
|
||||
</td>
|
||||
<td class="small">
|
||||
<button class="action copy-clipboard"
|
||||
<button
|
||||
class="action copy-clipboard"
|
||||
:data-clipboard-text="buildLink(link.hash)"
|
||||
:aria-label="$t('buttons.copyToClipboard')"
|
||||
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
|
||||
:title="$t('buttons.copyToClipboard')"
|
||||
>
|
||||
<i class="material-icons">content_paste</i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="small">
|
||||
<button class="action"
|
||||
<button
|
||||
class="action"
|
||||
@click="deleteLink($event, link)"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
|
||||
:title="$t('buttons.delete')"
|
||||
>
|
||||
<i class="material-icons">delete</i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
|
||||
<button class="button button--flat button--blue"
|
||||
:title="$t('buttons.close')"
|
||||
>
|
||||
{{ $t("buttons.close") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--blue"
|
||||
@click="() => switchListing()"
|
||||
:aria-label="$t('buttons.new')"
|
||||
:title="$t('buttons.new')">{{ $t('buttons.new') }}</button>
|
||||
:title="$t('buttons.new')"
|
||||
>
|
||||
{{ $t("buttons.new") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="card-content">
|
||||
<p>{{ $t('settings.shareDuration') }}</p>
|
||||
<p>{{ $t("settings.shareDuration") }}</p>
|
||||
<div class="input-group input">
|
||||
<input v-focus
|
||||
type="number"
|
||||
max="2147483647"
|
||||
min="1"
|
||||
@keyup.enter="submit"
|
||||
v-model.trim="time">
|
||||
<select class="right" v-model="unit" :aria-label="$t('time.unit')">
|
||||
<option value="seconds">{{ $t('time.seconds') }}</option>
|
||||
<option value="minutes">{{ $t('time.minutes') }}</option>
|
||||
<option value="hours">{{ $t('time.hours') }}</option>
|
||||
<option value="days">{{ $t('time.days') }}</option>
|
||||
</select>
|
||||
<input
|
||||
v-focus
|
||||
type="number"
|
||||
max="2147483647"
|
||||
min="1"
|
||||
@keyup.enter="submit"
|
||||
v-model.trim="time"
|
||||
/>
|
||||
<select class="right" v-model="unit" :aria-label="$t('time.unit')">
|
||||
<option value="seconds">{{ $t("time.seconds") }}</option>
|
||||
<option value="minutes">{{ $t("time.minutes") }}</option>
|
||||
<option value="hours">{{ $t("time.hours") }}</option>
|
||||
<option value="days">{{ $t("time.days") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<p>{{ $t('prompts.optionalPassword') }}</p>
|
||||
<input class="input input--block" type="password" v-model.trim="password">
|
||||
<p>{{ $t("prompts.optionalPassword") }}</p>
|
||||
<input
|
||||
class="input input--block"
|
||||
type="password"
|
||||
v-model.trim="password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="() => switchListing()"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="button button--flat button--blue"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--blue"
|
||||
@click="submit"
|
||||
:aria-label="$t('buttons.share')"
|
||||
:title="$t('buttons.share')">{{ $t('buttons.share') }}</button>
|
||||
:title="$t('buttons.share')"
|
||||
>
|
||||
{{ $t("buttons.share") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import { share as api } from '@/api'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import moment from 'moment'
|
||||
import Clipboard from 'clipboard'
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import { share as api } from "@/api";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import moment from "moment";
|
||||
import Clipboard from "clipboard";
|
||||
|
||||
export default {
|
||||
name: 'share',
|
||||
name: "share",
|
||||
data: function () {
|
||||
return {
|
||||
time: '',
|
||||
unit: 'hours',
|
||||
time: "",
|
||||
unit: "hours",
|
||||
links: [],
|
||||
clip: null,
|
||||
password: '',
|
||||
listing: true
|
||||
}
|
||||
password: "",
|
||||
listing: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'req', 'selected', 'selectedCount' ]),
|
||||
...mapGetters([ 'isListing' ]),
|
||||
url () {
|
||||
...mapState(["req", "selected", "selectedCount"]),
|
||||
...mapGetters(["isListing"]),
|
||||
url() {
|
||||
if (!this.isListing) {
|
||||
return this.$route.path
|
||||
return this.$route.path;
|
||||
}
|
||||
|
||||
if (this.selectedCount === 0 || this.selectedCount > 1) {
|
||||
// This shouldn't happen.
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
return this.req.items[this.selected[0]].url
|
||||
}
|
||||
return this.req.items[this.selected[0]].url;
|
||||
},
|
||||
},
|
||||
async beforeMount () {
|
||||
async beforeMount() {
|
||||
try {
|
||||
const links = await api.get(this.url)
|
||||
this.links = links
|
||||
this.sort()
|
||||
const links = await api.get(this.url);
|
||||
this.links = links;
|
||||
this.sort();
|
||||
|
||||
if (this.links.length == 0) {
|
||||
this.listing = false
|
||||
this.listing = false;
|
||||
}
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
this.$showError(e);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.clip = new Clipboard('.copy-clipboard')
|
||||
this.clip.on('success', () => {
|
||||
this.$showSuccess(this.$t('success.linkCopied'))
|
||||
})
|
||||
mounted() {
|
||||
this.clip = new Clipboard(".copy-clipboard");
|
||||
this.clip.on("success", () => {
|
||||
this.$showSuccess(this.$t("success.linkCopied"));
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.clip.destroy()
|
||||
beforeDestroy() {
|
||||
this.clip.destroy();
|
||||
},
|
||||
methods: {
|
||||
submit: async function () {
|
||||
let isPermanent = !this.time || this.time == 0
|
||||
let isPermanent = !this.time || this.time == 0;
|
||||
|
||||
try {
|
||||
let res = null
|
||||
let res = null;
|
||||
|
||||
if (isPermanent) {
|
||||
res = await api.create(this.url, this.password)
|
||||
res = await api.create(this.url, this.password);
|
||||
} else {
|
||||
res = await api.create(this.url, this.password, this.time, this.unit)
|
||||
res = await api.create(this.url, this.password, this.time, this.unit);
|
||||
}
|
||||
|
||||
this.links.push(res)
|
||||
this.sort()
|
||||
this.links.push(res);
|
||||
this.sort();
|
||||
|
||||
this.time = ''
|
||||
this.unit = 'hours'
|
||||
this.password = ''
|
||||
this.time = "";
|
||||
this.unit = "hours";
|
||||
this.password = "";
|
||||
|
||||
this.listing = true
|
||||
this.listing = true;
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
this.$showError(e);
|
||||
}
|
||||
},
|
||||
deleteLink: async function (event, link) {
|
||||
event.preventDefault()
|
||||
try {
|
||||
await api.remove(link.hash)
|
||||
this.links = this.links.filter(item => item.hash !== link.hash)
|
||||
event.preventDefault();
|
||||
try {
|
||||
await api.remove(link.hash);
|
||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||
|
||||
if (this.links.length == 0) {
|
||||
this.listing = false
|
||||
this.listing = false;
|
||||
}
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
this.$showError(e);
|
||||
}
|
||||
},
|
||||
humanTime (time) {
|
||||
return moment(time * 1000).fromNow()
|
||||
humanTime(time) {
|
||||
return moment(time * 1000).fromNow();
|
||||
},
|
||||
buildLink (hash) {
|
||||
return `${window.location.origin}${baseURL}/share/${hash}`
|
||||
buildLink(hash) {
|
||||
return `${window.location.origin}${baseURL}/share/${hash}`;
|
||||
},
|
||||
sort () {
|
||||
sort() {
|
||||
this.links = this.links.sort((a, b) => {
|
||||
if (a.expire === 0) return -1
|
||||
if (b.expire === 0) return 1
|
||||
return new Date(a.expire) - new Date(b.expire)
|
||||
})
|
||||
if (a.expire === 0) return -1;
|
||||
if (b.expire === 0) return 1;
|
||||
return new Date(a.expire) - new Date(b.expire);
|
||||
});
|
||||
},
|
||||
switchListing () {
|
||||
switchListing() {
|
||||
if (this.links.length == 0 && !this.listing) {
|
||||
this.$store.commit('closeHovers')
|
||||
this.$store.commit("closeHovers");
|
||||
}
|
||||
|
||||
this.listing = !this.listing
|
||||
}
|
||||
}
|
||||
}
|
||||
this.listing = !this.listing;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.deleteMessageShare', {path: ''}) }}</p>
|
||||
<p>{{ $t("prompts.deleteMessageShare", { path: "" }) }}</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button @click="$store.commit('closeHovers')"
|
||||
<button
|
||||
@click="$store.commit('closeHovers')"
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button @click="submit"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
@click="submit"
|
||||
class="button button--flat button--red"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
||||
:title="$t('buttons.delete')"
|
||||
>
|
||||
{{ $t("buttons.delete") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'share-delete',
|
||||
name: "share-delete",
|
||||
computed: {
|
||||
...mapState(['showConfirm'])
|
||||
...mapState(["showConfirm"]),
|
||||
},
|
||||
methods: {
|
||||
submit: function () {
|
||||
this.showConfirm()
|
||||
}
|
||||
}
|
||||
}
|
||||
this.showConfirm();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.upload') }}</h2>
|
||||
<h2>{{ $t("prompts.upload") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.uploadMessage') }}</p>
|
||||
<p>{{ $t("prompts.uploadMessage") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-action full">
|
||||
@@ -22,18 +22,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'upload',
|
||||
name: "upload",
|
||||
methods: {
|
||||
uploadFile: function () {
|
||||
document.getElementById('upload-input').value = ''
|
||||
document.getElementById('upload-input').click()
|
||||
document.getElementById("upload-input").value = "";
|
||||
document.getElementById("upload-input").click();
|
||||
},
|
||||
uploadFolder: function () {
|
||||
document.getElementById('upload-folder-input').value = ''
|
||||
document.getElementById('upload-folder-input').click()
|
||||
}
|
||||
}
|
||||
}
|
||||
document.getElementById("upload-folder-input").value = "";
|
||||
document.getElementById("upload-folder-input").click();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3>{{ $t('settings.userCommands') }}</h3>
|
||||
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
|
||||
<input class="input input--block" type="text" v-model.trim="raw">
|
||||
<h3>{{ $t("settings.userCommands") }}</h3>
|
||||
<p class="small">
|
||||
{{ $t("settings.userCommandsHelp") }} <i>git svn hg</i>.
|
||||
</p>
|
||||
<input class="input input--block" type="text" v-model.trim="raw" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'permissions',
|
||||
props: ['commands'],
|
||||
name: "permissions",
|
||||
props: ["commands"],
|
||||
computed: {
|
||||
raw: {
|
||||
get () {
|
||||
return this.commands.join(' ')
|
||||
get() {
|
||||
return this.commands.join(" ");
|
||||
},
|
||||
set (value) {
|
||||
if (value !== '') {
|
||||
this.$emit('update:commands', value.split(' '))
|
||||
set(value) {
|
||||
if (value !== "") {
|
||||
this.$emit("update:commands", value.split(" "));
|
||||
} else {
|
||||
this.$emit('update:commands', [])
|
||||
this.$emit("update:commands", []);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,46 +1,50 @@
|
||||
<template>
|
||||
<select v-on:change="change" :value="locale">
|
||||
<option v-for="(language, value) in locales" :key="value" :value="value">{{ $t('languages.' + language) }}</option>
|
||||
<option v-for="(language, value) in locales" :key="value" :value="value">
|
||||
{{ $t("languages." + language) }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'languages',
|
||||
props: [ 'locale' ],
|
||||
name: "languages",
|
||||
props: ["locale"],
|
||||
data() {
|
||||
let dataObj = {
|
||||
locales: {
|
||||
ar: 'ar',
|
||||
de: 'de',
|
||||
en: 'en',
|
||||
es: 'es',
|
||||
fr: 'fr',
|
||||
is: 'is',
|
||||
it: 'it',
|
||||
ja: 'ja',
|
||||
ko: 'ko',
|
||||
'nl-be': 'nlBE',
|
||||
pl: 'pl',
|
||||
'pt-br': 'ptBR',
|
||||
pt: 'pt',
|
||||
ro: 'ro',
|
||||
ru: 'ru',
|
||||
'sv-se': 'svSE',
|
||||
'zh-cn': 'zhCN',
|
||||
'zh-tw': 'zhTW'
|
||||
}
|
||||
ar: "ar",
|
||||
de: "de",
|
||||
en: "en",
|
||||
es: "es",
|
||||
fr: "fr",
|
||||
is: "is",
|
||||
it: "it",
|
||||
ja: "ja",
|
||||
ko: "ko",
|
||||
"nl-be": "nlBE",
|
||||
pl: "pl",
|
||||
"pt-br": "ptBR",
|
||||
pt: "pt",
|
||||
ro: "ro",
|
||||
ru: "ru",
|
||||
"sv-se": "svSE",
|
||||
"zh-cn": "zhCN",
|
||||
"zh-tw": "zhTW",
|
||||
},
|
||||
};
|
||||
|
||||
Object.defineProperty(dataObj, "locales", { configurable: false, writable: false });
|
||||
Object.defineProperty(dataObj, "locales", {
|
||||
configurable: false,
|
||||
writable: false,
|
||||
});
|
||||
|
||||
return dataObj;
|
||||
},
|
||||
methods: {
|
||||
change (event) {
|
||||
this.$emit('update:locale', event.target.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
change(event) {
|
||||
this.$emit("update:locale", event.target.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,41 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3>{{ $t('settings.permissions') }}</h3>
|
||||
<p class="small">{{ $t('settings.permissionsHelp') }}</p>
|
||||
<h3>{{ $t("settings.permissions") }}</h3>
|
||||
<p class="small">{{ $t("settings.permissionsHelp") }}</p>
|
||||
|
||||
<p><input type="checkbox" v-model="admin"> {{ $t('settings.administrator') }}</p>
|
||||
<p>
|
||||
<input type="checkbox" v-model="admin" />
|
||||
{{ $t("settings.administrator") }}
|
||||
</p>
|
||||
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.create"> {{ $t('settings.perm.create') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p>
|
||||
<p v-if="isExecEnabled"><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p>
|
||||
<p>
|
||||
<input type="checkbox" :disabled="admin" v-model="perm.create" />
|
||||
{{ $t("settings.perm.create") }}
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" :disabled="admin" v-model="perm.delete" />
|
||||
{{ $t("settings.perm.delete") }}
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" :disabled="admin" v-model="perm.download" />
|
||||
{{ $t("settings.perm.download") }}
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" :disabled="admin" v-model="perm.modify" />
|
||||
{{ $t("settings.perm.modify") }}
|
||||
</p>
|
||||
<p v-if="isExecEnabled">
|
||||
<input type="checkbox" :disabled="admin" v-model="perm.execute" />
|
||||
{{ $t("settings.perm.execute") }}
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" :disabled="admin" v-model="perm.rename" />
|
||||
{{ $t("settings.perm.rename") }}
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" :disabled="admin" v-model="perm.share" />
|
||||
{{ $t("settings.perm.share") }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { enableExec } from '@/utils/constants'
|
||||
import { enableExec } from "@/utils/constants";
|
||||
export default {
|
||||
name: 'permissions',
|
||||
props: ['perm'],
|
||||
name: "permissions",
|
||||
props: ["perm"],
|
||||
computed: {
|
||||
admin: {
|
||||
get () {
|
||||
return this.perm.admin
|
||||
get() {
|
||||
return this.perm.admin;
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
if (value) {
|
||||
for (const key in this.perm) {
|
||||
this.perm[key] = true
|
||||
this.perm[key] = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.perm.admin = value
|
||||
}
|
||||
this.perm.admin = value;
|
||||
},
|
||||
},
|
||||
isExecEnabled: () => enableExec
|
||||
}
|
||||
}
|
||||
isExecEnabled: () => enableExec,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,57 +1,63 @@
|
||||
<template>
|
||||
<form class="rules small">
|
||||
<div v-for="(rule, index) in rules" :key="index">
|
||||
<input type="checkbox" v-model="rule.regex"><label>Regex</label>
|
||||
<input type="checkbox" v-model="rule.allow"><label>Allow</label>
|
||||
<input type="checkbox" v-model="rule.regex" /><label>Regex</label>
|
||||
<input type="checkbox" v-model="rule.allow" /><label>Allow</label>
|
||||
|
||||
<input
|
||||
@keypress.enter.prevent
|
||||
type="text"
|
||||
v-if="rule.regex"
|
||||
v-model="rule.regexp.raw"
|
||||
:placeholder="$t('settings.insertRegex')" />
|
||||
:placeholder="$t('settings.insertRegex')"
|
||||
/>
|
||||
<input
|
||||
@keypress.enter.prevent
|
||||
type="text"
|
||||
v-else
|
||||
v-model="rule.path"
|
||||
:placeholder="$t('settings.insertPath')" />
|
||||
:placeholder="$t('settings.insertPath')"
|
||||
/>
|
||||
|
||||
<button class="button button--red" @click="remove($event, index)">-</button>
|
||||
<button class="button button--red" @click="remove($event, index)">
|
||||
-
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="button" @click="create" default="false">{{ $t('buttons.new') }}</button>
|
||||
<button class="button" @click="create" default="false">
|
||||
{{ $t("buttons.new") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'rules-textarea',
|
||||
props: ['rules'],
|
||||
name: "rules-textarea",
|
||||
props: ["rules"],
|
||||
methods: {
|
||||
remove (event, index) {
|
||||
event.preventDefault()
|
||||
let rules = [ ...this.rules ]
|
||||
rules.splice(index, 1)
|
||||
this.$emit('update:rules', [ ...rules ])
|
||||
remove(event, index) {
|
||||
event.preventDefault();
|
||||
let rules = [...this.rules];
|
||||
rules.splice(index, 1);
|
||||
this.$emit("update:rules", [...rules]);
|
||||
},
|
||||
create (event) {
|
||||
event.preventDefault()
|
||||
create(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.$emit('update:rules', [
|
||||
this.$emit("update:rules", [
|
||||
...this.rules,
|
||||
{
|
||||
allow: true,
|
||||
path: '',
|
||||
path: "",
|
||||
regex: false,
|
||||
regexp: {
|
||||
raw: ''
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
raw: "",
|
||||
},
|
||||
},
|
||||
]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<select v-on:change="change" :value="theme">
|
||||
<option value="">{{ $t('settings.themes.light') }}</option>
|
||||
<option value="dark">{{ $t('settings.themes.dark') }}</option>
|
||||
<option value="">{{ $t("settings.themes.light") }}</option>
|
||||
<option value="dark">{{ $t("settings.themes.dark") }}</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'themes',
|
||||
props: [ 'theme' ],
|
||||
name: "themes",
|
||||
props: ["theme"],
|
||||
methods: {
|
||||
change (event) {
|
||||
this.$emit('update:theme', event.target.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
change(event) {
|
||||
this.$emit("update:theme", event.target.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,67 +1,92 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="!isDefault">
|
||||
<label for="username">{{ $t('settings.username') }}</label>
|
||||
<input class="input input--block" type="text" v-model="user.username" id="username">
|
||||
<label for="username">{{ $t("settings.username") }}</label>
|
||||
<input
|
||||
class="input input--block"
|
||||
type="text"
|
||||
v-model="user.username"
|
||||
id="username"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p v-if="!isDefault">
|
||||
<label for="password">{{ $t('settings.password') }}</label>
|
||||
<input class="input input--block" type="password" :placeholder="passwordPlaceholder" v-model="user.password" id="password">
|
||||
<label for="password">{{ $t("settings.password") }}</label>
|
||||
<input
|
||||
class="input input--block"
|
||||
type="password"
|
||||
:placeholder="passwordPlaceholder"
|
||||
v-model="user.password"
|
||||
id="password"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="scope">{{ $t('settings.scope') }}</label>
|
||||
<input class="input input--block" type="text" v-model="user.scope" id="scope">
|
||||
<label for="scope">{{ $t("settings.scope") }}</label>
|
||||
<input
|
||||
class="input input--block"
|
||||
type="text"
|
||||
v-model="user.scope"
|
||||
id="scope"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="locale">{{ $t('settings.language') }}</label>
|
||||
<languages class="input input--block" id="locale" :locale.sync="user.locale"></languages>
|
||||
<label for="locale">{{ $t("settings.language") }}</label>
|
||||
<languages
|
||||
class="input input--block"
|
||||
id="locale"
|
||||
:locale.sync="user.locale"
|
||||
></languages>
|
||||
</p>
|
||||
|
||||
<p v-if="!isDefault">
|
||||
<input type="checkbox" :disabled="user.perm.admin" v-model="user.lockPassword"> {{ $t('settings.lockPassword') }}
|
||||
<input
|
||||
type="checkbox"
|
||||
:disabled="user.perm.admin"
|
||||
v-model="user.lockPassword"
|
||||
/>
|
||||
{{ $t("settings.lockPassword") }}
|
||||
</p>
|
||||
|
||||
<permissions :perm.sync="user.perm" />
|
||||
<commands v-if="isExecEnabled" :commands.sync="user.commands" />
|
||||
|
||||
<div v-if="!isDefault">
|
||||
<h3>{{ $t('settings.rules') }}</h3>
|
||||
<p class="small">{{ $t('settings.rulesHelp') }}</p>
|
||||
<h3>{{ $t("settings.rules") }}</h3>
|
||||
<p class="small">{{ $t("settings.rulesHelp") }}</p>
|
||||
<rules :rules.sync="user.rules" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Languages from './Languages'
|
||||
import Rules from './Rules'
|
||||
import Permissions from './Permissions'
|
||||
import Commands from './Commands'
|
||||
import { enableExec } from '@/utils/constants'
|
||||
import Languages from "./Languages";
|
||||
import Rules from "./Rules";
|
||||
import Permissions from "./Permissions";
|
||||
import Commands from "./Commands";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
name: 'user',
|
||||
name: "user",
|
||||
components: {
|
||||
Permissions,
|
||||
Languages,
|
||||
Rules,
|
||||
Commands
|
||||
Commands,
|
||||
},
|
||||
props: [ 'user', 'isNew', 'isDefault' ],
|
||||
props: ["user", "isNew", "isDefault"],
|
||||
computed: {
|
||||
passwordPlaceholder () {
|
||||
return this.isNew ? '' : this.$t('settings.avoidChanges')
|
||||
passwordPlaceholder() {
|
||||
return this.isNew ? "" : this.$t("settings.avoidChanges");
|
||||
},
|
||||
isExecEnabled: () => enableExec
|
||||
isExecEnabled: () => enableExec,
|
||||
},
|
||||
watch: {
|
||||
'user.perm.admin': function () {
|
||||
if (!this.user.perm.admin) return
|
||||
this.user.lockPassword = false
|
||||
}
|
||||
}
|
||||
}
|
||||
"user.perm.admin": function () {
|
||||
if (!this.user.perm.admin) return;
|
||||
this.user.lockPassword = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -204,12 +246,26 @@
|
||||
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 {
|
||||
background-color: #fafafa;
|
||||
position: fixed;
|
||||
margin-top: 4em;
|
||||
padding-top: 4em;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
@@ -217,11 +273,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#previewer .loading {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#editor-container #editor {
|
||||
height: calc(100vh - 8.4em);
|
||||
}
|
||||
@@ -283,7 +334,6 @@
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
-webkit-transform: rotate(-360deg);
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
"switchView": "Switch view",
|
||||
"toggleSidebar": "Toggle sidebar",
|
||||
"update": "Update",
|
||||
"upload": "Upload"
|
||||
"upload": "Upload",
|
||||
"openFile": "Open file"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download File",
|
||||
@@ -43,7 +44,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",
|
||||
@@ -214,6 +216,7 @@
|
||||
"settingsUpdated": "Settings updated!",
|
||||
"shareDuration": "Share Duration",
|
||||
"shareManagement": "Share Management",
|
||||
"shareDeleted": "Share deleted!",
|
||||
"singleClick": "Use single clicks to open files and directories",
|
||||
"themes": {
|
||||
"dark": "Dark",
|
||||
|
||||
@@ -1,116 +1,116 @@
|
||||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import Vue from "vue";
|
||||
import VueI18n from "vue-i18n";
|
||||
|
||||
import ar from './ar.json'
|
||||
import de from './de.json'
|
||||
import en from './en.json'
|
||||
import es from './es.json'
|
||||
import fr from './fr.json'
|
||||
import is from './is.json'
|
||||
import it from './it.json'
|
||||
import ja from './ja.json'
|
||||
import ko from './ko.json'
|
||||
import nlBE from './nl-be.json'
|
||||
import pl from './pl.json'
|
||||
import pt from './pt.json'
|
||||
import ptBR from './pt-br.json'
|
||||
import ro from './ro.json'
|
||||
import ru from './ru.json'
|
||||
import svSE from './sv-se.json'
|
||||
import zhCN from './zh-cn.json'
|
||||
import zhTW from './zh-tw.json'
|
||||
import ar from "./ar.json";
|
||||
import de from "./de.json";
|
||||
import en from "./en.json";
|
||||
import es from "./es.json";
|
||||
import fr from "./fr.json";
|
||||
import is from "./is.json";
|
||||
import it from "./it.json";
|
||||
import ja from "./ja.json";
|
||||
import ko from "./ko.json";
|
||||
import nlBE from "./nl-be.json";
|
||||
import pl from "./pl.json";
|
||||
import pt from "./pt.json";
|
||||
import ptBR from "./pt-br.json";
|
||||
import ro from "./ro.json";
|
||||
import ru from "./ru.json";
|
||||
import svSE from "./sv-se.json";
|
||||
import zhCN from "./zh-cn.json";
|
||||
import zhTW from "./zh-tw.json";
|
||||
|
||||
Vue.use(VueI18n)
|
||||
Vue.use(VueI18n);
|
||||
|
||||
export function detectLocale () {
|
||||
let locale = (navigator.language || navigator.browserLangugae).toLowerCase()
|
||||
export function detectLocale() {
|
||||
let locale = (navigator.language || navigator.browserLangugae).toLowerCase();
|
||||
switch (true) {
|
||||
case /^ar.*/i.test(locale):
|
||||
locale = 'ar'
|
||||
break
|
||||
locale = "ar";
|
||||
break;
|
||||
case /^es.*/i.test(locale):
|
||||
locale = 'es'
|
||||
break
|
||||
locale = "es";
|
||||
break;
|
||||
case /^en.*/i.test(locale):
|
||||
locale = 'en'
|
||||
break
|
||||
locale = "en";
|
||||
break;
|
||||
case /^it.*/i.test(locale):
|
||||
locale = 'it'
|
||||
break
|
||||
locale = "it";
|
||||
break;
|
||||
case /^fr.*/i.test(locale):
|
||||
locale = 'fr'
|
||||
break
|
||||
locale = "fr";
|
||||
break;
|
||||
case /^pt.*/i.test(locale):
|
||||
locale = 'pt'
|
||||
break
|
||||
locale = "pt";
|
||||
break;
|
||||
case /^pt-BR.*/i.test(locale):
|
||||
locale = 'pt-br'
|
||||
break
|
||||
locale = "pt-br";
|
||||
break;
|
||||
case /^ja.*/i.test(locale):
|
||||
locale = 'ja'
|
||||
break
|
||||
locale = "ja";
|
||||
break;
|
||||
case /^zh-CN/i.test(locale):
|
||||
locale = 'zh-cn'
|
||||
break
|
||||
locale = "zh-cn";
|
||||
break;
|
||||
case /^zh-TW/i.test(locale):
|
||||
locale = 'zh-tw'
|
||||
break
|
||||
locale = "zh-tw";
|
||||
break;
|
||||
case /^zh.*/i.test(locale):
|
||||
locale = 'zh-cn'
|
||||
break
|
||||
locale = "zh-cn";
|
||||
break;
|
||||
case /^de.*/i.test(locale):
|
||||
locale = 'de'
|
||||
break
|
||||
locale = "de";
|
||||
break;
|
||||
case /^ru.*/i.test(locale):
|
||||
locale = 'ru'
|
||||
break
|
||||
locale = "ru";
|
||||
break;
|
||||
case /^pl.*/i.test(locale):
|
||||
locale = 'pl'
|
||||
break
|
||||
locale = "pl";
|
||||
break;
|
||||
case /^ko.*/i.test(locale):
|
||||
locale = 'ko'
|
||||
break
|
||||
locale = "ko";
|
||||
break;
|
||||
default:
|
||||
locale = 'en'
|
||||
locale = "en";
|
||||
}
|
||||
|
||||
return locale
|
||||
return locale;
|
||||
}
|
||||
|
||||
const removeEmpty = (obj) =>
|
||||
Object.keys(obj)
|
||||
.filter((k) => obj[k] !== null && obj[k] !== undefined && obj[k] !== '') // Remove undef. and null and empty.string.
|
||||
.reduce(
|
||||
(newObj, k) =>
|
||||
typeof obj[k] === 'object'
|
||||
? Object.assign(newObj, { [k]: removeEmpty(obj[k]) }) // Recurse.
|
||||
: Object.assign(newObj, { [k]: obj[k] }), // Copy value.
|
||||
{},
|
||||
);
|
||||
Object.keys(obj)
|
||||
.filter((k) => obj[k] !== null && obj[k] !== undefined && obj[k] !== "") // Remove undef. and null and empty.string.
|
||||
.reduce(
|
||||
(newObj, k) =>
|
||||
typeof obj[k] === "object"
|
||||
? Object.assign(newObj, { [k]: removeEmpty(obj[k]) }) // Recurse.
|
||||
: Object.assign(newObj, { [k]: obj[k] }), // Copy value.
|
||||
{}
|
||||
);
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: detectLocale(),
|
||||
fallbackLocale: 'en',
|
||||
fallbackLocale: "en",
|
||||
messages: {
|
||||
'ar': removeEmpty(ar),
|
||||
'de': removeEmpty(de),
|
||||
'en': en,
|
||||
'es': removeEmpty(es),
|
||||
'fr': removeEmpty(fr),
|
||||
'is': removeEmpty(is),
|
||||
'it': removeEmpty(it),
|
||||
'ja': removeEmpty(ja),
|
||||
'ko': removeEmpty(ko),
|
||||
'nl-be': removeEmpty(nlBE),
|
||||
'pl': removeEmpty(pl),
|
||||
'pt-br': removeEmpty(ptBR),
|
||||
'pt': removeEmpty(pt),
|
||||
'ru': removeEmpty(ru),
|
||||
'ro': removeEmpty(ro),
|
||||
'sv-se': removeEmpty(svSE),
|
||||
'zh-cn': removeEmpty(zhCN),
|
||||
'zh-tw': removeEmpty(zhTW)
|
||||
}
|
||||
})
|
||||
ar: removeEmpty(ar),
|
||||
de: removeEmpty(de),
|
||||
en: en,
|
||||
es: removeEmpty(es),
|
||||
fr: removeEmpty(fr),
|
||||
is: removeEmpty(is),
|
||||
it: removeEmpty(it),
|
||||
ja: removeEmpty(ja),
|
||||
ko: removeEmpty(ko),
|
||||
"nl-be": removeEmpty(nlBE),
|
||||
pl: removeEmpty(pl),
|
||||
"pt-br": removeEmpty(ptBR),
|
||||
pt: removeEmpty(pt),
|
||||
ru: removeEmpty(ru),
|
||||
ro: removeEmpty(ro),
|
||||
"sv-se": removeEmpty(svSE),
|
||||
"zh-cn": removeEmpty(zhCN),
|
||||
"zh-tw": removeEmpty(zhTW),
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n
|
||||
export default i18n;
|
||||
|
||||
@@ -1,43 +1,51 @@
|
||||
import { sync } from 'vuex-router-sync'
|
||||
import store from '@/store'
|
||||
import router from '@/router'
|
||||
import i18n from '@/i18n'
|
||||
import Vue from '@/utils/vue'
|
||||
import { recaptcha, loginPage } from '@/utils/constants'
|
||||
import { login, validateLogin } from '@/utils/auth'
|
||||
import App from '@/App'
|
||||
import "whatwg-fetch";
|
||||
import cssVars from "css-vars-ponyfill";
|
||||
import { sync } from "vuex-router-sync";
|
||||
import store from "@/store";
|
||||
import router from "@/router";
|
||||
import i18n from "@/i18n";
|
||||
import Vue from "@/utils/vue";
|
||||
import { recaptcha, loginPage } from "@/utils/constants";
|
||||
import { login, validateLogin } from "@/utils/auth";
|
||||
import App from "@/App";
|
||||
|
||||
sync(store, router)
|
||||
cssVars();
|
||||
|
||||
async function start () {
|
||||
if (loginPage) {
|
||||
await validateLogin()
|
||||
} else {
|
||||
await login('', '', '')
|
||||
sync(store, router);
|
||||
|
||||
async function start() {
|
||||
try {
|
||||
if (loginPage) {
|
||||
await validateLogin();
|
||||
} else {
|
||||
await login("", "", "");
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if (recaptcha) {
|
||||
await new Promise (resolve => {
|
||||
await new Promise((resolve) => {
|
||||
const check = () => {
|
||||
if (typeof window.grecaptcha === 'undefined') {
|
||||
setTimeout(check, 100)
|
||||
if (typeof window.grecaptcha === "undefined") {
|
||||
setTimeout(check, 100);
|
||||
} else {
|
||||
resolve()
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
check()
|
||||
})
|
||||
check();
|
||||
});
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
el: "#app",
|
||||
store,
|
||||
router,
|
||||
i18n,
|
||||
template: '<App/>',
|
||||
components: { App }
|
||||
})
|
||||
template: "<App/>",
|
||||
components: { App },
|
||||
});
|
||||
}
|
||||
|
||||
start()
|
||||
start();
|
||||
|
||||
@@ -1,166 +1,166 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Login from '@/views/Login'
|
||||
import Layout from '@/views/Layout'
|
||||
import Files from '@/views/Files'
|
||||
import Share from '@/views/Share'
|
||||
import Users from '@/views/settings/Users'
|
||||
import User from '@/views/settings/User'
|
||||
import Settings from '@/views/Settings'
|
||||
import GlobalSettings from '@/views/settings/Global'
|
||||
import ProfileSettings from '@/views/settings/Profile'
|
||||
import Shares from '@/views/settings/Shares'
|
||||
import Errors from '@/views/Errors'
|
||||
import store from '@/store'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import Vue from "vue";
|
||||
import Router from "vue-router";
|
||||
import Login from "@/views/Login";
|
||||
import Layout from "@/views/Layout";
|
||||
import Files from "@/views/Files";
|
||||
import Share from "@/views/Share";
|
||||
import Users from "@/views/settings/Users";
|
||||
import User from "@/views/settings/User";
|
||||
import Settings from "@/views/Settings";
|
||||
import GlobalSettings from "@/views/settings/Global";
|
||||
import ProfileSettings from "@/views/settings/Profile";
|
||||
import Shares from "@/views/settings/Shares";
|
||||
import Errors from "@/views/Errors";
|
||||
import store from "@/store";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
|
||||
Vue.use(Router)
|
||||
Vue.use(Router);
|
||||
|
||||
const router = new Router({
|
||||
base: baseURL,
|
||||
mode: 'history',
|
||||
mode: "history",
|
||||
routes: [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
path: "/login",
|
||||
name: "Login",
|
||||
component: Login,
|
||||
beforeEnter: (to, from, next) => {
|
||||
if (store.getters.isLogged) {
|
||||
return next({ path: '/files' })
|
||||
return next({ path: "/files" });
|
||||
}
|
||||
|
||||
document.title = 'Login'
|
||||
next()
|
||||
}
|
||||
document.title = "Login";
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/*',
|
||||
path: "/*",
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: '/share/*',
|
||||
name: 'Share',
|
||||
component: Share
|
||||
path: "/share/*",
|
||||
name: "Share",
|
||||
component: Share,
|
||||
},
|
||||
{
|
||||
path: '/files/*',
|
||||
name: 'Files',
|
||||
path: "/files/*",
|
||||
name: "Files",
|
||||
component: Files,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
path: "/settings",
|
||||
name: "Settings",
|
||||
component: Settings,
|
||||
redirect: {
|
||||
path: '/settings/profile'
|
||||
path: "/settings/profile",
|
||||
},
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/settings/profile',
|
||||
name: 'Profile Settings',
|
||||
component: ProfileSettings
|
||||
path: "/settings/profile",
|
||||
name: "Profile Settings",
|
||||
component: ProfileSettings,
|
||||
},
|
||||
{
|
||||
path: '/settings/shares',
|
||||
name: 'Shares',
|
||||
component: Shares
|
||||
path: "/settings/shares",
|
||||
name: "Shares",
|
||||
component: Shares,
|
||||
},
|
||||
{
|
||||
path: '/settings/global',
|
||||
name: 'Global Settings',
|
||||
path: "/settings/global",
|
||||
name: "Global Settings",
|
||||
component: GlobalSettings,
|
||||
meta: {
|
||||
requiresAdmin: true
|
||||
}
|
||||
requiresAdmin: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/settings/users',
|
||||
name: 'Users',
|
||||
path: "/settings/users",
|
||||
name: "Users",
|
||||
component: Users,
|
||||
meta: {
|
||||
requiresAdmin: true
|
||||
}
|
||||
requiresAdmin: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/settings/users/*',
|
||||
name: 'User',
|
||||
path: "/settings/users/*",
|
||||
name: "User",
|
||||
component: User,
|
||||
meta: {
|
||||
requiresAdmin: true
|
||||
}
|
||||
}
|
||||
]
|
||||
requiresAdmin: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
name: 'Forbidden',
|
||||
path: "/403",
|
||||
name: "Forbidden",
|
||||
component: Errors,
|
||||
props: {
|
||||
errorCode: 403,
|
||||
showHeader: true
|
||||
}
|
||||
showHeader: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
name: 'Not Found',
|
||||
path: "/404",
|
||||
name: "Not Found",
|
||||
component: Errors,
|
||||
props: {
|
||||
errorCode: 404,
|
||||
showHeader: true
|
||||
}
|
||||
showHeader: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/500',
|
||||
name: 'Internal Server Error',
|
||||
path: "/500",
|
||||
name: "Internal Server Error",
|
||||
component: Errors,
|
||||
props: {
|
||||
errorCode: 500,
|
||||
showHeader: true
|
||||
}
|
||||
showHeader: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/files',
|
||||
path: "/files",
|
||||
redirect: {
|
||||
path: '/files/'
|
||||
}
|
||||
path: "/files/",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/*',
|
||||
redirect: to => `/files${to.path}`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
path: "/*",
|
||||
redirect: (to) => `/files${to.path}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
document.title = to.name
|
||||
document.title = to.name;
|
||||
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (to.matched.some((record) => record.meta.requiresAuth)) {
|
||||
if (!store.getters.isLogged) {
|
||||
next({
|
||||
path: '/login',
|
||||
query: { redirect: to.fullPath }
|
||||
})
|
||||
path: "/login",
|
||||
query: { redirect: to.fullPath },
|
||||
});
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (to.matched.some(record => record.meta.requiresAdmin)) {
|
||||
if (to.matched.some((record) => record.meta.requiresAdmin)) {
|
||||
if (!store.state.user.perm.admin) {
|
||||
next({ path: '/403' })
|
||||
return
|
||||
next({ path: "/403" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
next();
|
||||
});
|
||||
|
||||
export default router
|
||||
export default router;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
const getters = {
|
||||
isLogged: state => state.user !== null,
|
||||
isFiles: state => !state.loading && state.route.name === 'Files',
|
||||
isLogged: (state) => state.user !== null,
|
||||
isFiles: (state) => !state.loading && state.route.name === "Files",
|
||||
isListing: (state, getters) => getters.isFiles && state.req.isDir,
|
||||
selectedCount: state => state.selected.length,
|
||||
progress : state => {
|
||||
selectedCount: (state) => state.selected.length,
|
||||
progress: (state) => {
|
||||
if (state.upload.progress.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let sum = state.upload.progress.reduce((acc, val) => acc + val)
|
||||
return Math.ceil(sum / state.upload.size * 100);
|
||||
}
|
||||
}
|
||||
let sum = state.upload.progress.reduce((acc, val) => acc + val);
|
||||
return Math.ceil((sum / state.upload.size) * 100);
|
||||
},
|
||||
};
|
||||
|
||||
export default getters
|
||||
export default getters;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import mutations from './mutations'
|
||||
import getters from './getters'
|
||||
import upload from './modules/upload'
|
||||
import Vue from "vue";
|
||||
import Vuex from "vuex";
|
||||
import mutations from "./mutations";
|
||||
import getters from "./getters";
|
||||
import upload from "./modules/upload";
|
||||
|
||||
Vue.use(Vuex)
|
||||
Vue.use(Vuex);
|
||||
|
||||
const state = {
|
||||
user: null,
|
||||
req: {},
|
||||
oldReq: {},
|
||||
clipboard: {
|
||||
key: '',
|
||||
items: []
|
||||
key: "",
|
||||
items: [],
|
||||
},
|
||||
jwt: '',
|
||||
jwt: "",
|
||||
progress: 0,
|
||||
loading: false,
|
||||
reload: false,
|
||||
@@ -22,13 +22,13 @@ const state = {
|
||||
multiple: false,
|
||||
show: null,
|
||||
showShell: false,
|
||||
showConfirm: null
|
||||
}
|
||||
showConfirm: null,
|
||||
};
|
||||
|
||||
export default new Vuex.Store({
|
||||
strict: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
modules: { upload }
|
||||
})
|
||||
modules: { upload },
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import { files as api } from '@/api'
|
||||
import throttle from 'lodash.throttle'
|
||||
import buttons from '@/utils/buttons'
|
||||
import Vue from "vue";
|
||||
import { files as api } from "@/api";
|
||||
import throttle from "lodash.throttle";
|
||||
import buttons from "@/utils/buttons";
|
||||
|
||||
const UPLOADS_LIMIT = 5;
|
||||
|
||||
@@ -10,93 +10,100 @@ const state = {
|
||||
size: 0,
|
||||
progress: [],
|
||||
queue: [],
|
||||
uploads: {}
|
||||
}
|
||||
uploads: {},
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
setProgress(state, { id, loaded }) {
|
||||
Vue.set(state.progress, id, loaded)
|
||||
Vue.set(state.progress, id, loaded);
|
||||
},
|
||||
reset: (state) => {
|
||||
state.id = 0
|
||||
state.size = 0
|
||||
state.progress = []
|
||||
state.id = 0;
|
||||
state.size = 0;
|
||||
state.progress = [];
|
||||
},
|
||||
addJob: (state, item) => {
|
||||
state.queue.push(item)
|
||||
state.size += item.file.size
|
||||
state.id++
|
||||
state.queue.push(item);
|
||||
state.size += item.file.size;
|
||||
state.id++;
|
||||
},
|
||||
moveJob(state) {
|
||||
const item = state.queue[0]
|
||||
state.queue.shift()
|
||||
Vue.set(state.uploads, item.id, item)
|
||||
const item = state.queue[0];
|
||||
state.queue.shift();
|
||||
Vue.set(state.uploads, item.id, item);
|
||||
},
|
||||
removeJob(state, id) {
|
||||
delete state.uploads[id]
|
||||
}
|
||||
}
|
||||
delete state.uploads[id];
|
||||
},
|
||||
};
|
||||
|
||||
const beforeUnload = (event) => {
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
}
|
||||
event.preventDefault();
|
||||
event.returnValue = "";
|
||||
};
|
||||
|
||||
const actions = {
|
||||
upload: (context, item) => {
|
||||
let uploadsCount = Object.keys(context.state.uploads).length;
|
||||
|
||||
let isQueueEmpty = context.state.queue.length == 0
|
||||
let isUploadsEmpty = uploadsCount == 0
|
||||
let isQueueEmpty = context.state.queue.length == 0;
|
||||
let isUploadsEmpty = uploadsCount == 0;
|
||||
|
||||
if (isQueueEmpty && isUploadsEmpty) {
|
||||
window.addEventListener('beforeunload', beforeUnload)
|
||||
buttons.loading('upload')
|
||||
window.addEventListener("beforeunload", beforeUnload);
|
||||
buttons.loading("upload");
|
||||
}
|
||||
|
||||
context.commit('addJob', item)
|
||||
context.dispatch('processUploads')
|
||||
context.commit("addJob", item);
|
||||
context.dispatch("processUploads");
|
||||
},
|
||||
finishUpload: (context, item) => {
|
||||
context.commit('setProgress', { id: item.id, loaded: item.file.size })
|
||||
context.commit('removeJob', item.id)
|
||||
context.dispatch('processUploads')
|
||||
context.commit("setProgress", { id: item.id, loaded: item.file.size });
|
||||
context.commit("removeJob", item.id);
|
||||
context.dispatch("processUploads");
|
||||
},
|
||||
processUploads: async (context) => {
|
||||
let uploadsCount = Object.keys(context.state.uploads).length;
|
||||
|
||||
let isBellowLimit = uploadsCount < UPLOADS_LIMIT
|
||||
let isQueueEmpty = context.state.queue.length == 0
|
||||
let isUploadsEmpty = uploadsCount == 0
|
||||
let isBellowLimit = uploadsCount < UPLOADS_LIMIT;
|
||||
let isQueueEmpty = context.state.queue.length == 0;
|
||||
let isUploadsEmpty = uploadsCount == 0;
|
||||
|
||||
let isFinished = isQueueEmpty && isUploadsEmpty
|
||||
let canProcess = isBellowLimit && !isQueueEmpty
|
||||
let isFinished = isQueueEmpty && isUploadsEmpty;
|
||||
let canProcess = isBellowLimit && !isQueueEmpty;
|
||||
|
||||
if (isFinished) {
|
||||
window.removeEventListener('beforeunload', beforeUnload)
|
||||
buttons.success('upload')
|
||||
context.commit('reset')
|
||||
context.commit('setReload', true, { root: true })
|
||||
window.removeEventListener("beforeunload", beforeUnload);
|
||||
buttons.success("upload");
|
||||
context.commit("reset");
|
||||
context.commit("setReload", true, { root: true });
|
||||
}
|
||||
|
||||
if (canProcess) {
|
||||
const item = context.state.queue[0];
|
||||
context.commit('moveJob')
|
||||
context.commit("moveJob");
|
||||
|
||||
if (item.file.isDir) {
|
||||
await api.post(item.path).catch(Vue.prototype.$showError)
|
||||
await api.post(item.path).catch(Vue.prototype.$showError);
|
||||
} else {
|
||||
let onUpload = throttle(
|
||||
(event) => context.commit('setProgress', { id: item.id, loaded: event.loaded }),
|
||||
100, { leading: true, trailing: false }
|
||||
)
|
||||
(event) =>
|
||||
context.commit("setProgress", {
|
||||
id: item.id,
|
||||
loaded: event.loaded,
|
||||
}),
|
||||
100,
|
||||
{ leading: true, trailing: false }
|
||||
);
|
||||
|
||||
await api.post(item.path, item.file, item.overwrite, onUpload).catch(Vue.prototype.$showError)
|
||||
await api
|
||||
.post(item.path, item.file, item.overwrite, onUpload)
|
||||
.catch(Vue.prototype.$showError);
|
||||
}
|
||||
|
||||
context.dispatch('finishUpload', item)
|
||||
context.dispatch("finishUpload", item);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default { state, mutations, actions, namespaced: true }
|
||||
export default { state, mutations, actions, namespaced: true };
|
||||
|
||||
@@ -1,83 +1,87 @@
|
||||
import * as i18n from '@/i18n'
|
||||
import moment from 'moment'
|
||||
import * as i18n from "@/i18n";
|
||||
import moment from "moment";
|
||||
|
||||
const mutations = {
|
||||
closeHovers: state => {
|
||||
state.show = null
|
||||
state.showConfirm = null
|
||||
closeHovers: (state) => {
|
||||
state.show = null;
|
||||
state.showConfirm = null;
|
||||
},
|
||||
toggleShell: (state) => {
|
||||
state.showShell = !state.showShell
|
||||
state.showShell = !state.showShell;
|
||||
},
|
||||
showHover: (state, value) => {
|
||||
if (typeof value !== 'object') {
|
||||
state.show = value
|
||||
return
|
||||
if (typeof value !== "object") {
|
||||
state.show = value;
|
||||
return;
|
||||
}
|
||||
|
||||
state.show = value.prompt
|
||||
state.showConfirm = value.confirm
|
||||
state.show = value.prompt;
|
||||
state.showConfirm = value.confirm;
|
||||
},
|
||||
showError: (state) => {
|
||||
state.show = 'error'
|
||||
state.show = "error";
|
||||
},
|
||||
showSuccess: (state) => {
|
||||
state.show = 'success'
|
||||
state.show = "success";
|
||||
},
|
||||
setLoading: (state, value) => {
|
||||
state.loading = value;
|
||||
},
|
||||
setReload: (state, value) => {
|
||||
state.reload = value;
|
||||
},
|
||||
setLoading: (state, value) => { state.loading = value },
|
||||
setReload: (state, value) => { state.reload = value },
|
||||
setUser: (state, value) => {
|
||||
if (value === null) {
|
||||
state.user = null
|
||||
return
|
||||
state.user = null;
|
||||
return;
|
||||
}
|
||||
|
||||
let locale = value.locale
|
||||
let locale = value.locale;
|
||||
|
||||
if (locale === '') {
|
||||
locale = i18n.detectLocale()
|
||||
if (locale === "") {
|
||||
locale = i18n.detectLocale();
|
||||
}
|
||||
|
||||
moment.locale(locale)
|
||||
i18n.default.locale = locale
|
||||
state.user = value
|
||||
moment.locale(locale);
|
||||
i18n.default.locale = locale;
|
||||
state.user = value;
|
||||
},
|
||||
setJWT: (state, value) => (state.jwt = value),
|
||||
multiple: (state, value) => (state.multiple = value),
|
||||
addSelected: (state, value) => (state.selected.push(value)),
|
||||
addSelected: (state, value) => state.selected.push(value),
|
||||
removeSelected: (state, value) => {
|
||||
let i = state.selected.indexOf(value)
|
||||
if (i === -1) return
|
||||
state.selected.splice(i, 1)
|
||||
let i = state.selected.indexOf(value);
|
||||
if (i === -1) return;
|
||||
state.selected.splice(i, 1);
|
||||
},
|
||||
resetSelected: (state) => {
|
||||
state.selected = []
|
||||
state.selected = [];
|
||||
},
|
||||
updateUser: (state, value) => {
|
||||
if (typeof value !== 'object') return
|
||||
if (typeof value !== "object") return;
|
||||
|
||||
for (let field in value) {
|
||||
if (field === 'locale') {
|
||||
moment.locale(value[field])
|
||||
i18n.default.locale = value[field]
|
||||
if (field === "locale") {
|
||||
moment.locale(value[field]);
|
||||
i18n.default.locale = value[field];
|
||||
}
|
||||
|
||||
state.user[field] = value[field]
|
||||
state.user[field] = value[field];
|
||||
}
|
||||
},
|
||||
updateRequest: (state, value) => {
|
||||
state.oldReq = state.req
|
||||
state.req = value
|
||||
state.oldReq = state.req;
|
||||
state.req = value;
|
||||
},
|
||||
updateClipboard: (state, value) => {
|
||||
state.clipboard.key = value.key
|
||||
state.clipboard.items = value.items
|
||||
state.clipboard.path = value.path
|
||||
state.clipboard.key = value.key;
|
||||
state.clipboard.items = value.items;
|
||||
state.clipboard.path = value.path;
|
||||
},
|
||||
resetClipboard: (state) => {
|
||||
state.clipboard.key = ''
|
||||
state.clipboard.items = []
|
||||
}
|
||||
}
|
||||
state.clipboard.key = "";
|
||||
state.clipboard.items = [];
|
||||
},
|
||||
};
|
||||
|
||||
export default mutations
|
||||
export default mutations;
|
||||
|
||||
@@ -1,88 +1,92 @@
|
||||
import store from '@/store'
|
||||
import router from '@/router'
|
||||
import { Base64 } from 'js-base64'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import store from "@/store";
|
||||
import router from "@/router";
|
||||
import { Base64 } from "js-base64";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
|
||||
export function parseToken (token) {
|
||||
const parts = token.split('.')
|
||||
export function parseToken(token) {
|
||||
const parts = token.split(".");
|
||||
|
||||
if (parts.length !== 3) {
|
||||
throw new Error('token malformed')
|
||||
throw new Error("token malformed");
|
||||
}
|
||||
|
||||
const data = JSON.parse(Base64.decode(parts[1]))
|
||||
const data = JSON.parse(Base64.decode(parts[1]));
|
||||
|
||||
localStorage.setItem('jwt', token)
|
||||
store.commit('setJWT', token)
|
||||
store.commit('setUser', data.user)
|
||||
document.cookie = `auth=${token}; path=/`;
|
||||
|
||||
localStorage.setItem("jwt", token);
|
||||
store.commit("setJWT", token);
|
||||
store.commit("setUser", data.user);
|
||||
}
|
||||
|
||||
export async function validateLogin () {
|
||||
export async function validateLogin() {
|
||||
try {
|
||||
if (localStorage.getItem('jwt')) {
|
||||
await renew(localStorage.getItem('jwt'))
|
||||
if (localStorage.getItem("jwt")) {
|
||||
await renew(localStorage.getItem("jwt"));
|
||||
}
|
||||
} catch (_) {
|
||||
console.warn('Invalid JWT token in storage') // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
||||
export async function login (username, password, recaptcha) {
|
||||
const data = { username, password, recaptcha }
|
||||
export async function login(username, password, recaptcha) {
|
||||
const data = { username, password, recaptcha };
|
||||
|
||||
const res = await fetch(`${baseURL}/api/login`, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
const body = await res.text()
|
||||
const body = await res.text();
|
||||
|
||||
if (res.status === 200) {
|
||||
parseToken(body)
|
||||
parseToken(body);
|
||||
} else {
|
||||
throw new Error(body)
|
||||
throw new Error(body);
|
||||
}
|
||||
}
|
||||
|
||||
export async function renew (jwt) {
|
||||
export async function renew(jwt) {
|
||||
const res = await fetch(`${baseURL}/api/renew`, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'X-Auth': jwt,
|
||||
}
|
||||
})
|
||||
"X-Auth": jwt,
|
||||
},
|
||||
});
|
||||
|
||||
const body = await res.text()
|
||||
const body = await res.text();
|
||||
|
||||
if (res.status === 200) {
|
||||
parseToken(body)
|
||||
parseToken(body);
|
||||
} else {
|
||||
throw new Error(body)
|
||||
throw new Error(body);
|
||||
}
|
||||
}
|
||||
|
||||
export async function signup (username, password) {
|
||||
const data = { username, password }
|
||||
export async function signup(username, password) {
|
||||
const data = { username, password };
|
||||
|
||||
const res = await fetch(`${baseURL}/api/signup`, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(res.status)
|
||||
throw new Error(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
export function logout () {
|
||||
store.commit('setJWT', '')
|
||||
store.commit('setUser', null)
|
||||
localStorage.setItem('jwt', null)
|
||||
router.push({path: '/login'})
|
||||
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);
|
||||
router.push({ path: "/login" });
|
||||
}
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
function loading (button) {
|
||||
let el = document.querySelector(`#${button}-button > i`)
|
||||
function loading(button) {
|
||||
let el = document.querySelector(`#${button}-button > i`);
|
||||
|
||||
if (el === undefined || el === null) {
|
||||
console.log('Error getting button ' + button) // eslint-disable-line
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (el.innerHTML == 'autorenew' || el.innerHTML == 'done') {
|
||||
return
|
||||
if (el.innerHTML == "autorenew" || el.innerHTML == "done") {
|
||||
return;
|
||||
}
|
||||
|
||||
el.dataset.icon = el.innerHTML
|
||||
el.style.opacity = 0
|
||||
el.dataset.icon = el.innerHTML;
|
||||
el.style.opacity = 0;
|
||||
|
||||
setTimeout(() => {
|
||||
el.classList.add('spin')
|
||||
el.innerHTML = 'autorenew'
|
||||
el.style.opacity = 1
|
||||
}, 100)
|
||||
el.classList.add("spin");
|
||||
el.innerHTML = "autorenew";
|
||||
el.style.opacity = 1;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function done (button) {
|
||||
let el = document.querySelector(`#${button}-button > i`)
|
||||
function done(button) {
|
||||
let el = document.querySelector(`#${button}-button > i`);
|
||||
|
||||
if (el === undefined || el === null) {
|
||||
console.log('Error getting button ' + button) // eslint-disable-line
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
el.style.opacity = 0
|
||||
el.style.opacity = 0;
|
||||
|
||||
setTimeout(() => {
|
||||
el.classList.remove('spin')
|
||||
el.innerHTML = el.dataset.icon
|
||||
el.style.opacity = 1
|
||||
}, 100)
|
||||
el.classList.remove("spin");
|
||||
el.innerHTML = el.dataset.icon;
|
||||
el.style.opacity = 1;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function success (button) {
|
||||
let el = document.querySelector(`#${button}-button > i`)
|
||||
function success(button) {
|
||||
let el = document.querySelector(`#${button}-button > i`);
|
||||
|
||||
if (el === undefined || el === null) {
|
||||
console.log('Error getting button ' + button) // eslint-disable-line
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
el.style.opacity = 0
|
||||
el.style.opacity = 0;
|
||||
|
||||
setTimeout(() => {
|
||||
el.classList.remove('spin')
|
||||
el.innerHTML = 'done'
|
||||
el.style.opacity = 1
|
||||
el.classList.remove("spin");
|
||||
el.innerHTML = "done";
|
||||
el.style.opacity = 1;
|
||||
|
||||
setTimeout(() => {
|
||||
el.style.opacity = 0
|
||||
el.style.opacity = 0;
|
||||
|
||||
setTimeout(() => {
|
||||
el.innerHTML = el.dataset.icon
|
||||
el.style.opacity = 1
|
||||
}, 100)
|
||||
}, 500)
|
||||
}, 100)
|
||||
el.innerHTML = el.dataset.icon;
|
||||
el.style.opacity = 1;
|
||||
}, 100);
|
||||
}, 500);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
export default {
|
||||
loading,
|
||||
done,
|
||||
success
|
||||
}
|
||||
success,
|
||||
};
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
const name = window.FileBrowser.Name || 'File Browser'
|
||||
const disableExternal = window.FileBrowser.DisableExternal
|
||||
const baseURL = window.FileBrowser.BaseURL
|
||||
const staticURL = window.FileBrowser.StaticURL
|
||||
const recaptcha = window.FileBrowser.ReCaptcha
|
||||
const recaptchaKey = window.FileBrowser.ReCaptchaKey
|
||||
const signup = window.FileBrowser.Signup
|
||||
const version = window.FileBrowser.Version
|
||||
const logoURL = `${staticURL}/img/logo.svg`
|
||||
const noAuth = window.FileBrowser.NoAuth
|
||||
const authMethod = window.FileBrowser.AuthMethod
|
||||
const loginPage = window.FileBrowser.LoginPage
|
||||
const theme = window.FileBrowser.Theme
|
||||
const enableThumbs = window.FileBrowser.EnableThumbs
|
||||
const resizePreview = window.FileBrowser.ResizePreview
|
||||
const enableExec = window.FileBrowser.EnableExec
|
||||
const name = window.FileBrowser.Name || "File Browser";
|
||||
const disableExternal = window.FileBrowser.DisableExternal;
|
||||
const baseURL = window.FileBrowser.BaseURL;
|
||||
const staticURL = window.FileBrowser.StaticURL;
|
||||
const recaptcha = window.FileBrowser.ReCaptcha;
|
||||
const recaptchaKey = window.FileBrowser.ReCaptchaKey;
|
||||
const signup = window.FileBrowser.Signup;
|
||||
const version = window.FileBrowser.Version;
|
||||
const logoURL = `${staticURL}/img/logo.svg`;
|
||||
const noAuth = window.FileBrowser.NoAuth;
|
||||
const authMethod = window.FileBrowser.AuthMethod;
|
||||
const loginPage = window.FileBrowser.LoginPage;
|
||||
const theme = window.FileBrowser.Theme;
|
||||
const enableThumbs = window.FileBrowser.EnableThumbs;
|
||||
const resizePreview = window.FileBrowser.ResizePreview;
|
||||
const enableExec = window.FileBrowser.EnableExec;
|
||||
|
||||
export {
|
||||
name,
|
||||
@@ -30,5 +30,5 @@ export {
|
||||
theme,
|
||||
enableThumbs,
|
||||
resizePreview,
|
||||
enableExec
|
||||
}
|
||||
enableExec,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export default function (name) {
|
||||
let re = new RegExp('(?:(?:^|.*;\\s*)' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$')
|
||||
return document.cookie.replace(re, '$1')
|
||||
let re = new RegExp(
|
||||
"(?:(?:^|.*;\\s*)" + name + "\\s*\\=\\s*([^;]*).*$)|^.*$"
|
||||
);
|
||||
return document.cookie.replace(re, "$1");
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
export default function getRule (rules) {
|
||||
export default function getRule(rules) {
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
rules[i] = rules[i].toLowerCase()
|
||||
rules[i] = rules[i].toLowerCase();
|
||||
}
|
||||
|
||||
let result = null
|
||||
let find = Array.prototype.find
|
||||
let result = null;
|
||||
let find = Array.prototype.find;
|
||||
|
||||
find.call(document.styleSheets, styleSheet => {
|
||||
result = find.call(styleSheet.cssRules, cssRule => {
|
||||
let found = false
|
||||
find.call(document.styleSheets, (styleSheet) => {
|
||||
result = find.call(styleSheet.cssRules, (cssRule) => {
|
||||
let found = false;
|
||||
|
||||
if (cssRule instanceof window.CSSStyleRule) {
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
if (cssRule.selectorText.toLowerCase() === rules[i]) {
|
||||
found = true
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
})
|
||||
return found;
|
||||
});
|
||||
|
||||
return result != null
|
||||
})
|
||||
return result != null;
|
||||
});
|
||||
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,124 +1,127 @@
|
||||
import store from '@/store'
|
||||
import url from '@/utils/url'
|
||||
import store from "@/store";
|
||||
import url from "@/utils/url";
|
||||
|
||||
export function checkConflict(files, items) {
|
||||
if (typeof items === 'undefined' || items === null) {
|
||||
items = []
|
||||
if (typeof items === "undefined" || items === null) {
|
||||
items = [];
|
||||
}
|
||||
|
||||
let folder_upload = files[0].fullPath !== undefined
|
||||
let folder_upload = files[0].fullPath !== undefined;
|
||||
|
||||
let conflict = false
|
||||
let conflict = false;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let file = files[i]
|
||||
let name = file.name
|
||||
let file = files[i];
|
||||
let name = file.name;
|
||||
|
||||
if (folder_upload) {
|
||||
let dirs = file.fullPath.split("/")
|
||||
let dirs = file.fullPath.split("/");
|
||||
if (dirs.length > 1) {
|
||||
name = dirs[0]
|
||||
name = dirs[0];
|
||||
}
|
||||
}
|
||||
|
||||
let res = items.findIndex(function hasConflict(element) {
|
||||
return (element.name === this)
|
||||
}, name)
|
||||
return element.name === this;
|
||||
}, name);
|
||||
|
||||
if (res >= 0) {
|
||||
conflict = true
|
||||
break
|
||||
conflict = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return conflict
|
||||
return conflict;
|
||||
}
|
||||
|
||||
export function scanFiles(dt) {
|
||||
return new Promise((resolve) => {
|
||||
let reading = 0
|
||||
const contents = []
|
||||
let reading = 0;
|
||||
const contents = [];
|
||||
|
||||
if (dt.items !== undefined) {
|
||||
for (let item of dt.items) {
|
||||
if (item.kind === "file" && typeof item.webkitGetAsEntry === "function") {
|
||||
const entry = item.webkitGetAsEntry()
|
||||
readEntry(entry)
|
||||
if (
|
||||
item.kind === "file" &&
|
||||
typeof item.webkitGetAsEntry === "function"
|
||||
) {
|
||||
const entry = item.webkitGetAsEntry();
|
||||
readEntry(entry);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resolve(dt.files)
|
||||
resolve(dt.files);
|
||||
}
|
||||
|
||||
function readEntry(entry, directory = "") {
|
||||
if (entry.isFile) {
|
||||
reading++
|
||||
entry.file(file => {
|
||||
reading--
|
||||
reading++;
|
||||
entry.file((file) => {
|
||||
reading--;
|
||||
|
||||
file.fullPath = `${directory}${file.name}`
|
||||
contents.push(file)
|
||||
file.fullPath = `${directory}${file.name}`;
|
||||
contents.push(file);
|
||||
|
||||
if (reading === 0) {
|
||||
resolve(contents)
|
||||
resolve(contents);
|
||||
}
|
||||
})
|
||||
});
|
||||
} else if (entry.isDirectory) {
|
||||
const dir = {
|
||||
isDir: true,
|
||||
size: 0,
|
||||
fullPath: `${directory}${entry.name}`
|
||||
}
|
||||
fullPath: `${directory}${entry.name}`,
|
||||
};
|
||||
|
||||
contents.push(dir)
|
||||
contents.push(dir);
|
||||
|
||||
readReaderContent(entry.createReader(), `${directory}${entry.name}`)
|
||||
readReaderContent(entry.createReader(), `${directory}${entry.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function readReaderContent(reader, directory) {
|
||||
reading++
|
||||
reading++;
|
||||
|
||||
reader.readEntries(function (entries) {
|
||||
reading--
|
||||
reading--;
|
||||
if (entries.length > 0) {
|
||||
for (const entry of entries) {
|
||||
readEntry(entry, `${directory}/`)
|
||||
readEntry(entry, `${directory}/`);
|
||||
}
|
||||
|
||||
readReaderContent(reader, `${directory}/`)
|
||||
readReaderContent(reader, `${directory}/`);
|
||||
}
|
||||
|
||||
if (reading === 0) {
|
||||
resolve(contents)
|
||||
resolve(contents);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function handleFiles(files, base, overwrite = false) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let id = store.state.upload.id
|
||||
let path = base
|
||||
let file = files[i]
|
||||
let id = store.state.upload.id;
|
||||
let path = base;
|
||||
let file = files[i];
|
||||
|
||||
if (file.fullPath !== undefined) {
|
||||
path += url.encodePath(file.fullPath)
|
||||
path += url.encodePath(file.fullPath);
|
||||
} else {
|
||||
path += url.encodeRFC5987ValueChars(file.name)
|
||||
path += url.encodeRFC5987ValueChars(file.name);
|
||||
}
|
||||
|
||||
if (file.isDir) {
|
||||
path += '/'
|
||||
path += "/";
|
||||
}
|
||||
|
||||
const item = {
|
||||
id,
|
||||
path,
|
||||
file,
|
||||
overwrite
|
||||
}
|
||||
overwrite,
|
||||
};
|
||||
|
||||
store.dispatch('upload/upload', item);
|
||||
store.dispatch("upload/upload", item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
function removeLastDir (url) {
|
||||
var arr = url.split('/')
|
||||
if (arr.pop() === '') {
|
||||
arr.pop()
|
||||
function removeLastDir(url) {
|
||||
var arr = url.split("/");
|
||||
if (arr.pop() === "") {
|
||||
arr.pop();
|
||||
}
|
||||
|
||||
return arr.join('/')
|
||||
return arr.join("/");
|
||||
}
|
||||
|
||||
// this code borrow from mozilla
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples
|
||||
function encodeRFC5987ValueChars(str) {
|
||||
return encodeURIComponent(str).
|
||||
return (
|
||||
encodeURIComponent(str)
|
||||
// Note that although RFC3986 reserves "!", RFC5987 does not,
|
||||
// so we do not need to escape it
|
||||
replace(/['()]/g, escape). // i.e., %27 %28 %29
|
||||
replace(/\*/g, '%2A').
|
||||
// The following are not required for percent-encoding per RFC5987,
|
||||
// so we can allow for a little better readability over the wire: |`^
|
||||
replace(/%(?:7C|60|5E)/g, unescape);
|
||||
.replace(/['()]/g, escape) // i.e., %27 %28 %29
|
||||
.replace(/\*/g, "%2A")
|
||||
// The following are not required for percent-encoding per RFC5987,
|
||||
// so we can allow for a little better readability over the wire: |`^
|
||||
.replace(/%(?:7C|60|5E)/g, unescape)
|
||||
);
|
||||
}
|
||||
|
||||
function encodePath(str) {
|
||||
return str.split('/').map(v => encodeURIComponent(v)).join('/')
|
||||
return str
|
||||
.split("/")
|
||||
.map((v) => encodeURIComponent(v))
|
||||
.join("/");
|
||||
}
|
||||
|
||||
export default {
|
||||
encodeRFC5987ValueChars: encodeRFC5987ValueChars,
|
||||
removeLastDir: removeLastDir,
|
||||
encodePath: encodePath
|
||||
}
|
||||
encodePath: encodePath,
|
||||
};
|
||||
|
||||
@@ -1,58 +1,66 @@
|
||||
import Vue from 'vue'
|
||||
import Noty from 'noty'
|
||||
import VueLazyload from 'vue-lazyload'
|
||||
import i18n from '@/i18n'
|
||||
import { disableExternal } from '@/utils/constants'
|
||||
import Vue from "vue";
|
||||
import Noty from "noty";
|
||||
import VueLazyload from "vue-lazyload";
|
||||
import i18n from "@/i18n";
|
||||
import { disableExternal } from "@/utils/constants";
|
||||
|
||||
Vue.use(VueLazyload)
|
||||
Vue.use(VueLazyload);
|
||||
|
||||
Vue.config.productionTip = true
|
||||
Vue.config.productionTip = true;
|
||||
|
||||
const notyDefault = {
|
||||
type: 'info',
|
||||
layout: 'bottomRight',
|
||||
type: "info",
|
||||
layout: "bottomRight",
|
||||
timeout: 1000,
|
||||
progressBar: true
|
||||
}
|
||||
progressBar: true,
|
||||
};
|
||||
|
||||
Vue.prototype.$noty = (opts) => {
|
||||
new Noty(Object.assign({}, notyDefault, opts)).show()
|
||||
}
|
||||
new Noty(Object.assign({}, notyDefault, opts)).show();
|
||||
};
|
||||
|
||||
Vue.prototype.$showSuccess = (message) => {
|
||||
new Noty(Object.assign({}, notyDefault, {
|
||||
text: message,
|
||||
type: 'success'
|
||||
})).show()
|
||||
}
|
||||
new Noty(
|
||||
Object.assign({}, notyDefault, {
|
||||
text: message,
|
||||
type: "success",
|
||||
})
|
||||
).show();
|
||||
};
|
||||
|
||||
Vue.prototype.$showError = (error, displayReport = true) => {
|
||||
let btns = [
|
||||
Noty.button(i18n.t('buttons.close'), '', function () {
|
||||
n.close()
|
||||
})
|
||||
]
|
||||
Noty.button(i18n.t("buttons.close"), "", function () {
|
||||
n.close();
|
||||
}),
|
||||
];
|
||||
|
||||
if (!disableExternal && displayReport) {
|
||||
btns.unshift(Noty.button(i18n.t('buttons.reportIssue'), '', function () {
|
||||
window.open('https://github.com/filebrowser/filebrowser/issues/new/choose')
|
||||
}))
|
||||
btns.unshift(
|
||||
Noty.button(i18n.t("buttons.reportIssue"), "", function () {
|
||||
window.open(
|
||||
"https://github.com/filebrowser/filebrowser/issues/new/choose"
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let n = new Noty(Object.assign({}, notyDefault, {
|
||||
text: error.message || error,
|
||||
type: 'error',
|
||||
timeout: null,
|
||||
buttons: btns
|
||||
}))
|
||||
let n = new Noty(
|
||||
Object.assign({}, notyDefault, {
|
||||
text: error.message || error,
|
||||
type: "error",
|
||||
timeout: null,
|
||||
buttons: btns,
|
||||
})
|
||||
);
|
||||
|
||||
n.show()
|
||||
}
|
||||
n.show();
|
||||
};
|
||||
|
||||
Vue.directive('focus', {
|
||||
Vue.directive("focus", {
|
||||
inserted: function (el) {
|
||||
el.focus()
|
||||
}
|
||||
})
|
||||
el.focus();
|
||||
},
|
||||
});
|
||||
|
||||
export default Vue
|
||||
export default Vue;
|
||||
|
||||
@@ -3,44 +3,51 @@
|
||||
<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>
|
||||
|
||||
<script>
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
|
||||
const errors = {
|
||||
0: {
|
||||
icon: "cloud_off",
|
||||
message: "errors.connection",
|
||||
},
|
||||
403: {
|
||||
icon: 'error',
|
||||
message: 'errors.forbidden'
|
||||
icon: "error",
|
||||
message: "errors.forbidden",
|
||||
},
|
||||
404: {
|
||||
icon: 'gps_off',
|
||||
message: 'errors.notFound'
|
||||
icon: "gps_off",
|
||||
message: "errors.notFound",
|
||||
},
|
||||
500: {
|
||||
icon: 'error_outline',
|
||||
message: 'errors.internal'
|
||||
}
|
||||
}
|
||||
icon: "error_outline",
|
||||
message: "errors.internal",
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'errors',
|
||||
name: "errors",
|
||||
components: {
|
||||
HeaderBar
|
||||
HeaderBar,
|
||||
},
|
||||
props: [
|
||||
'errorCode', 'showHeader'
|
||||
],
|
||||
data: function () {
|
||||
return {
|
||||
icon: errors[this.errorCode].icon,
|
||||
message: this.$t(errors[this.errorCode].message)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
props: ["errorCode", "showHeader"],
|
||||
computed: {
|
||||
code() {
|
||||
return this.errorCode === "0" ||
|
||||
this.errorCode === "404" ||
|
||||
this.errorCode === "403"
|
||||
? parseInt(this.errorCode)
|
||||
: 500;
|
||||
},
|
||||
info() {
|
||||
return errors[this.code];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,144 +1,135 @@
|
||||
<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">
|
||||
<span>{{ $t('files.loading') }}</span>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { files as api } from '@/api'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { files as api } from "@/api";
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
import Errors from '@/views/Errors'
|
||||
import Preview from '@/views/files/Preview'
|
||||
import Listing from '@/views/files/Listing'
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs";
|
||||
import Errors from "@/views/Errors";
|
||||
import Preview from "@/views/files/Preview";
|
||||
import Listing from "@/views/files/Listing";
|
||||
|
||||
function clean (path) {
|
||||
return path.endsWith('/') ? path.slice(0, -1) : path
|
||||
function clean(path) {
|
||||
return path.endsWith("/") ? path.slice(0, -1) : path;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'files',
|
||||
name: "files",
|
||||
components: {
|
||||
HeaderBar,
|
||||
Breadcrumbs,
|
||||
Errors,
|
||||
Preview,
|
||||
Listing,
|
||||
Editor: () => import('@/views/files/Editor'),
|
||||
Editor: () => import("@/views/files/Editor"),
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
error: null,
|
||||
width: window.innerWidth
|
||||
}
|
||||
width: window.innerWidth,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'req',
|
||||
'reload',
|
||||
'loading',
|
||||
'show'
|
||||
]),
|
||||
currentView () {
|
||||
...mapState(["req", "reload", "loading", "show"]),
|
||||
currentView() {
|
||||
if (this.req.type == undefined) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.req.isDir) {
|
||||
return 'listing'
|
||||
} else if(this.req.type === 'text' || this.req.type === 'textImmutable') {
|
||||
return 'editor'
|
||||
return "listing";
|
||||
} else if (
|
||||
this.req.type === "text" ||
|
||||
this.req.type === "textImmutable"
|
||||
) {
|
||||
return "editor";
|
||||
} else {
|
||||
return 'preview'
|
||||
return "preview";
|
||||
}
|
||||
},
|
||||
errorCode() {
|
||||
return (this.error.message === '404' || this.error.message === '403') ? parseInt(this.error.message) : 500
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
created() {
|
||||
this.fetchData();
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData',
|
||||
'reload': function (value) {
|
||||
if (value === true) {
|
||||
this.fetchData()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
},
|
||||
destroyed () {
|
||||
if (this.$store.state.showShell) {
|
||||
this.$store.commit('toggleShell')
|
||||
}
|
||||
this.$store.commit('updateRequest', {})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'setLoading' ]),
|
||||
async fetchData () {
|
||||
// Reset view information.
|
||||
this.$store.commit('setReload', false)
|
||||
this.$store.commit('resetSelected')
|
||||
this.$store.commit('multiple', false)
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
// Set loading to true and reset the error.
|
||||
this.setLoading(true)
|
||||
this.error = null
|
||||
|
||||
let url = this.$route.path
|
||||
if (url === '') url = '/'
|
||||
if (url[0] !== '/') url = '/' + url
|
||||
|
||||
try {
|
||||
const res = await api.fetch(url)
|
||||
|
||||
if (clean(res.path) !== clean(`/${this.$route.params.pathMatch}`)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$store.commit('updateRequest', res)
|
||||
document.title = res.name
|
||||
} catch (e) {
|
||||
this.error = e
|
||||
} finally {
|
||||
this.setLoading(false)
|
||||
$route: "fetchData",
|
||||
reload: function (value) {
|
||||
if (value === true) {
|
||||
this.fetchData();
|
||||
}
|
||||
},
|
||||
keyEvent (event) {
|
||||
if (this.show !== null) {
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
this.$store.commit('closeHovers')
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("keydown", this.keyEvent);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("keydown", this.keyEvent);
|
||||
},
|
||||
destroyed() {
|
||||
if (this.$store.state.showShell) {
|
||||
this.$store.commit("toggleShell");
|
||||
}
|
||||
this.$store.commit("updateRequest", {});
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["setLoading"]),
|
||||
async fetchData() {
|
||||
// Reset view information.
|
||||
this.$store.commit("setReload", false);
|
||||
this.$store.commit("resetSelected");
|
||||
this.$store.commit("multiple", false);
|
||||
this.$store.commit("closeHovers");
|
||||
|
||||
// Set loading to true and reset the error.
|
||||
this.setLoading(true);
|
||||
this.error = null;
|
||||
|
||||
let url = this.$route.path;
|
||||
if (url === "") url = "/";
|
||||
if (url[0] !== "/") url = "/" + url;
|
||||
|
||||
try {
|
||||
const res = await api.fetch(url);
|
||||
|
||||
if (clean(res.path) !== clean(`/${this.$route.params.pathMatch}`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return
|
||||
this.$store.commit("updateRequest", res);
|
||||
document.title = `${res.name} - ${this.$route.name}`;
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
|
||||
},
|
||||
keyEvent(event) {
|
||||
// F1!
|
||||
if (event.keyCode === 112) {
|
||||
event.preventDefault()
|
||||
this.$store.commit('showHover', 'help')
|
||||
event.preventDefault();
|
||||
this.$store.commit("showHover", "help");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -13,30 +13,31 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import Sidebar from '@/components/Sidebar'
|
||||
import Prompts from '@/components/prompts/Prompts'
|
||||
import Shell from '@/components/Shell'
|
||||
import { enableExec } from '@/utils/constants'
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import Prompts from "@/components/prompts/Prompts";
|
||||
import Shell from "@/components/Shell";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
name: 'layout',
|
||||
name: "layout",
|
||||
components: {
|
||||
Sidebar,
|
||||
Prompts,
|
||||
Shell
|
||||
Shell,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([ 'isLogged', 'progress' ]),
|
||||
...mapState([ 'user' ]),
|
||||
isExecEnabled: () => enableExec
|
||||
...mapGetters(["isLogged", "progress"]),
|
||||
...mapState(["user"]),
|
||||
isExecEnabled: () => enableExec,
|
||||
},
|
||||
watch: {
|
||||
'$route': function () {
|
||||
this.$store.commit('resetSelected')
|
||||
this.$store.commit('multiple', false)
|
||||
if (this.$store.state.show !== 'success') this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
}
|
||||
$route: function () {
|
||||
this.$store.commit("resetSelected");
|
||||
this.$store.commit("multiple", false);
|
||||
if (this.$store.state.show !== "success")
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,97 +1,127 @@
|
||||
<template>
|
||||
<div id="login" :class="{ recaptcha: recaptcha }">
|
||||
<form @submit="submit">
|
||||
<img :src="logoURL" alt="File Browser">
|
||||
<img :src="logoURL" alt="File Browser" />
|
||||
<h1>{{ name }}</h1>
|
||||
<div v-if="error !== ''" class="wrong">{{ error }}</div>
|
||||
|
||||
<input class="input input--block" type="text" v-model="username" :placeholder="$t('login.username')">
|
||||
<input class="input input--block" type="password" v-model="password" :placeholder="$t('login.password')">
|
||||
<input class="input input--block" v-if="createMode" type="password" v-model="passwordConfirm" :placeholder="$t('login.passwordConfirm')" />
|
||||
<input
|
||||
class="input input--block"
|
||||
type="text"
|
||||
v-model="username"
|
||||
:placeholder="$t('login.username')"
|
||||
/>
|
||||
<input
|
||||
class="input input--block"
|
||||
type="password"
|
||||
v-model="password"
|
||||
:placeholder="$t('login.password')"
|
||||
/>
|
||||
<input
|
||||
class="input input--block"
|
||||
v-if="createMode"
|
||||
type="password"
|
||||
v-model="passwordConfirm"
|
||||
:placeholder="$t('login.passwordConfirm')"
|
||||
/>
|
||||
|
||||
<div v-if="recaptcha" id="recaptcha"></div>
|
||||
<input class="button button--block" type="submit" :value="createMode ? $t('login.signup') : $t('login.submit')">
|
||||
<input
|
||||
class="button button--block"
|
||||
type="submit"
|
||||
:value="createMode ? $t('login.signup') : $t('login.submit')"
|
||||
/>
|
||||
|
||||
<p @click="toggleMode" v-if="signup">{{ createMode ? $t('login.loginInstead') : $t('login.createAnAccount') }}</p>
|
||||
<p @click="toggleMode" v-if="signup">
|
||||
{{
|
||||
createMode ? $t("login.loginInstead") : $t("login.createAnAccount")
|
||||
}}
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as auth from '@/utils/auth'
|
||||
import { name, logoURL, recaptcha, recaptchaKey, signup } from '@/utils/constants'
|
||||
import * as auth from "@/utils/auth";
|
||||
import {
|
||||
name,
|
||||
logoURL,
|
||||
recaptcha,
|
||||
recaptchaKey,
|
||||
signup,
|
||||
} from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
name: 'login',
|
||||
name: "login",
|
||||
computed: {
|
||||
signup: () => signup,
|
||||
name: () => name,
|
||||
logoURL: () => logoURL
|
||||
logoURL: () => logoURL,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
createMode: false,
|
||||
error: '',
|
||||
username: '',
|
||||
password: '',
|
||||
error: "",
|
||||
username: "",
|
||||
password: "",
|
||||
recaptcha: recaptcha,
|
||||
passwordConfirm: ''
|
||||
}
|
||||
passwordConfirm: "",
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
if (!recaptcha) return
|
||||
mounted() {
|
||||
if (!recaptcha) return;
|
||||
|
||||
window.grecaptcha.ready(function () {
|
||||
window.grecaptcha.render('recaptcha', {
|
||||
sitekey: recaptchaKey
|
||||
})
|
||||
})
|
||||
window.grecaptcha.render("recaptcha", {
|
||||
sitekey: recaptchaKey,
|
||||
});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
toggleMode () {
|
||||
this.createMode = !this.createMode
|
||||
toggleMode() {
|
||||
this.createMode = !this.createMode;
|
||||
},
|
||||
async submit (event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
async submit(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let redirect = this.$route.query.redirect
|
||||
if (redirect === '' || redirect === undefined || redirect === null) {
|
||||
redirect = '/files/'
|
||||
let redirect = this.$route.query.redirect;
|
||||
if (redirect === "" || redirect === undefined || redirect === null) {
|
||||
redirect = "/files/";
|
||||
}
|
||||
|
||||
let captcha = ''
|
||||
let captcha = "";
|
||||
if (recaptcha) {
|
||||
captcha = window.grecaptcha.getResponse()
|
||||
captcha = window.grecaptcha.getResponse();
|
||||
|
||||
if (captcha === '') {
|
||||
this.error = this.$t('login.wrongCredentials')
|
||||
return
|
||||
if (captcha === "") {
|
||||
this.error = this.$t("login.wrongCredentials");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.createMode) {
|
||||
if (this.password !== this.passwordConfirm) {
|
||||
this.error = this.$t('login.passwordsDontMatch')
|
||||
return
|
||||
this.error = this.$t("login.passwordsDontMatch");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.createMode) {
|
||||
await auth.signup(this.username, this.password)
|
||||
await auth.signup(this.username, this.password);
|
||||
}
|
||||
|
||||
await auth.login(this.username, this.password, captcha)
|
||||
this.$router.push({ path: redirect })
|
||||
await auth.login(this.username, this.password, captcha);
|
||||
this.$router.push({ path: redirect });
|
||||
} catch (e) {
|
||||
if (e.message == 409) {
|
||||
this.error = this.$t('login.usernameTaken')
|
||||
this.error = this.$t("login.usernameTaken");
|
||||
} else {
|
||||
this.error = this.$t('login.wrongCredentials')
|
||||
this.error = this.$t("login.wrongCredentials");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -5,30 +5,62 @@
|
||||
<div id="nav">
|
||||
<div class="wrapper">
|
||||
<ul>
|
||||
<router-link to="/settings/profile"><li :class="{ active: $route.path === '/settings/profile' }">{{ $t('settings.profileSettings') }}</li></router-link>
|
||||
<router-link to="/settings/shares"><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">{{ $t('settings.globalSettings') }}</li></router-link>
|
||||
<router-link to="/settings/users"><li :class="{ active: $route.path === '/settings/users' || $route.name === 'User' }" v-if="user.perm.admin">{{ $t('settings.userManagement') }}</li></router-link>
|
||||
<router-link to="/settings/profile"
|
||||
><li :class="{ active: $route.path === '/settings/profile' }">
|
||||
{{ $t("settings.profileSettings") }}
|
||||
</li></router-link
|
||||
>
|
||||
<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" v-if="user.perm.admin"
|
||||
><li :class="{ active: $route.path === '/settings/global' }">
|
||||
{{ $t("settings.globalSettings") }}
|
||||
</li></router-link
|
||||
>
|
||||
<router-link to="/settings/users" v-if="user.perm.admin"
|
||||
><li
|
||||
:class="{
|
||||
active:
|
||||
$route.path === '/settings/users' || $route.name === 'User',
|
||||
}"
|
||||
>
|
||||
{{ $t("settings.userManagement") }}
|
||||
</li></router-link
|
||||
>
|
||||
</ul>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState } from "vuex";
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
|
||||
export default {
|
||||
name: 'settings',
|
||||
name: "settings",
|
||||
components: {
|
||||
HeaderBar
|
||||
HeaderBar,
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'user' ])
|
||||
}
|
||||
}
|
||||
...mapState(["user", "loading"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -3,43 +3,120 @@
|
||||
<header-bar showMenu showLogo>
|
||||
<title />
|
||||
|
||||
<action v-if="selectedCount" icon="file_download" :label="$t('buttons.download')" @action="download" :counter="selectedCount" />
|
||||
<action icon="check_circle" :label="$t('buttons.selectMultiple')" @action="toggleMultipleSelection" />
|
||||
<action
|
||||
v-if="selectedCount"
|
||||
icon="file_download"
|
||||
:label="$t('buttons.download')"
|
||||
@action="download"
|
||||
:counter="selectedCount"
|
||||
/>
|
||||
<action
|
||||
icon="check_circle"
|
||||
:label="$t('buttons.selectMultiple')"
|
||||
@action="toggleMultipleSelection"
|
||||
/>
|
||||
</header-bar>
|
||||
|
||||
<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">
|
||||
{{ req.isDir ? $t('download.downloadFolder') : $t('download.downloadFile') }}
|
||||
</div>
|
||||
<div class="share__box__element share__box__center share__box__icon">
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<strong>{{ $t('prompts.displayName') }}</strong> {{ req.name }}
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<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>
|
||||
</div>
|
||||
<div class="share__box__element share__box__center">
|
||||
<qrcode-vue :value="link" size="200" level="M"></qrcode-vue>
|
||||
</div>
|
||||
<div class="share__box__header">
|
||||
{{
|
||||
req.isDir
|
||||
? $t("download.downloadFolder")
|
||||
: $t("download.downloadFile")
|
||||
}}
|
||||
</div>
|
||||
<div class="share__box__element share__box__center share__box__icon">
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<strong>{{ $t("prompts.displayName") }}</strong> {{ req.name }}
|
||||
</div>
|
||||
<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">
|
||||
<div>
|
||||
<i class="material-icons">file_download</i
|
||||
>{{ $t("buttons.download") }}
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
:href="link + '?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 class="share__box__element share__box__center">
|
||||
<qrcode-vue :value="fullLink" size="200" level="M"></qrcode-vue>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="req.isDir && req.items.length > 0" class="share__box share__box__items">
|
||||
<div
|
||||
v-if="req.isDir && req.items.length > 0"
|
||||
class="share__box share__box__items"
|
||||
>
|
||||
<div class="share__box__header" v-if="req.isDir">
|
||||
{{ $t('files.files') }}
|
||||
{{ $t("files.files") }}
|
||||
</div>
|
||||
<div id="listing" class="list">
|
||||
<item v-for="(item) in req.items.slice(0, this.showLimit)"
|
||||
<item
|
||||
v-for="item in req.items.slice(0, this.showLimit)"
|
||||
:key="base64(item.name)"
|
||||
v-bind:index="item.index"
|
||||
v-bind:name="item.name"
|
||||
@@ -48,202 +125,213 @@
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size"
|
||||
readOnly>
|
||||
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>
|
||||
<p class="name">+ {{ req.items.length - showLimit }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="{ active: $store.state.multiple }" id="multiple-selection">
|
||||
<p>{{ $t('files.multipleSelectionEnabled') }}</p>
|
||||
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" :title="$t('files.clear')" :aria-label="$t('files.clear')" class="action">
|
||||
<div
|
||||
:class="{ active: $store.state.multiple }"
|
||||
id="multiple-selection"
|
||||
>
|
||||
<p>{{ $t("files.multipleSelectionEnabled") }}</p>
|
||||
<div
|
||||
@click="$store.commit('multiple', false)"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:title="$t('files.clear')"
|
||||
:aria-label="$t('files.clear')"
|
||||
class="action"
|
||||
>
|
||||
<i class="material-icons">clear</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="req.isDir && req.items.length === 0" class="share__box share__box__items">
|
||||
<div
|
||||
v-else-if="req.isDir && req.items.length === 0"
|
||||
class="share__box share__box__items"
|
||||
>
|
||||
<h2 class="message">
|
||||
<i class="material-icons">sentiment_dissatisfied</i>
|
||||
<span>{{ $t('files.lonely') }}</span>
|
||||
<span>{{ $t("files.lonely") }}</span>
|
||||
</h2>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
import {mapState, mapMutations, mapGetters} from 'vuex';
|
||||
import { pub as api } from '@/api'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import filesize from 'filesize'
|
||||
import moment from 'moment'
|
||||
import { mapState, mapMutations, mapGetters } from "vuex";
|
||||
import { pub as api } from "@/api";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import filesize from "filesize";
|
||||
import moment from "moment";
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
import Action from '@/components/header/Action'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
import Errors from '@/views/Errors'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import Item from "@/components/files/ListingItem"
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Action from "@/components/header/Action";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs";
|
||||
import Errors from "@/views/Errors";
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
import Item from "@/components/files/ListingItem";
|
||||
|
||||
export default {
|
||||
name: 'share',
|
||||
name: "share",
|
||||
components: {
|
||||
HeaderBar,
|
||||
Action,
|
||||
Breadcrumbs,
|
||||
Item,
|
||||
QrcodeVue,
|
||||
Errors
|
||||
Errors,
|
||||
},
|
||||
data: () => ({
|
||||
error: null,
|
||||
showLimit: 500,
|
||||
password: '',
|
||||
showLimit: 100,
|
||||
password: "",
|
||||
attemptedPasswordLogin: false,
|
||||
hash: null,
|
||||
token: null
|
||||
token: null,
|
||||
}),
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
$route: function () {
|
||||
this.showLimit = 100;
|
||||
|
||||
this.fetchData();
|
||||
},
|
||||
},
|
||||
created: async function () {
|
||||
const hash = this.$route.params.pathMatch.split('/')[0]
|
||||
this.hash = hash
|
||||
await this.fetchData()
|
||||
const hash = this.$route.params.pathMatch.split("/")[0];
|
||||
this.hash = hash;
|
||||
await this.fetchData();
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
mounted() {
|
||||
window.addEventListener("keydown", this.keyEvent);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("keydown", this.keyEvent);
|
||||
},
|
||||
computed: {
|
||||
...mapState(['req', 'loading', 'multiple', 'selected']),
|
||||
...mapGetters(['selectedCount', 'selectedCount']),
|
||||
...mapState(["req", "loading", "multiple", "selected"]),
|
||||
...mapGetters(["selectedCount", "selectedCount"]),
|
||||
icon: function () {
|
||||
if (this.req.isDir) return 'folder'
|
||||
if (this.req.type === 'image') return 'insert_photo'
|
||||
if (this.req.type === 'audio') return 'volume_up'
|
||||
if (this.req.type === 'video') return 'movie'
|
||||
return 'insert_drive_file'
|
||||
if (this.req.isDir) return "folder";
|
||||
if (this.req.type === "image") return "insert_photo";
|
||||
if (this.req.type === "audio") return "volume_up";
|
||||
if (this.req.type === "video") return "movie";
|
||||
return "insert_drive_file";
|
||||
},
|
||||
link: function () {
|
||||
let queryArg = '';
|
||||
if (this.token !== ''){
|
||||
queryArg = `?token=${this.token}`
|
||||
let queryArg = "";
|
||||
if (this.token !== "") {
|
||||
queryArg = `?token=${this.token}`;
|
||||
}
|
||||
|
||||
const path = this.$route.path.split('/').splice(2).join('/')
|
||||
return `${baseURL}/api/public/dl/${path}${queryArg}`
|
||||
const path = this.$route.path.split("/").splice(2).join("/");
|
||||
return `${baseURL}/api/public/dl/${path}${queryArg}`;
|
||||
},
|
||||
fullLink: function () {
|
||||
return window.location.origin + this.link;
|
||||
},
|
||||
humanSize: function () {
|
||||
if (this.req.isDir) {
|
||||
return this.req.items.length
|
||||
return this.req.items.length;
|
||||
}
|
||||
|
||||
return filesize(this.req.size)
|
||||
return filesize(this.req.size);
|
||||
},
|
||||
humanTime: function () {
|
||||
return moment(this.req.modified).fromNow()
|
||||
return moment(this.req.modified).fromNow();
|
||||
},
|
||||
modTime: function () {
|
||||
return new Date(Date.parse(this.req.modified)).toLocaleString();
|
||||
},
|
||||
errorCode() {
|
||||
return (this.error.message === '404' || this.error.message === '403') ? parseInt(this.error.message) : 500
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'resetSelected', 'updateRequest', 'setLoading' ]),
|
||||
...mapMutations(["resetSelected", "updateRequest", "setLoading"]),
|
||||
base64: function (name) {
|
||||
return window.btoa(unescape(encodeURIComponent(name)))
|
||||
return window.btoa(unescape(encodeURIComponent(name)));
|
||||
},
|
||||
fetchData: async function () {
|
||||
// Reset view information.
|
||||
this.$store.commit('setReload', false)
|
||||
this.$store.commit('resetSelected')
|
||||
this.$store.commit('multiple', false)
|
||||
this.$store.commit('closeHovers')
|
||||
this.$store.commit("setReload", false);
|
||||
this.$store.commit("resetSelected");
|
||||
this.$store.commit("multiple", false);
|
||||
this.$store.commit("closeHovers");
|
||||
|
||||
// Set loading to true and reset the error.
|
||||
this.setLoading(true)
|
||||
this.error = null
|
||||
this.setLoading(true);
|
||||
this.error = null;
|
||||
|
||||
if (this.password !== ''){
|
||||
this.attemptedPasswordLogin = true
|
||||
if (this.password !== "") {
|
||||
this.attemptedPasswordLogin = true;
|
||||
}
|
||||
|
||||
let url = this.$route.path
|
||||
if (url === '') url = '/'
|
||||
if (url[0] !== '/') url = '/' + url
|
||||
let url = this.$route.path;
|
||||
if (url === "") url = "/";
|
||||
if (url[0] !== "/") url = "/" + url;
|
||||
|
||||
try {
|
||||
let file = await api.fetch(url, this.password)
|
||||
let file = await api.fetch(url, this.password);
|
||||
|
||||
this.token = file.token || ''
|
||||
this.token = file.token || "";
|
||||
|
||||
this.updateRequest(file)
|
||||
this.setLoading(false)
|
||||
this.updateRequest(file);
|
||||
document.title = `${file.name} - ${this.$route.name}`;
|
||||
} catch (e) {
|
||||
this.error = e
|
||||
this.error = e;
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
},
|
||||
keyEvent (event) {
|
||||
keyEvent(event) {
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
// If we're on a listing, unselect all
|
||||
// files and folders.
|
||||
if (this.selectedCount > 0) {
|
||||
this.resetSelected()
|
||||
this.resetSelected();
|
||||
}
|
||||
}
|
||||
},
|
||||
toggleMultipleSelection () {
|
||||
this.$store.commit('multiple', !this.multiple)
|
||||
toggleMultipleSelection() {
|
||||
this.$store.commit("multiple", !this.multiple);
|
||||
},
|
||||
download () {
|
||||
download() {
|
||||
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
||||
api.download(null, this.hash, this.token, this.req.items[this.selected[0]].path)
|
||||
return
|
||||
api.download(
|
||||
null,
|
||||
this.hash,
|
||||
this.token,
|
||||
this.req.items[this.selected[0]].path
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'download',
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "download",
|
||||
confirm: (format) => {
|
||||
this.$store.commit('closeHovers')
|
||||
this.$store.commit("closeHovers");
|
||||
|
||||
let files = []
|
||||
let files = [];
|
||||
|
||||
for (let i of this.selected) {
|
||||
files.push(this.req.items[i].path)
|
||||
files.push(this.req.items[i].path);
|
||||
}
|
||||
|
||||
api.download(format, this.hash, this.token, ...files)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
api.download(format, this.hash, this.token, ...files);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
<title>{{ req.name }}</title>
|
||||
|
||||
<template #actions>
|
||||
<action id="save-button" icon="save" :label="$t('buttons.save')" @action="save()" />
|
||||
<action
|
||||
v-if="user.perm.modify"
|
||||
id="save-button"
|
||||
icon="save"
|
||||
:label="$t('buttons.save')"
|
||||
@action="save()"
|
||||
/>
|
||||
</template>
|
||||
</header-bar>
|
||||
|
||||
@@ -16,120 +22,120 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { files as api } from '@/api'
|
||||
import { theme } from '@/utils/constants'
|
||||
import buttons from '@/utils/buttons'
|
||||
import url from '@/utils/url'
|
||||
import { mapState } from "vuex";
|
||||
import { files as api } from "@/api";
|
||||
import { theme } from "@/utils/constants";
|
||||
import buttons from "@/utils/buttons";
|
||||
import url from "@/utils/url";
|
||||
|
||||
import ace from 'ace-builds/src-min-noconflict/ace.js'
|
||||
import modelist from 'ace-builds/src-min-noconflict/ext-modelist.js'
|
||||
import 'ace-builds/webpack-resolver'
|
||||
import ace from "ace-builds/src-min-noconflict/ace.js";
|
||||
import modelist from "ace-builds/src-min-noconflict/ext-modelist.js";
|
||||
import "ace-builds/webpack-resolver";
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
import Action from '@/components/header/Action'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Action from "@/components/header/Action";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs";
|
||||
|
||||
export default {
|
||||
name: 'editor',
|
||||
name: "editor",
|
||||
components: {
|
||||
HeaderBar,
|
||||
Action,
|
||||
Breadcrumbs
|
||||
Breadcrumbs,
|
||||
},
|
||||
data: function () {
|
||||
return {}
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['req', 'user']),
|
||||
breadcrumbs () {
|
||||
let parts = this.$route.path.split('/')
|
||||
...mapState(["req", "user"]),
|
||||
breadcrumbs() {
|
||||
let parts = this.$route.path.split("/");
|
||||
|
||||
if (parts[0] === '') {
|
||||
parts.shift()
|
||||
if (parts[0] === "") {
|
||||
parts.shift();
|
||||
}
|
||||
|
||||
if (parts[parts.length - 1] === '') {
|
||||
parts.pop()
|
||||
if (parts[parts.length - 1] === "") {
|
||||
parts.pop();
|
||||
}
|
||||
|
||||
let breadcrumbs = []
|
||||
let breadcrumbs = [];
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]) })
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]) });
|
||||
}
|
||||
|
||||
breadcrumbs.shift()
|
||||
breadcrumbs.shift();
|
||||
|
||||
if (breadcrumbs.length > 3) {
|
||||
while (breadcrumbs.length !== 4) {
|
||||
breadcrumbs.shift()
|
||||
breadcrumbs.shift();
|
||||
}
|
||||
|
||||
breadcrumbs[0].name = '...'
|
||||
breadcrumbs[0].name = "...";
|
||||
}
|
||||
|
||||
return breadcrumbs
|
||||
}
|
||||
return breadcrumbs;
|
||||
},
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
created() {
|
||||
window.addEventListener("keydown", this.keyEvent);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("keydown", this.keyEvent);
|
||||
this.editor.destroy();
|
||||
},
|
||||
mounted: function () {
|
||||
const fileContent = this.req.content || '';
|
||||
const fileContent = this.req.content || "";
|
||||
|
||||
this.editor = ace.edit('editor', {
|
||||
this.editor = ace.edit("editor", {
|
||||
value: fileContent,
|
||||
showPrintMargin: false,
|
||||
readOnly: this.req.type === 'textImmutable',
|
||||
theme: 'ace/theme/chrome',
|
||||
readOnly: this.req.type === "textImmutable",
|
||||
theme: "ace/theme/chrome",
|
||||
mode: modelist.getModeForPath(this.req.name).mode,
|
||||
wrap: true
|
||||
})
|
||||
wrap: true,
|
||||
});
|
||||
|
||||
if (theme == 'dark') {
|
||||
if (theme == "dark") {
|
||||
this.editor.setTheme("ace/theme/twilight");
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
back () {
|
||||
let uri = url.removeLastDir(this.$route.path) + '/'
|
||||
this.$router.push({ path: uri })
|
||||
back() {
|
||||
let uri = url.removeLastDir(this.$route.path) + "/";
|
||||
this.$router.push({ path: uri });
|
||||
},
|
||||
keyEvent (event) {
|
||||
keyEvent(event) {
|
||||
if (!event.ctrlKey && !event.metaKey) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.fromCharCode(event.which).toLowerCase() !== 's') {
|
||||
return
|
||||
if (String.fromCharCode(event.which).toLowerCase() !== "s") {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
this.save()
|
||||
event.preventDefault();
|
||||
this.save();
|
||||
},
|
||||
async save () {
|
||||
const button = 'save'
|
||||
buttons.loading('save')
|
||||
async save() {
|
||||
const button = "save";
|
||||
buttons.loading("save");
|
||||
|
||||
try {
|
||||
await api.put(this.$route.path, this.editor.getValue())
|
||||
buttons.success(button)
|
||||
await api.put(this.$route.path, this.editor.getValue());
|
||||
buttons.success(button);
|
||||
} catch (e) {
|
||||
buttons.done(button)
|
||||
this.$showError(e)
|
||||
buttons.done(button);
|
||||
this.$showError(e);
|
||||
}
|
||||
},
|
||||
close () {
|
||||
this.$store.commit('updateRequest', {})
|
||||
close() {
|
||||
this.$store.commit("updateRequest", {});
|
||||
|
||||
let uri = url.removeLastDir(this.$route.path) + '/'
|
||||
this.$router.push({ path: uri })
|
||||
}
|
||||
}
|
||||
}
|
||||
let uri = url.removeLastDir(this.$route.path) + "/";
|
||||
this.$router.push({ path: uri });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user