Compare commits

...
214 Commits
Author SHA1 Message Date
Oleg Lobanov b73d278ded chore(release): 2.21.1 2022-02-22 10:58:44 +01:00
Oleg Lobanov 6366cf0b18 fix: display user scope for admin users (#1834) 2022-02-22 10:58:22 +01:00
Oleg Lobanov f73518029c chore(release): 2.21.0 2022-02-21 20:49:31 +01:00
Oleg Lobanov c782f21b0f fix: correctly handle non-ascii passwords for shared resources 2022-02-21 20:47:28 +01:00
Oleg Lobanov 0942fc7042 fix: don't expose scope for non-admin users 2022-02-21 20:17:42 +01:00
Oleg Lobanov c1987237d0 feat: use real image path to calculate cache key 2022-02-21 19:59:22 +01:00
Filippo FinkeandGitHub cf85404dd2 feat: add upload file list with progress (#1825) 2022-02-21 19:30:42 +01:00
Oleg LobanovandGitHub 6f226fa549 Merge pull request #1832 from filebrowser/dependabot/npm_and_yarn/frontend/url-parse-1.5.7
build(deps): bump url-parse from 1.5.4 to 1.5.7 in /frontend
2022-02-20 12:23:22 +01:00
dependabot[bot]andGitHub 228ebea66c build(deps): bump url-parse from 1.5.4 to 1.5.7 in /frontend
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.4 to 1.5.7.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.4...1.5.7)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-19 16:43:38 +00:00
Oleg LobanovandGitHub bb19834042 Merge pull request #1777 from adrium/feat-gallery
Add gallery view mode
2022-02-10 17:39:59 +01:00
Adrian 7870e89bc0 feat: smaller column width to fit 2 columns in landscape mobiles 2022-02-10 17:11:24 +01:00
Adrian 8888b9f446 feat: add gallery view mode 2022-02-10 17:11:24 +01:00
Oleg LobanovandGitHub f6e5c6f0de Merge pull request #1822 from filebrowser/dependabot/npm_and_yarn/frontend/hosted-git-info-2.8.9
build(deps): bump hosted-git-info from 2.8.8 to 2.8.9 in /frontend
2022-02-09 10:38:25 +01:00
dependabot[bot]andGitHub e7659ea36b build(deps): bump hosted-git-info from 2.8.8 to 2.8.9 in /frontend
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

---
updated-dependencies:
- dependency-name: hosted-git-info
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-09 09:33:46 +00:00
Oleg LobanovandGitHub 7730ccd611 Merge pull request #1819 from filebrowser/dependabot/npm_and_yarn/frontend/browserslist-4.19.1
build(deps): bump browserslist from 4.16.3 to 4.19.1 in /frontend
2022-02-09 10:31:07 +01:00
dependabot[bot]andGitHub 80890075e8 build(deps): bump browserslist from 4.16.3 to 4.19.1 in /frontend
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.16.3 to 4.19.1.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.16.3...4.19.1)

---
updated-dependencies:
- dependency-name: browserslist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-08 20:02:42 +00:00
Oleg LobanovandGitHub 9b04004120 Merge pull request #1818 from filebrowser/dependabot/npm_and_yarn/frontend/dns-packet-1.3.4
build(deps): bump dns-packet from 1.3.1 to 1.3.4 in /frontend
2022-02-08 21:01:42 +01:00
dependabot[bot]andGitHub a73d7f14b7 build(deps): bump dns-packet from 1.3.1 to 1.3.4 in /frontend
Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 1.3.1 to 1.3.4.
- [Release notes](https://github.com/mafintosh/dns-packet/releases)
- [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mafintosh/dns-packet/compare/v1.3.1...v1.3.4)

---
updated-dependencies:
- dependency-name: dns-packet
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-08 19:55:47 +00:00
Oleg LobanovandGitHub ffe960a8c2 Merge pull request #1817 from filebrowser/dependabot/npm_and_yarn/frontend/ws-6.2.2
build(deps): bump ws from 6.2.1 to 6.2.2 in /frontend
2022-02-08 20:55:07 +01:00
dependabot[bot]andGitHub 73c80732d9 build(deps): bump ws from 6.2.1 to 6.2.2 in /frontend
Bumps [ws](https://github.com/websockets/ws) from 6.2.1 to 6.2.2.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/6.2.1...6.2.2)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-08 19:53:09 +00:00
Oleg LobanovandGitHub 8e2663bf7b Merge pull request #1816 from filebrowser/dependabot/npm_and_yarn/frontend/path-parse-1.0.7
build(deps): bump path-parse from 1.0.6 to 1.0.7 in /frontend
2022-02-08 20:53:01 +01:00
Oleg LobanovandGitHub e697e58164 Merge pull request #1815 from filebrowser/dependabot/npm_and_yarn/frontend/url-parse-1.5.4
build(deps): bump url-parse from 1.5.1 to 1.5.4 in /frontend
2022-02-08 20:52:24 +01:00
dependabot[bot]andGitHub c01496624a build(deps): bump path-parse from 1.0.6 to 1.0.7 in /frontend
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-08 19:29:52 +00:00
dependabot[bot]andGitHub 8906408a8f build(deps): bump url-parse from 1.5.1 to 1.5.4 in /frontend
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.1 to 1.5.4.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.1...1.5.4)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-08 19:29:52 +00:00
Oleg LobanovandGitHub 3ec7951380 Merge pull request #1813 from filebrowser/dependabot/npm_and_yarn/frontend/postcss-7.0.39
build(deps): bump postcss from 7.0.35 to 7.0.39 in /frontend
2022-02-08 20:29:21 +01:00
Oleg LobanovandGitHub b30aefa522 Merge pull request #1812 from filebrowser/dependabot/npm_and_yarn/frontend/follow-redirects-1.14.8
build(deps): bump follow-redirects from 1.13.3 to 1.14.8 in /frontend
2022-02-08 20:29:04 +01:00
Oleg LobanovandGitHub bc8a750dfe Merge pull request #1814 from filebrowser/dependabot/npm_and_yarn/frontend/tar-6.1.11
build(deps): bump tar from 6.1.0 to 6.1.11 in /frontend
2022-02-08 20:28:47 +01:00
dependabot[bot]andGitHub f1f7f17ade build(deps): bump follow-redirects from 1.13.3 to 1.14.8 in /frontend
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.13.3 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.13.3...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-08 19:27:05 +00:00
dependabot[bot]andGitHub 9182d33e1c build(deps): bump postcss from 7.0.35 to 7.0.39 in /frontend
Bumps [postcss](https://github.com/postcss/postcss) from 7.0.35 to 7.0.39.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/7.0.39/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/7.0.35...7.0.39)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-08 19:26:52 +00:00
Oleg LobanovandGitHub 7d836a3728 Merge pull request #1786 from Jmainguy/typos
fix typos
2022-02-08 20:25:30 +01:00
dependabot[bot]andGitHub 010d16fc1d build(deps): bump tar from 6.1.0 to 6.1.11 in /frontend
Bumps [tar](https://github.com/npm/node-tar) from 6.1.0 to 6.1.11.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v6.1.0...v6.1.11)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-08 19:25:15 +00:00
Oleg LobanovandGitHub fa89ba4665 Merge branch 'master' into typos 2022-02-08 20:25:14 +01:00
Oleg LobanovandGitHub a0752904c1 Merge pull request #1811 from filebrowser/dependabot/npm_and_yarn/frontend/ssri-6.0.2
build(deps): bump ssri from 6.0.1 to 6.0.2 in /frontend
2022-02-08 20:24:18 +01:00
dependabot[bot]andGitHub 371718634b build(deps): bump ssri from 6.0.1 to 6.0.2 in /frontend
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

---
updated-dependencies:
- dependency-name: ssri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-08 19:09:43 +00:00
Oleg LobanovandGitHub 0f4f8751f2 Merge pull request #1769 from adrium/feat-icons
Add colorized file type icons
2022-02-08 20:08:52 +01:00
sejinPandGitHub ec45ee471f chore: remove GOMAXPROCS setting (#1803) 2022-02-08 19:56:04 +01:00
Jonathan Seth Mainguy 6fffcbac4e chore: fix typos 2022-01-28 09:46:21 -05:00
Adrian 2948589fcd feat: add colorized file type icons 2022-01-18 08:40:06 +01:00
Adrian ecd0b2ee0d chore: update Material Icons 2022-01-17 23:21:45 +01:00
on4randGitHub 205f11d677 chore: rotate the spinner clockwise (#1765) 2022-01-11 21:23:46 +01:00
niubility000andGitHub 949f0f277f fix: open all the pdf files correctly (#1742) 2022-01-10 17:01:21 +01:00
Anton GrouchtchakandGitHub 665e45889c feat: add Ukrainian translation / update Russian translation (#1753) 2022-01-07 12:29:57 +01:00
Oleg Lobanov 8d87e0d5f9 chore(release): 2.20.1 2021-12-21 14:40:04 +01:00
Oleg Lobanov 46d80464d2 build: revert to using the default alpine based docker image
Build both standard and linuxserver based images.
linuxserver based images can be accessed by adding `-s6` suffix to the docker image name
2021-12-21 14:39:34 +01:00
Oleg Lobanov 829ed9fb6d chore(release): 2.20.0 2021-12-21 00:48:46 +01:00
Oleg Lobanov 988d3e5bdd fix: set correct default database path in the config 2021-12-21 00:47:40 +01:00
Oleg Lobanov 6eb3ab0635 fix: upgrade vulnerable versions of the library 2021-12-21 00:17:26 +01:00
Mazen BesherandGitHub c2e03bbfab feat: detect multiple subtitle languages (#1723) 2021-12-21 00:07:43 +01:00
Oleg LobanovandGitHub 608a0015ee Merge pull request #1729 from filebrowser/add_linuxserver
Refactoring
2021-12-20 23:40:11 +01:00
Oleg Lobanov f81857acce build: refactor makefile 2021-12-20 23:36:50 +01:00
Oleg Lobanov b1e0d5b39f chore: add make fmt target 2021-12-20 22:39:00 +01:00
Oleg Lobanov 68cf7a2173 chore: upgrade go to 1.17 2021-12-20 22:31:53 +01:00
Oleg Lobanov 89d1c06441 ci: run CI linters concurrently 2021-12-20 22:31:52 +01:00
Oleg Lobanov 2bebb5f0f8 ci: fix frontend formatting 2021-12-20 22:29:41 +01:00
Oleg Lobanov 683b11d265 chore: add dist folder to .gitignore 2021-12-20 22:29:40 +01:00
Oleg Lobanov 4d1b9dd211 build: remove deprecated goreleaser use_buildx param 2021-12-20 22:29:39 +01:00
Oleg Lobanov b8f35ce932 feat: use linuxserver based docker image 2021-12-20 22:29:39 +01:00
Oleg Lobanov a078f0b787 chore(release): 2.19.0 2021-11-24 21:30:26 +01:00
niubility000andGitHub 7401d16e45 feat: prefetch previous and next images in preview. (#1627) 2021-11-15 14:54:28 +01:00
Oleg LobanovandGitHub 958a44f95e Merge pull request #1662 from ramiresviana/fixes-11 2021-11-11 16:45:53 +01:00
Ramires Viana e08239781f fix: empty file listing on share 2021-11-11 13:06:04 +00:00
Ramires Viana c29698dffa fix: relative font sizes 2021-11-11 12:54:30 +00:00
coxdeandGitHub 81de95632a chore: update zh-cn.json (#1656) 2021-11-10 15:07:53 +01:00
Oleg Lobanov 7f2d221083 chore(release): 2.18.0 2021-10-31 17:18:01 +01:00
Oleg Lobanov 74b7cd8e81 fix: security issue in command runner (closes #1621) 2021-10-31 17:13:16 +01:00
niubility000andGitHub 6cb51b4eb4 Add files via upload (#1615) 2021-10-24 13:35:29 +04:00
niubility000andGitHub f09bf3e1d0 fix: fix sidebar navigation on mobile devices (#1618) 2021-10-19 20:37:12 +04:00
niubility000andGitHub 6f345be3e4 fix: search box is misaligned when the browser preferred font size is other than 16px (#1613) 2021-10-19 20:34:17 +04:00
niubility000andGitHub ddd4ffa4ca fix: set correct editor height regardless of preferred font size (#1614) 2021-10-19 20:32:25 +04:00
niubility000andGitHub deabc80fd7 fix: back button behaviour in preview (#1573) 2021-10-12 13:09:05 +02:00
niubility000andGitHub b6a51bed51 fix: zoom pics when dlclick at first time (#1561) 2021-09-23 10:21:17 +02:00
lilihxandGitHub 0426629a59 feat: add ability to select file modified time format (#1536) 2021-09-11 14:12:51 +02:00
Ryan QianandGitHub 0358e42d2c feat: add manifest theme color param (#1542) 2021-09-10 17:08:15 +02:00
Filip HanesandGitHub 3768e3345f chore: add slovak translation (#1534) 2021-09-03 12:03:50 +02:00
ahmetlutfuandGitHub 16e434be66 chore: add turkish translation (#1524) 2021-08-31 11:49:33 +02:00
Oleg Lobanov bf303c536a chore(release): 2.17.2 2021-08-27 12:40:51 +02:00
Andrew KennedyandGitHub 43a460993c fix: bug with inlineLink not creating url properly (#1515) 2021-08-26 12:43:37 +02:00
Oleg Lobanov 7f0673ee70 chore(release): 2.17.1 2021-08-23 10:03:31 +02:00
Oleg Lobanov 4c3099a086 fix: internal server error if --disable-preview-resize flag is set (closes #1510) 2021-08-23 10:03:11 +02:00
Oleg Lobanov f0bc9167b1 chore(release): 2.17.0 2021-08-21 16:51:34 +02:00
Ramires VianaandOleg Lobanov 23d646c456 fix: escape quote on index template
fixes #1501
2021-08-20 14:43:06 +02:00
Ramires VianaandOleg Lobanov 76add9e527 feat: open file option on preview 2021-08-20 14:43:06 +02:00
Ramires VianaandOleg Lobanov c63cc5a2d2 fix: file caching directive 2021-08-20 14:43:06 +02:00
Andrew KennedyandGitHub 25c8788390 fix: 401 error in share view open file button (#1495) 2021-08-19 14:35:24 +02:00
Oleg Lobanov aa52b07bb1 chore(release): 2.16.1 2021-08-04 11:44:29 +02:00
Oleg Lobanov 76b466f649 fix: check symlink target type (closes #1488) 2021-08-04 11:44:02 +02:00
Oleg Lobanov 8ecc2da947 chore(release): 2.16.0 2021-07-26 13:03:53 +02:00
Oleg Lobanov 8650d2ffe7 fix: failure on broken symlink deletion 2021-07-26 13:02:11 +02:00
Oleg Lobanov 34d7d2c8c4 chore: upgrade golangci-lint 2021-07-26 12:00:05 +02:00
Oleg Lobanov 201329abce chore: add Content-Security-Policy header 2021-07-26 11:08:39 +02:00
Oleg Lobanov f2b5dd3787 chore: don't break folder download if any file processing causes an error 2021-07-26 10:41:56 +02:00
Oleg Lobanov 5072bbb2cb fix: break resource create/update handlers on error (closes #1464) 2021-07-24 15:33:54 +02:00
Oleg Lobanov 6b19ab6613 fix: don't remove files on unsuccessful updates (closes #1456) 2021-07-24 15:32:24 +02:00
Oleg LobanovandGitHub 730be5ef6b Create SECURITY.md 2021-07-03 16:56:27 +02:00
laggardkernelandGitHub 46ee595389 fix: short commit sha and typo fix in Makefile (#1411) 2021-05-25 11:29:38 +02:00
Oleg LobanovandGitHub dee465ab86 Merge pull request #1373 from ramiresviana/fixes-9
Some fixes
2021-05-03 23:29:12 +02:00
Ramires Viana 209f9fa77f fix: omit file content 2021-04-23 12:04:02 +00:00
Ramires Viana ba8c09f454 feat: show more button on share 2021-04-23 11:59:34 +00:00
Ramires Viana 16a34defc0 feat: file name on page title 2021-04-23 11:59:04 +00:00
Ramires Viana 7d1e03075d feat: mod time title on file info 2021-04-23 11:57:56 +00:00
Ramires Viana 1c25f6ee69 feat: open file option on share 2021-04-23 11:55:56 +00:00
Ramires Viana aa172b8bb5 feat: gzip encoding for static js files 2021-04-22 12:48:45 +00:00
Ramires Viana 4711e7bcd5 chore: set public path on the fly 2021-04-20 19:51:10 +00:00
Ramires Viana 8a47aee137 chore: split preview creation logic 2021-04-19 13:16:48 +00:00
Ramires Viana 190cb99a79 feat: browser cache directives 2021-04-19 12:49:40 +00:00
Ramires Viana 603203848a feat: display error messages on settings 2021-04-16 15:07:05 +00:00
Ramires Viana 5e6f14b5dc feat: message for connection error 2021-04-16 14:01:10 +00:00
Ramires Viana 976eb5583d feat: loading spinner on views navigation 2021-04-16 12:47:50 +00:00
Ramires Viana b92152693f chore: split action on resource patch handler 2021-04-16 12:04:06 +00:00
Ramires Viana 7ec24d9d77 feat: support for IE11 browser 2021-04-15 12:28:19 +00:00
Ramires Viana 20ebbf6611 fix: copying files with special characters 2021-04-15 11:50:30 +00:00
Ramires Viana ba7e71a7c3 fix: inconsistent double click on listing item 2021-04-14 15:29:06 +00:00
Ramires Viana 8973c4598f fix: delete image cache when moving 2021-04-14 15:20:38 +00:00
Ramires Viana 18889ad725 fix: no items displayed on file listing 2021-04-14 11:51:33 +00:00
Oleg Lobanov 73ccbe912f chore(release): 2.15.0 2021-04-06 13:57:29 +02:00
Oleg LobanovandGitHub 84e3a98303 Merge pull request #1353 from ramiresviana/fixes-8
Some fixes
2021-03-30 09:43:27 +02:00
adriumandGitHub 7dd5b34d42 feat: add EXIF thumbnail support for JPEG files (#1234) 2021-03-29 11:40:00 +02:00
Alexis LefebvreandGitHub 4470d0a704 chore: update issue templates (#1355) 2021-03-28 12:52:03 +02:00
Ramires Viana a76e01d2b7 feat: dynamic autoplay on previewer 2021-03-26 17:31:27 +00:00
Ramires Viana 2697093ac1 fix: empty archive name on directory download 2021-03-26 14:45:18 +00:00
Ramires Viana 59f9964e80 fix: check modify permission on file overwrite 2021-03-26 13:30:14 +00:00
Ramires Viana 1516d9932b fix: buttons without permission on header 2021-03-26 12:45:17 +00:00
Ramires Viana fcb115f42d fix: mouse wheel zoom on previewer 2021-03-25 19:37:54 +00:00
Ramires Viana e410272e6b feat: dynamic zoom limit on previewer 2021-03-25 19:36:53 +00:00
Ramires Viana 87f1881b42 fix: list item interactions on share 2021-03-25 15:47:49 +00:00
Ramires Viana c0d85f3d85 fix: image quality switch on previewer 2021-03-25 14:24:46 +00:00
Ramires Viana 98d79b8ed9 fix: missing bold variation for Roboto font 2021-03-24 19:06:56 +00:00
Ramires Viana fe80730bb1 fix: no header button animations on file listing 2021-03-24 19:05:15 +00:00
Ramires Viana 6c8ee96e6a feat: dynamic item count on file listing 2021-03-24 17:50:16 +00:00
Ramires Viana b521dec8f9 fix: hidden editor header on Safari 2021-03-24 12:23:05 +00:00
Ramires Viana e9baf0c4b6 fix: empty text file on editor 2021-03-23 18:18:02 +00:00
Ramires Viana e1a6f593e1 fix: error causes panic on upload 2021-03-23 13:13:46 +00:00
Oleg Lobanov 4b068b3058 chore(release): 2.14.1 2021-03-21 14:24:33 +01:00
Oleg Lobanov da54bd6c21 fix: display public routes with header proxy auth 2021-03-21 14:24:23 +01:00
Oleg Lobanov 0d179eca4d chore(release): 2.14.0 2021-03-21 13:19:53 +01:00
Oleg Lobanov dacd511d24 chore: run npm update 2021-03-21 13:05:22 +01:00
Oleg Lobanov c44b37c50c chore: add prettier frontent linter 2021-03-21 12:51:58 +01:00
Oleg Lobanov a721dc1f31 feat: add health check handler 2021-03-21 12:30:48 +01:00
Oleg LobanovandGitHub d2e6d23741 Merge pull request #1339 from ramiresviana/fixes-7
Some fixes
2021-03-19 17:32:41 +01:00
Ramires Viana 5f4a0317ab fix: hide dotfile error on share 2021-03-18 18:24:24 +00:00
Ramires Viana 22f4be8f54 fix: qr code url on share 2021-03-18 13:10:10 +00:00
Ramires Viana eeadc532fe fix: text file detection on editor 2021-03-17 18:06:56 +00:00
Ramires Viana 93a35ad251 fix: prefix handling on http router 2021-03-17 17:54:25 +00:00
Oleg LobanovandGitHub 99787287bb Merge pull request #1331 from ramiresviana/tweaks-2
Some development tweaks
2021-03-15 16:56:20 +01:00
Ramires Viana bdd523190e chore: frontend DirFS for development 2021-03-15 14:06:21 +00:00
Ramires Viana 4c1dd5c097 chore: automatic output name on build 2021-03-15 14:00:23 +00:00
Oleg Lobanov e1f658633d chore(release): 2.13.0 2021-03-14 20:02:02 +01:00
Oleg Lobanov 9c79105c02 chore: prevent deleting .gitignore from dist folder 2021-03-14 19:59:55 +01:00
Jürgen HötzelandGitHub 6d5ceae8b4 fix: wait for async command exit (#1326)
This prevents the accumulation of zombie processes when using
async (&) event commands. Also log async command failures.
2021-03-14 19:32:14 +01:00
Oleg LobanovandGitHub 381f09087a Merge pull request #1321 from ramiresviana/fixes-6 2021-03-14 14:19:50 +01:00
Ramires Viana 426b38bb33 fix: root path name on archive 2021-03-12 15:52:52 +00:00
Ramires Viana 488d98045e fix: download current dir on file listing 2021-03-12 15:28:49 +00:00
Ramires Viana 7955e0720b fix: encoded file path on share 2021-03-12 15:15:56 +00:00
Ramires Viana e017a19985 fix: full file path on share 2021-03-12 12:14:58 +00:00
Ramires Viana f8df76f526 fix: header dropdown icon color on previewer 2021-03-11 16:01:54 +00:00
Ramires Viana 11ebaec5f0 fix: modified time on info prompt 2021-03-11 15:20:37 +00:00
Ramires Viana 326b35a7ac fix: item dragging on file listing 2021-03-11 12:09:12 +00:00
Ramires Viana 5bf15548d0 fix: check rules on http resource handlers 2021-03-10 17:38:11 +00:00
Ramires Viana 6a734c0139 fix: stuck icon on header button 2021-03-10 15:32:10 +00:00
Ramires Viana 81b6f4d6f6 fix: update image cache when replacing 2021-03-10 15:14:01 +00:00
Ramires Viana 0b92d94570 chore: split POST method on resource http handler 2021-03-10 13:32:11 +00:00
Oleg Lobanov fc5506179a refactor: migrate from rice to embed.FS 2021-03-09 19:09:32 +01:00
Oleg LobanovandGitHub 0fe34ad224 Merge pull request #1307 from ramiresviana/tweaks-1
Frontend code quality changes
2021-03-09 18:26:46 +01:00
Ramires Viana 54f35701a2 fix: archive contains parent path on Windows 2021-03-09 15:54:54 +00:00
FrzMtrsprtandGitHub a809404ce1 chore: update zh-cn.json (#1311) 2021-03-08 10:24:16 +01:00
Po ChenandGitHub fb32e44b47 chore: update zh-cn.json (#1309) 2021-03-07 15:41:05 +01:00
Oleg Lobanov e9c0369062 chore(release): 2.12.1 2021-03-07 15:25:34 +01:00
Oleg Lobanov 7358b3fe31 fix: add missing default config into the docker image 2021-03-07 15:23:12 +01:00
Ramires Viana 2a1f759e9e chore: remove prompts events 2021-03-04 14:40:18 +00:00
Oleg Lobanov 2fccb8c367 chore(release): 2.12.0 2021-03-04 13:12:19 +01:00
Oleg Lobanov e039d95192 chore: fix major docker tag name 2021-03-04 13:12:03 +01:00
Oleg LobanovandGitHub 0f96031d6f Merge pull request #1305 from filebrowser/github_actions 2021-03-04 11:39:30 +01:00
Oleg Lobanov 6214fc84fa chore: add github action badge 2021-03-04 11:35:52 +01:00
Oleg Lobanov 47578e02e3 ci: migrate to github actions 2021-03-04 11:35:52 +01:00
Oleg Lobanov 35a4379b67 chore: go mod tidy 2021-03-04 00:57:35 +01:00
Oleg LobanovandGitHub 23f84642e6 build: use make for building the project (#1304) 2021-03-04 00:10:08 +01:00
Ramires Viana edb9e85efd chore: share view logic responsability 2021-03-03 17:46:37 +00:00
Oleg Lobanov 2d2c598fa6 feat: add homebrew tap 2021-03-03 16:25:03 +01:00
Oleg Lobanov cf4836dc75 feat: build multi-arch docker images 2021-03-03 13:46:22 +01:00
Ramires Viana d8306559fd chore: breadcrumbs component 2021-03-03 12:25:59 +00:00
WeidiDengandGitHub e8c9d1c539 feat: added tiff files preview support (#1222) 2021-03-02 12:14:32 +01:00
Alvaro AlemanandGitHub d8f415f8ab feat: allow to password protect shares (#1252)
This changes allows to password protect shares. It works by:
* Allowing to optionally pass a password when creating a share
* If set, the password + salt that is configured via a new flag will be
  hashed via bcrypt and the hash stored together with the rest of the
  share
* Additionally, a random 96 byte long token gets generated and stored
  as part of the share
* When the backend retrieves an unauthenticated request for a share that
  has authentication configured, it will return a http 401
* The frontend detects this and will show a login prompt
* The actual download links are protected via an url arg that contains
  the previously generated token. This allows us to avoid buffering the
  download in the browser and allows pasting the link without breaking
  it
2021-03-02 12:00:18 +01:00
Ramires Viana 7b6579ac8a chore: files view dynamic component 2021-03-01 16:12:17 +00:00
Ramires Viana 057307181e chore: removed header buttons components 2021-03-01 13:41:35 +00:00
Ramires Viana 4fb832c042 feat: increased header button counter size 2021-03-01 12:57:39 +00:00
Ramires Viana e503cb69f2 chore: files pages logic responsability 2021-02-26 15:10:21 +00:00
Ramires Viana 95811e99bc chore: header bar component 2021-02-25 18:37:07 +00:00
Ramires Viana 62fff5ca60 feat: larger previewer content 2021-02-22 16:01:13 +00:00
Ramires Viana 5b28aa0848 feat: improved settings navbar 2021-02-19 16:01:43 +00:00
Ramires Viana db5aad8eb6 feat: dual pane settings view 2021-02-19 13:15:46 +00:00
Alvaro AlemanandGitHub 977ec33918 chore: close preview on esc key press (#1288) 2021-02-16 17:15:04 +01:00
Ramires Viana 1819377897 feat: improved sharing prompt 2021-02-16 15:39:11 +00:00
niubility000andGitHub f1b7bd59f6 fix: double click to zoom pics in phone's browser (#1274)
fixed this : https://github.com/filebrowser/filebrowser/issues/1266
2021-02-08 09:54:40 +01:00
WeidiDengandGitHub f3afd5cb79 fix: environmental variables not expanded in command (#1241) 2021-01-19 17:02:46 +01:00
Oleg Lobanov e6a5bf116e chore: remove empty string from locales (closes #1245) 2021-01-19 16:56:06 +01:00
Oleg Lobanov 21b5a76fa7 chore: refactor search function 2021-01-12 19:14:23 +01:00
WeidiDengandGitHub b6263eb607 chore: search by type with empty file name (#1228) 2021-01-12 18:08:23 +01:00
WeidiDengandGitHub c8257e848e chore: move single click to user profile page (#1236)
Single clicks applies to FileList component.
2021-01-12 00:15:26 +01:00
叫我彭一凡andGitHub 05bb7c8553 fix: fetch resource api once when sorting (closes #1172) (#1202) 2021-01-12 00:00:40 +01:00
WeidiDengandGitHub b600b11415 feat: share management delete confirm (#1212) 2021-01-11 23:54:16 +01:00
Oleg Lobanov 019ce80fc5 fix: don't allow to remove root user 2021-01-11 22:33:36 +01:00
Adam DobrawyandGitHub 8cea2f75b3 chore: fix url to documentation in templates (#1231) 2021-01-07 11:48:53 +01:00
WeidiDengandGitHub 6914063853 feat: allow disabling file detections by reading header (#1175) 2021-01-07 11:30:17 +01:00
Oleg Lobanov 43e0d4a856 chore: fix translation files indent 2020-12-31 12:31:26 +01:00
Oleg Lobanov 066d8e8d6c chore: add ru translation 2020-12-31 12:26:49 +01:00
Oleg Lobanov 948e05c083 chore(release): 2.11.0 2020-12-28 17:37:19 +01:00
fb5b28d9cb feat: download shared subdirectory (#1184)
Co-authored-by: Oleg Lobanov <oleg@lobanov.me>
2020-12-28 17:35:29 +01:00
WeidiDengandGitHub 677bce376b feat: add sharing management (#1178) (closes #1000) 2020-12-24 19:02:28 +01:00
Alexis LefebvreandGitHub 8faa96f5e6 chore: fix typo costumize -> costumize (#1194) 2020-12-24 18:24:15 +01:00
WeidiDengandGitHub f62806f6c9 fix: check user input to prevent permission elevation (#1196) (closes #1195) 2020-12-24 18:22:48 +01:00
Oleg Lobanov 58835b7e53 fix: move files between different volumes (closes #1177) 2020-12-24 17:50:27 +01:00
WeidiDengandGitHub 7a5298a755 fix: delete extra remove prefix (#1186)
Fix file actions within /files dir
2020-12-16 16:08:56 +01:00
WeidiDengandGitHub bc4a6462ce chore: use command key to select multiple files on mac (#1183) 2020-12-12 14:09:50 +01:00
WeidiDengandGitHub ac3673e111 fix: recaptcha race condition (#1176) 2020-12-08 11:26:29 +01:00
228 changed files with 34236 additions and 12251 deletions
-92
View File
@@ -1,92 +0,0 @@
version: 2
jobs:
lint:
docker:
- image: golangci/golangci-lint:v1.27.0
steps:
- checkout
- run: golangci-lint run -v
build-node:
docker:
- image: circleci/node
steps:
- checkout
- run:
name: "Build"
command: ./wizard.sh -a
- run:
name: "Cleanup"
command: rm -rf frontend/node_modules
- persist_to_workspace:
root: .
paths:
- '*'
test:
docker:
- image: circleci/golang:1.15.2
steps:
- checkout
- run:
name: "Test"
command: go test ./...
build-go:
docker:
- image: circleci/golang:1.15.2
steps:
- attach_workspace:
at: '~/project'
- run:
name: "Compile"
command: GOOS=linux GOARCH=amd64 ./wizard.sh -c
- run:
name: "Cleanup"
command: |
rm -rf frontend/build
git checkout -- go.sum # TODO: why is it being changed?
- persist_to_workspace:
root: .
paths:
- '*'
release:
docker:
- image: circleci/golang:1.15.2
steps:
- attach_workspace:
at: '~/project'
- setup_remote_docker
- run: echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin
- run: curl -sL https://git.io/goreleaser | bash
- run: docker logout
workflows:
version: 2
build-workflow:
jobs:
- lint:
filters:
tags:
only: /.*/
- test:
filters:
tags:
only: /.*/
- build-node:
filters:
tags:
only: /.*/
- build-go:
filters:
tags:
only: /.*/
requires:
- build-node
- lint
- test
- release:
context: deploy
requires:
- build-go
filters:
tags:
only: /^v.*/
branches:
ignore: /.*/
+3 -3
View File
@@ -1,3 +1,3 @@
testdata/
.github/
**.git
*
!docker/*
!filebrowser
+5
View File
@@ -0,0 +1,5 @@
# These owners will be the default owners for everything in the repo.
# Unless a later match takes precedence, @o1egl will be requested for
# review when someone opens a pull request.
* @o1egl
+6 -6
View File
@@ -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. -->
+4 -4
View File
@@ -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
View File
@@ -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!
-->
+100
View File
@@ -0,0 +1,100 @@
name: main
on:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
jobs:
# linters
lint-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: make lint-frontend
lint-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: 1.17
- run: make lint-backend
lint-commits:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: make lint-commits
lint:
runs-on: ubuntu-latest
needs: [lint-frontend, lint-backend, lint-commits]
steps:
- run: echo "done"
# tests
test-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: make test-frontend
test-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: 1.17
- run: make test-backend
test:
runs-on: ubuntu-latest
needs: [test-frontend, test-backend]
steps:
- run: echo "done"
# release
release:
runs-on: ubuntu-latest
needs: [lint, test]
if: startsWith(github.event.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-go@v2
with:
go-version: 1.17
- uses: actions/setup-node@v2
with:
node-version: '14'
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build frontend
run: make build-frontend
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
+5 -4
View File
@@ -1,15 +1,14 @@
*.db
*.lock
*.bak
_old
rice-box.go
.idea/
filebrowser
dist/
/filebrowser
/filebrowser.exe
/dist
.DS_Store
node_modules
/frontend/dist
# local env files
.env.local
@@ -28,3 +27,5 @@ yarn-error.log*
*.njsproj
*.sln
*.sw*
bin/
build/
+7 -19
View File
@@ -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,27 +58,25 @@ linters:
- dogsled
- dupl
- errcheck
- exportloopref
- exhaustive
- funlen
- gochecknoinits
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- golint
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- nolintlint
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
@@ -90,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
@@ -119,6 +104,9 @@ issues:
- text: "Auther"
linters:
- misspell
- text: "strconv.Parse"
linters:
- gomnd
run:
skip-dirs:
+178 -45
View File
@@ -3,10 +3,6 @@ project_name: filebrowser
env:
- GO111MODULE=on
before:
hooks:
- go mod download
build:
env:
- CGO_ENABLED=0
@@ -19,10 +15,6 @@ build:
- linux
- windows
- freebsd
- netbsd
- openbsd
- dragonfly
- solaris
goarch:
- amd64
- 386
@@ -35,14 +27,8 @@ build:
ignore:
- goos: darwin
goarch: 386
- goos: openbsd
goarch: arm
- goos: freebsd
goarch: arm
- goos: netbsd
goarch: arm
- goos: solaris
goarch: arm
archives:
-
@@ -55,53 +41,200 @@ archives:
dockers:
-
dockerfile: Dockerfile
binaries:
- filebrowser
use: buildx
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/amd64"
goos: linux
goarch: amd64
goarm: ''
image_templates:
- "filebrowser/filebrowser:latest"
- "filebrowser/filebrowser:{{ .Tag }}"
- "filebrowser/filebrowser:v{{ .Major }}"
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
extra_files:
- .docker.json
- docker_config.json
-
dockerfile: Dockerfile
binaries:
- filebrowser
use: buildx
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm64"
goos: linux
goarch: arm64
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
extra_files:
- docker_config.json
-
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm/v6"
goos: linux
goarch: arm
goarm: '5'
goarm: '6'
image_templates:
- "filebrowser/filebrowser:pi"
- "filebrowser/filebrowser:{{ .Tag }}-pi"
- "filebrowser/filebrowser:v{{ .Major }}-pi"
- "filebrowser/filebrowser:{{ .Tag }}-armv6"
- "filebrowser/filebrowser:v{{ .Major }}-armv6"
extra_files:
- .docker.json
- docker_config.json
-
dockerfile: Dockerfile.alpine
binaries:
- filebrowser
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm/v7"
goos: linux
goarch: arm
goarm: '7'
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
- "filebrowser/filebrowser:v{{ .Major }}-armv7"
extra_files:
- docker_config.json
## s6 based docker images
-
dockerfile: Dockerfile.s6
use: buildx
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/amd64"
goos: linux
goarch: amd64
goarm: ''
image_templates:
- "filebrowser/filebrowser:alpine"
- "filebrowser/filebrowser:{{ .Tag }}-alpine"
- "filebrowser/filebrowser:v{{ .Major }}-alpine"
- "filebrowser/filebrowser:{{ .Tag }}-amd64-s6"
- "filebrowser/filebrowser:v{{ .Major }}-amd64-s6"
extra_files:
- .docker.json
- docker/root
-
dockerfile: Dockerfile.debian
binaries:
- filebrowser
dockerfile: Dockerfile.s6.aarch64
use: buildx
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm64"
goos: linux
goarch: amd64
goarm: ''
goarch: arm64
image_templates:
- "filebrowser/filebrowser:debian"
- "filebrowser/filebrowser:{{ .Tag }}-debian"
- "filebrowser/filebrowser:v{{ .Major }}-debian"
- "filebrowser/filebrowser:{{ .Tag }}-arm64-s6"
- "filebrowser/filebrowser:v{{ .Major }}-arm64-s6"
extra_files:
- .docker.json
- docker/root
-
dockerfile: Dockerfile.s6.armhf
use: buildx
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm/v6"
goos: linux
goarch: arm
goarm: '6'
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-armv6-s6"
- "filebrowser/filebrowser:v{{ .Major }}-armv6-s6"
extra_files:
- docker/root
-
dockerfile: Dockerfile.s6.armhf
use: buildx
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm/v7"
goos: linux
goarch: arm
goarm: '7'
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-armv7-s6"
- "filebrowser/filebrowser:v{{ .Major }}-armv7-s6"
extra_files:
- docker/root
docker_manifests:
- name_template: "filebrowser/filebrowser:latest"
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
- "filebrowser/filebrowser:{{ .Tag }}-armv6"
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
- name_template: "filebrowser/filebrowser:{{ .Tag }}"
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
- "filebrowser/filebrowser:{{ .Tag }}-armv6"
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
- name_template: "filebrowser/filebrowser:v{{ .Major }}"
image_templates:
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
- "filebrowser/filebrowser:v{{ .Major }}-armv6"
- "filebrowser/filebrowser:v{{ .Major }}-armv7"
## s6 image manifests
- name_template: "filebrowser/filebrowser:s6"
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-amd64-s6"
- "filebrowser/filebrowser:{{ .Tag }}-arm64-s6"
- "filebrowser/filebrowser:{{ .Tag }}-armv6-s6"
- "filebrowser/filebrowser:{{ .Tag }}-armv7-s6"
- name_template: "filebrowser/filebrowser:{{ .Tag }}-s6"
image_templates:
- "filebrowser/filebrowser:{{ .Tag }}-amd64-s6"
- "filebrowser/filebrowser:{{ .Tag }}-arm64-s6"
- "filebrowser/filebrowser:{{ .Tag }}-armv6-s6"
- "filebrowser/filebrowser:{{ .Tag }}-armv7-s6"
- name_template: "filebrowser/filebrowser:v{{ .Major }}-s6"
image_templates:
- "filebrowser/filebrowser:v{{ .Major }}-amd64-s6"
- "filebrowser/filebrowser:v{{ .Major }}-arm64-s6"
- "filebrowser/filebrowser:v{{ .Major }}-armv6-s6"
- "filebrowser/filebrowser:v{{ .Major }}-armv7-s6"
brews:
- name: filebrowser
tap:
owner: filebrowser
name: homebrew-tap
folder: Formula
homepage: https://filebrowser.org
commit_author:
name: FileBrowser Robot
email: robot@filebrowser.org
description: File Browser is a create-your-own-cloud-kind of software where you can install it on a server, direct it to a path and then access your files through a nice web interface
license: "MIT"
+14
View File
@@ -0,0 +1,14 @@
{
"types": [
{ "type": "feat", "section": "Features" },
{ "type": "fix", "section": "Bug Fixes" },
{ "type": "perf", "section": "Performance improvements" },
{ "type": "revert", "section": "Reverts" },
{ "type": "refactor", "section": "Refactorings" },
{ "type": "build", "section": "Build" },
{ "type": "ci", "hidden": true },
{ "type": "test", "hidden": true },
{ "type": "chore", "hidden": true },
{ "type": "docs", "hidden": true }
]
}
+290
View File
@@ -2,6 +2,296 @@
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.21.1](https://github.com/filebrowser/filebrowser/compare/v2.21.0...v2.21.1) (2022-02-22)
### Bug Fixes
* display user scope for admin users ([#1834](https://github.com/filebrowser/filebrowser/issues/1834)) ([6366cf0](https://github.com/filebrowser/filebrowser/commit/6366cf0b181f13eac38f69f1760d6f6f0586a5d1))
## [2.21.0](https://github.com/filebrowser/filebrowser/compare/v2.20.1...v2.21.0) (2022-02-21)
### Features
* add colorized file type icons ([2948589](https://github.com/filebrowser/filebrowser/commit/2948589fcde6d1dca7f3ea52a621d8213fa3300c))
* add gallery view mode ([8888b9f](https://github.com/filebrowser/filebrowser/commit/8888b9f44640394df9e3583db4392472d7027a4b))
* add Ukrainian translation / update Russian translation ([#1753](https://github.com/filebrowser/filebrowser/issues/1753)) ([665e458](https://github.com/filebrowser/filebrowser/commit/665e45889cd333f1e3500e4bf38d15d229c9fe2a))
* add upload file list with progress ([#1825](https://github.com/filebrowser/filebrowser/issues/1825)) ([cf85404](https://github.com/filebrowser/filebrowser/commit/cf85404dd25cd7fdd73aa32878b4dc5f85ee3e96))
* smaller column width to fit 2 columns in landscape mobiles ([7870e89](https://github.com/filebrowser/filebrowser/commit/7870e89bc04f1494f2705795476b5f1c9d621e38))
* use real image path to calculate cache key ([c198723](https://github.com/filebrowser/filebrowser/commit/c1987237d05adcce77c614e5247a181ae5cdfacd))
### Bug Fixes
* correctly handle non-ascii passwords for shared resources ([c782f21](https://github.com/filebrowser/filebrowser/commit/c782f21b0fa4511a15e7015117d075eaf5ea332c))
* don't expose scope for non-admin users ([0942fc7](https://github.com/filebrowser/filebrowser/commit/0942fc7042fd949cce91855169d0bcf16eb75771))
* open all the pdf files correctly ([#1742](https://github.com/filebrowser/filebrowser/issues/1742)) ([949f0f2](https://github.com/filebrowser/filebrowser/commit/949f0f277f6004904b3edfa716a8365ec93fa0fa))
### Build
* **deps:** bump browserslist from 4.16.3 to 4.19.1 in /frontend ([8089007](https://github.com/filebrowser/filebrowser/commit/80890075e802e2a4217edbb01d6417122d702f5e))
* **deps:** bump dns-packet from 1.3.1 to 1.3.4 in /frontend ([a73d7f1](https://github.com/filebrowser/filebrowser/commit/a73d7f14b787935c6ebe525dba64b65f8ed733e2))
* **deps:** bump follow-redirects from 1.13.3 to 1.14.8 in /frontend ([f1f7f17](https://github.com/filebrowser/filebrowser/commit/f1f7f17ade8d40fc6cfb22c79960bce299876b56))
* **deps:** bump hosted-git-info from 2.8.8 to 2.8.9 in /frontend ([e7659ea](https://github.com/filebrowser/filebrowser/commit/e7659ea36bdf780ce17005f7170a2fef02a2d5e5))
* **deps:** bump path-parse from 1.0.6 to 1.0.7 in /frontend ([c014966](https://github.com/filebrowser/filebrowser/commit/c01496624a7ebfc8a7c256bd919a400367281cbb))
* **deps:** bump postcss from 7.0.35 to 7.0.39 in /frontend ([9182d33](https://github.com/filebrowser/filebrowser/commit/9182d33e1cc375473fb18989a92d20252884f096))
* **deps:** bump ssri from 6.0.1 to 6.0.2 in /frontend ([3717186](https://github.com/filebrowser/filebrowser/commit/371718634b11f32e68165f31c51b6b1139c829ec))
* **deps:** bump tar from 6.1.0 to 6.1.11 in /frontend ([010d16f](https://github.com/filebrowser/filebrowser/commit/010d16fc1d8f0200e5662943aef17ee89c5877b7))
* **deps:** bump url-parse from 1.5.1 to 1.5.4 in /frontend ([8906408](https://github.com/filebrowser/filebrowser/commit/8906408a8f0ed86d1e11ea90fc573b36815c9c0d))
* **deps:** bump url-parse from 1.5.4 to 1.5.7 in /frontend ([228ebea](https://github.com/filebrowser/filebrowser/commit/228ebea66cc871b33459406590a80ef906298e7d))
* **deps:** bump ws from 6.2.1 to 6.2.2 in /frontend ([73c8073](https://github.com/filebrowser/filebrowser/commit/73c80732d934bc8802a6d7c7a559cad37df405f0))
### [2.20.1](https://github.com/filebrowser/filebrowser/compare/v2.20.0...v2.20.1) (2021-12-21)
### Build
* revert to using the default alpine based docker image ([46d8046](https://github.com/filebrowser/filebrowser/commit/46d80464d2a67927b06a11b83fb137ad364a90ed))
## [2.20.0](https://github.com/filebrowser/filebrowser/compare/v2.19.0...v2.20.0) (2021-12-20)
### Features
* detect multiple subtitle languages ([#1723](https://github.com/filebrowser/filebrowser/issues/1723)) ([c2e03bb](https://github.com/filebrowser/filebrowser/commit/c2e03bbfab97fc6716bcdd59158e9d5129bf0ea7))
* use linuxserver based docker image ([b8f35ce](https://github.com/filebrowser/filebrowser/commit/b8f35ce9322c2b0dbf954cfd3ff584bc9f742fdd))
### Bug Fixes
* set correct default database path in the config ([988d3e5](https://github.com/filebrowser/filebrowser/commit/988d3e5bdd224509ddc2f08444560e3087e9c67d))
* upgrade vulnerable versions of the library ([6eb3ab0](https://github.com/filebrowser/filebrowser/commit/6eb3ab063509a015ad630ab704ae3791461d0982))
### Build
* refactor makefile ([f81857a](https://github.com/filebrowser/filebrowser/commit/f81857acce25936a700945db5ef4af545eaeb1cf))
* remove deprecated goreleaser use_buildx param ([4d1b9dd](https://github.com/filebrowser/filebrowser/commit/4d1b9dd2112002a93bb26cece07dcfd81c31dc2c))
## [2.19.0](https://github.com/filebrowser/filebrowser/compare/v2.18.0...v2.19.0) (2021-11-24)
### Features
* prefetch previous and next images in preview. ([#1627](https://github.com/filebrowser/filebrowser/issues/1627)) ([7401d16](https://github.com/filebrowser/filebrowser/commit/7401d16e457bb232fd7dd7ef427e8960d465705c))
### Bug Fixes
* empty file listing on share ([e082397](https://github.com/filebrowser/filebrowser/commit/e08239781f61e7bb25d9b8c5c6cce90f34621a76))
* relative font sizes ([c29698d](https://github.com/filebrowser/filebrowser/commit/c29698dffac769077ab7c7869569a902979ee3d7))
## [2.18.0](https://github.com/filebrowser/filebrowser/compare/v2.17.2...v2.18.0) (2021-10-31)
### Features
* add ability to select file modified time format ([#1536](https://github.com/filebrowser/filebrowser/issues/1536)) ([0426629](https://github.com/filebrowser/filebrowser/commit/0426629a59c712849570d3e29956948ae7725a4a))
* add manifest theme color param ([#1542](https://github.com/filebrowser/filebrowser/issues/1542)) ([0358e42](https://github.com/filebrowser/filebrowser/commit/0358e42d2c206732fffa77714f5a66f4fe50a69d))
### Bug Fixes
* back button behaviour in preview ([#1573](https://github.com/filebrowser/filebrowser/issues/1573)) ([deabc80](https://github.com/filebrowser/filebrowser/commit/deabc80fd7670983039dfcd29531b45002ca5d9e))
* fix sidebar navigation on mobile devices ([#1618](https://github.com/filebrowser/filebrowser/issues/1618)) ([f09bf3e](https://github.com/filebrowser/filebrowser/commit/f09bf3e1d076b27d29ba8a91cf448a99993bc444))
* search box is misaligned when the browser preferred font size is other than 16px ([#1613](https://github.com/filebrowser/filebrowser/issues/1613)) ([6f345be](https://github.com/filebrowser/filebrowser/commit/6f345be3e47ba57ecc1eb9a62587ab949078c125))
* security issue in command runner (closes [#1621](https://github.com/filebrowser/filebrowser/issues/1621)) ([74b7cd8](https://github.com/filebrowser/filebrowser/commit/74b7cd8e81840537a8206317344f118093153e8d))
* set correct editor height regardless of preferred font size ([#1614](https://github.com/filebrowser/filebrowser/issues/1614)) ([ddd4ffa](https://github.com/filebrowser/filebrowser/commit/ddd4ffa4caa6b292a3a644ecd897aba1237c7503))
* zoom pics when dlclick at first time ([#1561](https://github.com/filebrowser/filebrowser/issues/1561)) ([b6a51be](https://github.com/filebrowser/filebrowser/commit/b6a51bed516814944f8aa41440652242d57824c5))
### [2.17.2](https://github.com/filebrowser/filebrowser/compare/v2.17.1...v2.17.2) (2021-08-27)
### Bug Fixes
* bug with inlineLink not creating url properly ([#1515](https://github.com/filebrowser/filebrowser/issues/1515)) ([43a4609](https://github.com/filebrowser/filebrowser/commit/43a460993c3f0d158b876db4b20caa7963e9f361))
### [2.17.1](https://github.com/filebrowser/filebrowser/compare/v2.17.0...v2.17.1) (2021-08-23)
### Bug Fixes
* internal server error if --disable-preview-resize flag is set (closes [#1510](https://github.com/filebrowser/filebrowser/issues/1510)) ([4c3099a](https://github.com/filebrowser/filebrowser/commit/4c3099a086c206dcb3bc70ee8c8da02eee61c30b))
## [2.17.0](https://github.com/filebrowser/filebrowser/compare/v2.16.1...v2.17.0) (2021-08-21)
### Features
* open file option on preview ([76add9e](https://github.com/filebrowser/filebrowser/commit/76add9e5274b0373c6b983e3b20e387a14ea6c9e))
### Bug Fixes
* 401 error in share view open file button ([#1495](https://github.com/filebrowser/filebrowser/issues/1495)) ([25c8788](https://github.com/filebrowser/filebrowser/commit/25c87883908babde073390a2e2320a8e5880a87c))
* escape quote on index template ([23d646c](https://github.com/filebrowser/filebrowser/commit/23d646c456876d06cf48e71c1e57b69de99511f0)), closes [#1501](https://github.com/filebrowser/filebrowser/issues/1501)
* file caching directive ([c63cc5a](https://github.com/filebrowser/filebrowser/commit/c63cc5a2d25909cc4e2f2e7235f276ec66c32bf2))
### [2.16.1](https://github.com/filebrowser/filebrowser/compare/v2.16.0...v2.16.1) (2021-08-04)
### Bug Fixes
* check symlink target type (closes [#1488](https://github.com/filebrowser/filebrowser/issues/1488)) ([76b466f](https://github.com/filebrowser/filebrowser/commit/76b466f6492e74cf13e66a33e7e5f597ac92b240))
## [2.16.0](https://github.com/filebrowser/filebrowser/compare/v2.15.0...v2.16.0) (2021-07-26)
### Features
* browser cache directives ([190cb99](https://github.com/filebrowser/filebrowser/commit/190cb99a79a0d438eca2da13539f8c6449ad73ac))
* display error messages on settings ([6032038](https://github.com/filebrowser/filebrowser/commit/603203848a8b2221158088b6d849609db4c0c46c))
* file name on page title ([16a34de](https://github.com/filebrowser/filebrowser/commit/16a34defc02554a77c6ac47b9e17e69d098a09fe))
* gzip encoding for static js files ([aa172b8](https://github.com/filebrowser/filebrowser/commit/aa172b8bb5f17d5f5cb9666bfb5ee650d8091fb5))
* loading spinner on views navigation ([976eb55](https://github.com/filebrowser/filebrowser/commit/976eb5583dae474125fd7ddec5dc19b6c291f98f))
* message for connection error ([5e6f14b](https://github.com/filebrowser/filebrowser/commit/5e6f14b5dcb9c5efdf526f1346e09c2d0b2f6974))
* mod time title on file info ([7d1e030](https://github.com/filebrowser/filebrowser/commit/7d1e03075d2c27148f60813defa0f68403d1d3c2))
* open file option on share ([1c25f6e](https://github.com/filebrowser/filebrowser/commit/1c25f6ee69bd71eed82af7020006d0e27537a967))
* show more button on share ([ba8c09f](https://github.com/filebrowser/filebrowser/commit/ba8c09f454feeadf4a1e97547a34151a81b389d5))
* support for IE11 browser ([7ec24d9](https://github.com/filebrowser/filebrowser/commit/7ec24d9d7794fa37825f64ca2d1575f568fb1362))
### Bug Fixes
* break resource create/update handlers on error (closes [#1464](https://github.com/filebrowser/filebrowser/issues/1464)) ([5072bbb](https://github.com/filebrowser/filebrowser/commit/5072bbb2cbf5b29d041629faa8367f15e4d145a2))
* copying files with special characters ([20ebbf6](https://github.com/filebrowser/filebrowser/commit/20ebbf6611b734371426fb1b9cb5e388be90bf7e))
* delete image cache when moving ([8973c45](https://github.com/filebrowser/filebrowser/commit/8973c4598ff817647f1f1ad6ee36480054cd2776))
* don't remove files on unsuccessful updates (closes [#1456](https://github.com/filebrowser/filebrowser/issues/1456)) ([6b19ab6](https://github.com/filebrowser/filebrowser/commit/6b19ab6613b12be7f075299cd98f4b41d43827c7))
* failure on broken symlink deletion ([8650d2f](https://github.com/filebrowser/filebrowser/commit/8650d2ffe7a29cbafa800efcecbf6a61598a9f0c))
* inconsistent double click on listing item ([ba7e71a](https://github.com/filebrowser/filebrowser/commit/ba7e71a7c3b0cc71012e5adf94b1c642e554972e))
* no items displayed on file listing ([18889ad](https://github.com/filebrowser/filebrowser/commit/18889ad725f7f7e5a7e3f7abcf156487556dbeaf))
* omit file content ([209f9fa](https://github.com/filebrowser/filebrowser/commit/209f9fa77f751054512355f2b74b9b7258465d0b))
* short commit sha and typo fix in Makefile ([#1411](https://github.com/filebrowser/filebrowser/issues/1411)) ([46ee595](https://github.com/filebrowser/filebrowser/commit/46ee59538914dc2859f0da6b32e2d062d0a01b10))
## [2.15.0](https://github.com/filebrowser/filebrowser/compare/v2.14.1...v2.15.0) (2021-04-06)
### Features
* add EXIF thumbnail support for JPEG files ([#1234](https://github.com/filebrowser/filebrowser/issues/1234)) ([7dd5b34](https://github.com/filebrowser/filebrowser/commit/7dd5b34d425dfbc2782152310483cbecf85c800a))
* dynamic autoplay on previewer ([a76e01d](https://github.com/filebrowser/filebrowser/commit/a76e01d2b78a785f3665a8b3532c7cc566bfabce))
* dynamic item count on file listing ([6c8ee96](https://github.com/filebrowser/filebrowser/commit/6c8ee96e6a21fae5d4608bdc7a5c5a161d7dafd3))
* dynamic zoom limit on previewer ([e410272](https://github.com/filebrowser/filebrowser/commit/e410272e6be6a0b660efe8d4eee6c6e9dd834cc5))
### Bug Fixes
* buttons without permission on header ([1516d99](https://github.com/filebrowser/filebrowser/commit/1516d9932bf9926ac8b4cb3e738a5f51e80d5b1d))
* check modify permission on file overwrite ([59f9964](https://github.com/filebrowser/filebrowser/commit/59f9964e80c8233775f27be33a4c16a31bfe848a))
* empty archive name on directory download ([2697093](https://github.com/filebrowser/filebrowser/commit/2697093ac151f74eea3022951d128acfe04d1dcf))
* empty text file on editor ([e9baf0c](https://github.com/filebrowser/filebrowser/commit/e9baf0c4b688fab291cdc842ec464c7a7a816499))
* error causes panic on upload ([e1a6f59](https://github.com/filebrowser/filebrowser/commit/e1a6f593e1824e7fa4345a61dff5b1bb8cd22d05))
* hidden editor header on Safari ([b521dec](https://github.com/filebrowser/filebrowser/commit/b521dec8f9b14dd92248c429e902ebc639046389))
* image quality switch on previewer ([c0d85f3](https://github.com/filebrowser/filebrowser/commit/c0d85f3d85926c8790757bf142140d19455ae8ca))
* list item interactions on share ([87f1881](https://github.com/filebrowser/filebrowser/commit/87f1881b429877a740ea84a8e783ad4103248289))
* missing bold variation for Roboto font ([98d79b8](https://github.com/filebrowser/filebrowser/commit/98d79b8ed955df5691a306d709b4ab60d91da408))
* mouse wheel zoom on previewer ([fcb115f](https://github.com/filebrowser/filebrowser/commit/fcb115f42d33db2be7a4d428ec53d65d6050320b))
* no header button animations on file listing ([fe80730](https://github.com/filebrowser/filebrowser/commit/fe80730bb135b38e4d9de470c75cbe10b1aec201))
### [2.14.1](https://github.com/filebrowser/filebrowser/compare/v2.14.0...v2.14.1) (2021-03-21)
### Bug Fixes
* display public routes with header proxy auth ([da54bd6](https://github.com/filebrowser/filebrowser/commit/da54bd6c214d7ee39b71d710ddfe6dd25fc4e5d6))
## [2.14.0](https://github.com/filebrowser/filebrowser/compare/v2.13.0...v2.14.0) (2021-03-21)
### 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)
### Features
* dual pane settings view ([db5aad8](https://github.com/filebrowser/filebrowser/commit/db5aad8eb679cfe1b1ace5142cf342951217f0f7))
* improved settings navbar ([5b28aa0](https://github.com/filebrowser/filebrowser/commit/5b28aa0848710b9d3ee02a2aa912856395f48bd2))
* improved sharing prompt ([1819377](https://github.com/filebrowser/filebrowser/commit/18193778971e27d18b5a35df8c2d0e2953b48111))
* increased header button counter size ([4fb832c](https://github.com/filebrowser/filebrowser/commit/4fb832c0422107e16f22b7aa928224f36de4978f))
* larger previewer content ([62fff5c](https://github.com/filebrowser/filebrowser/commit/62fff5ca60da1f887c1f95fa4808d3753596dab2))
### Bug Fixes
* archive contains parent path on Windows ([54f3570](https://github.com/filebrowser/filebrowser/commit/54f35701a2bd5cb7ec0628ca9789047072c073db))
* check rules on http resource handlers ([5bf1554](https://github.com/filebrowser/filebrowser/commit/5bf15548d0ad147acfad5000277531be2671f7ce))
* download current dir on file listing ([488d980](https://github.com/filebrowser/filebrowser/commit/488d98045e7476ed11e53c13d9498a9db3165bbc))
* encoded file path on share ([7955e07](https://github.com/filebrowser/filebrowser/commit/7955e0720baef3710106c7e69bbbf078d5489220))
* full file path on share ([e017a19](https://github.com/filebrowser/filebrowser/commit/e017a199850e19dd51b960ba59402c215fd8f1af))
* header dropdown icon color on previewer ([f8df76f](https://github.com/filebrowser/filebrowser/commit/f8df76f52684f10722ce123fec2c90e321ddf103))
* item dragging on file listing ([326b35a](https://github.com/filebrowser/filebrowser/commit/326b35a7ac7871afcdf892ca150349665b7f6379))
* modified time on info prompt ([11ebaec](https://github.com/filebrowser/filebrowser/commit/11ebaec5f0671ec02ebe55d4a73a514bce3a6713))
* root path name on archive ([426b38b](https://github.com/filebrowser/filebrowser/commit/426b38bb3362d2d477d0d8aa27d880664d537431))
* stuck icon on header button ([6a734c0](https://github.com/filebrowser/filebrowser/commit/6a734c01391b437c2842f5d97fb63f29a0017510))
* update image cache when replacing ([81b6f4d](https://github.com/filebrowser/filebrowser/commit/81b6f4d6f6a01886583016f61f4f1951a59f244d))
* wait for async command exit ([#1326](https://github.com/filebrowser/filebrowser/issues/1326)) ([6d5ceae](https://github.com/filebrowser/filebrowser/commit/6d5ceae8b454edd749b3b65c88aacc0a31ce9215))
### Refactorings
* migrate from rice to embed.FS ([fc55061](https://github.com/filebrowser/filebrowser/commit/fc5506179a64e9e2f57f7b6d6cce4b95f5ebc235))
### [2.12.1](https://github.com/filebrowser/filebrowser/compare/v2.12.0...v2.12.1) (2021-03-07)
### Bug Fixes
* add missing default config into the docker image ([7358b3f](https://github.com/filebrowser/filebrowser/commit/7358b3fe3178c20007b4b5ef5c03705badd538c4))
## [2.12.0](https://github.com/filebrowser/filebrowser/compare/v2.11.0...v2.12.0) (2021-03-04)
### Features
* add homebrew tap ([2d2c598](https://github.com/filebrowser/filebrowser/commit/2d2c598fa6bd1ecaf39c542182890c8dd9b1cad0))
* added tiff files preview support ([#1222](https://github.com/filebrowser/filebrowser/issues/1222)) ([e8c9d1c](https://github.com/filebrowser/filebrowser/commit/e8c9d1c53989b4b52f6fba2a8ac41ae612c03a7c))
* allow disabling file detections by reading header ([#1175](https://github.com/filebrowser/filebrowser/issues/1175)) ([6914063](https://github.com/filebrowser/filebrowser/commit/6914063853a8a3f3cecfa4b21f223820c2a0b7df))
* allow to password protect shares ([#1252](https://github.com/filebrowser/filebrowser/issues/1252)) ([d8f415f](https://github.com/filebrowser/filebrowser/commit/d8f415f8abd0c4301803bd968c54429dd3fe4b59))
* build multi-arch docker images ([cf4836d](https://github.com/filebrowser/filebrowser/commit/cf4836dc757ef79ad615179bb7a6c7bbd3b09c2c))
* share management delete confirm ([#1212](https://github.com/filebrowser/filebrowser/issues/1212)) ([b600b11](https://github.com/filebrowser/filebrowser/commit/b600b11415fd1fb90ff2f5136be95a9c737ae1cb))
### Bug Fixes
* don't allow to remove root user ([019ce80](https://github.com/filebrowser/filebrowser/commit/019ce80fc529a0437984fdc3d1ab6916f34dd594))
* double click to zoom pics in phone's browser ([#1274](https://github.com/filebrowser/filebrowser/issues/1274)) ([f1b7bd5](https://github.com/filebrowser/filebrowser/commit/f1b7bd59f67e719b7bfd203b0d7ec016fd21ab49))
* environmental variables not expanded in command ([#1241](https://github.com/filebrowser/filebrowser/issues/1241)) ([f3afd5c](https://github.com/filebrowser/filebrowser/commit/f3afd5cb79d6ad8b9cc8d54cb8fc2344b7c07d3d))
* fetch resource api once when sorting (closes [#1172](https://github.com/filebrowser/filebrowser/issues/1172)) ([#1202](https://github.com/filebrowser/filebrowser/issues/1202)) ([05bb7c8](https://github.com/filebrowser/filebrowser/commit/05bb7c85531349f3e9d1d8a523bb1243587b2ebc))
### Build
* use make for building the project ([#1304](https://github.com/filebrowser/filebrowser/issues/1304)) ([23f8464](https://github.com/filebrowser/filebrowser/commit/23f84642e6c1e07f89f98d2c1bb6fc9da36cc71c))
## [2.11.0](https://github.com/filebrowser/filebrowser/compare/v2.10.0...v2.11.0) (2020-12-28)
### Features
* add sharing management ([#1178](https://github.com/filebrowser/filebrowser/issues/1178)) (closes [#1000](https://github.com/filebrowser/filebrowser/issues/1000)) ([677bce3](https://github.com/filebrowser/filebrowser/commit/677bce376b024d9ff38f34e74243034fe5a1ec3c))
* download shared subdirectory ([#1184](https://github.com/filebrowser/filebrowser/issues/1184)) ([fb5b28d](https://github.com/filebrowser/filebrowser/commit/fb5b28d9cbdee10d38fcd719b9fd832121be58ef))
### Bug Fixes
* check user input to prevent permission elevation ([#1196](https://github.com/filebrowser/filebrowser/issues/1196)) (closes [#1195](https://github.com/filebrowser/filebrowser/issues/1195)) ([f62806f](https://github.com/filebrowser/filebrowser/commit/f62806f6c9e9c7f392d1b747d65b8fe40b313e89))
* delete extra remove prefix ([#1186](https://github.com/filebrowser/filebrowser/issues/1186)) ([7a5298a](https://github.com/filebrowser/filebrowser/commit/7a5298a7556f7dcc52f59b8ea76d040d3ddc3d12))
* move files between different volumes (closes [#1177](https://github.com/filebrowser/filebrowser/issues/1177)) ([58835b7](https://github.com/filebrowser/filebrowser/commit/58835b7e535cc96e1c8a5d85821c1545743ca757))
* recaptcha race condition ([#1176](https://github.com/filebrowser/filebrowser/issues/1176)) ([ac3673e](https://github.com/filebrowser/filebrowser/commit/ac3673e111afac6616af9650ca07028b6c27e6cd))
## [2.10.0](https://github.com/filebrowser/filebrowser/compare/v2.9.0...v2.10.0) (2020-11-24)
+8 -8
View File
@@ -1,15 +1,15 @@
FROM alpine:latest as alpine
RUN apk --update add ca-certificates
RUN apk --update add mailcap
FROM alpine:latest
RUN apk --update add ca-certificates \
mailcap \
curl
FROM scratch
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=alpine /etc/mime.types /etc/mime.types
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
CMD curl -f http://localhost/health || exit 1
VOLUME /srv
EXPOSE 80
COPY .docker.json /.filebrowser.json
COPY docker_config.json /.filebrowser.json
COPY filebrowser /filebrowser
ENTRYPOINT [ "/filebrowser" ]
ENTRYPOINT [ "/filebrowser" ]
-11
View File
@@ -1,11 +0,0 @@
FROM alpine:latest as alpine
RUN apk --update add ca-certificates
RUN apk --update add mailcap
VOLUME /srv
EXPOSE 80
COPY .docker.json /.filebrowser.json
COPY filebrowser /filebrowser
ENTRYPOINT [ "/filebrowser" ]
-9
View File
@@ -1,9 +0,0 @@
FROM debian:buster
VOLUME /srv
EXPOSE 80
COPY .docker.json /.filebrowser.json
COPY filebrowser /filebrowser
ENTRYPOINT [ "/filebrowser" ]
+16
View File
@@ -0,0 +1,16 @@
FROM ghcr.io/linuxserver/baseimage-alpine:3.14
RUN apk --update add ca-certificates \
mailcap \
curl
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
CMD curl -f http://localhost/health || exit 1
# copy local files
COPY docker/root/ /
COPY filebrowser /usr/bin/filebrowser
# ports and volumes
VOLUME /srv /config /database
EXPOSE 80
+16
View File
@@ -0,0 +1,16 @@
FROM ghcr.io/linuxserver/baseimage-alpine:arm64v8-3.14
RUN apk --update add ca-certificates \
mailcap \
curl
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
CMD curl -f http://localhost/health || exit 1
# copy local files
COPY docker/root/ /
COPY filebrowser /usr/bin/filebrowser
# ports and volumes
VOLUME /srv /config /database
EXPOSE 80
+16
View File
@@ -0,0 +1,16 @@
FROM ghcr.io/linuxserver/baseimage-alpine:arm32v7-3.14
RUN apk --update add ca-certificates \
mailcap \
curl
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
CMD curl -f http://localhost/health || exit 1
# copy local files
COPY docker/root/ /
COPY filebrowser /usr/bin/filebrowser
# ports and volumes
VOLUME /srv /config /database
EXPOSE 80
+68
View File
@@ -0,0 +1,68 @@
include common.mk
include tools.mk
LDFLAGS += -X "$(MODULE)/version.Version=$(VERSION)" -X "$(MODULE)/version.CommitSHA=$(VERSION_HASH)"
## Build:
.PHONY: build
build: | build-frontend build-backend ## Build binary
.PHONY: build-frontend
build-frontend: ## Build frontend
$Q cd frontend && npm ci && npm run build
.PHONY: build-backend
build-backend: ## Build backend
$Q $(go) build -ldflags '$(LDFLAGS)' -o .
.PHONY: test
test: | test-frontend test-backend ## Run all tests
.PHONY: test-frontend
test-frontend: ## Run frontend tests
.PHONY: test-backend
test-backend: ## Run backend tests
$Q $(go) test -v ./...
.PHONY: lint
lint: lint-frontend lint-backend lint-commits ## Run all linters
.PHONY: lint-frontend
lint-frontend: ## Run frontend linters
$Q cd frontend && npm ci && npm run lint
.PHONY: lint-backend
lint-backend: | $(golangci-lint) ## Run backend linters
$Q $(golangci-lint) run -v
.PHONY: lint-commits
lint-commits: $(commitlint) ## Run commit linters
$Q ./scripts/commitlint.sh
fmt: $(goimports) ## Format source files
$Q $(goimports) -local $(MODULE) -w $$(find . -type f -name '*.go' -not -path "./vendor/*")
clean: clean-tools ## Clean
## Release:
.PHONY: bump-version
bump-version: $(standard-version) ## Bump app version
$Q ./scripts/bump_version.sh
## Help:
help: ## Show this help
@echo ''
@echo 'Usage:'
@echo ' ${YELLOW}make${RESET} ${GREEN}<target> [options]${RESET}'
@echo ''
@echo 'Options:'
@$(call global_option, "V [0|1]", "enable verbose mode (default:0)")
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} { \
if (/^[a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \
else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \
}' $(MAKEFILE_LIST)
+1 -1
View File
@@ -4,7 +4,7 @@
![Preview](https://user-images.githubusercontent.com/5447088/50716739-ebd26700-107a-11e9-9817-14230c53efd2.gif)
[![Travis](https://img.shields.io/travis/com/filebrowser/filebrowser.svg?style=flat-square)](https://travis-ci.com/filebrowser/filebrowser)
[![Build](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml/badge.svg)](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/filebrowser/filebrowser?style=flat-square)](https://goreportcard.com/report/github.com/filebrowser/filebrowser)
[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/filebrowser/filebrowser)
[![Version](https://img.shields.io/github/release/filebrowser/filebrowser.svg?style=flat-square)](https://github.com/filebrowser/filebrowser/releases/latest)
+26
View 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.
+1 -1
View File
@@ -9,7 +9,7 @@ import (
// Auther is the authentication interface.
type Auther interface {
// Auth is called to authenticate a request.
Auth(r *http.Request, s *users.Storage, root string) (*users.User, error)
Auth(r *http.Request, s users.Store, root string) (*users.User, error)
// LoginPage indicates if this auther needs a login page.
LoginPage() bool
}
+2 -2
View File
@@ -26,7 +26,7 @@ type JSONAuth struct {
}
// Auth authenticates the user via a json in content body.
func (a JSONAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) {
func (a JSONAuth) Auth(r *http.Request, sto users.Store, root string) (*users.User, error) {
var cred jsonCred
if r.Body == nil {
@@ -40,7 +40,7 @@ func (a JSONAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users
// If ReCaptcha is enabled, check the code.
if a.ReCaptcha != nil && len(a.ReCaptcha.Secret) > 0 {
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:shadow
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:govet
if err != nil {
return nil, err
+1 -1
View File
@@ -14,7 +14,7 @@ const MethodNoAuth settings.AuthMethod = "noauth"
type NoAuth struct{}
// Auth uses authenticates user 1.
func (a NoAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) {
func (a NoAuth) Auth(r *http.Request, sto users.Store, root string) (*users.User, error) {
return sto.Get(root, uint(1))
}
+1 -1
View File
@@ -18,7 +18,7 @@ type ProxyAuth struct {
}
// Auth authenticates the user via an HTTP header.
func (a ProxyAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) {
func (a ProxyAuth) Auth(r *http.Request, sto users.Store, root string) (*users.User, error) {
username := r.Header.Get(a.Header)
user, err := sto.Get(root, username)
if err == errors.ErrNotExist {
+1 -1
View File
@@ -14,7 +14,7 @@ var cmdsAddCmd = &cobra.Command{
Use: "add <event> <command>",
Short: "Add a command to run on a specific event",
Long: `Add a command to run on a specific event.`,
Args: cobra.MinimumNArgs(2), //nolint:mnd
Args: cobra.MinimumNArgs(2), //nolint:gomnd
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
s, err := d.store.Settings.Get()
checkErr(err)
+2 -2
View File
@@ -23,7 +23,7 @@ You can also specify an optional parameter (index_end) so
you can remove all commands from 'index' to 'index_end',
including 'index_end'.`,
Args: func(cmd *cobra.Command, args []string) error {
if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil { //nolint:mnd
if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil { //nolint:gomnd
return err
}
@@ -43,7 +43,7 @@ including 'index_end'.`,
i, err := strconv.Atoi(args[1])
checkErr(err)
f := i
if len(args) == 3 { //nolint:mnd
if len(args) == 3 { //nolint:gomnd
f, err = strconv.Atoi(args[2])
checkErr(err)
}
+3 -1
View File
@@ -41,6 +41,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
flags.String("recaptcha.secret", "", "ReCaptcha secret")
flags.String("branding.name", "", "replace 'File Browser' by this name")
flags.String("branding.color", "", "set the theme color")
flags.String("branding.files", "", "path to directory with images and custom styles")
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
}
@@ -121,7 +122,7 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
}
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) //nolint:gomnd
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
@@ -131,6 +132,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Branding.DisableExternal)
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color)
fmt.Fprintln(w, "\nServer:")
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)
+2
View File
@@ -51,6 +51,8 @@ you want to change. Other options will remain unchanged.`,
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
case "branding.name":
set.Branding.Name = mustGetString(flags, flag.Name)
case "branding.color":
set.Branding.Color = mustGetString(flags, flag.Name)
case "branding.disableExternal":
set.Branding.DisableExternal = mustGetBool(flags, flag.Name)
case "branding.files":
+23 -9
View File
@@ -3,7 +3,8 @@ package cmd
import (
"crypto/tls"
"errors"
"io/ioutil"
"io"
"io/fs"
"log"
"net"
"net/http"
@@ -22,6 +23,7 @@ import (
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/diskcache"
"github.com/filebrowser/filebrowser/v2/frontend"
fbhttp "github.com/filebrowser/filebrowser/v2/http"
"github.com/filebrowser/filebrowser/v2/img"
"github.com/filebrowser/filebrowser/v2/settings"
@@ -59,13 +61,14 @@ func addServerFlags(flags *pflag.FlagSet) {
flags.StringP("key", "k", "", "tls key")
flags.StringP("root", "r", ".", "root to prepend to relative paths")
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
flags.Uint32("socket-perm", 0666, "unix socket file permissions")
flags.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")
flags.Bool("disable-type-detection-by-header", false, "disables type detection by reading file headers")
}
var rootCmd = &cobra.Command{
@@ -125,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)
@@ -151,12 +154,15 @@ user created with the credentials from options "username" and "password".`,
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
checkErr(err)
case server.TLSKey != "" && server.TLSCert != "":
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:shadow
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet
checkErr(err)
listener, err = tls.Listen("tcp", adr, &tls.Config{Certificates: []tls.Certificate{cer}}) //nolint:shadow
listener, err = tls.Listen("tcp", adr, &tls.Config{
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{cer}},
)
checkErr(err)
default:
listener, err = net.Listen("tcp", adr) //nolint:shadow
listener, err = net.Listen("tcp", adr)
checkErr(err)
}
@@ -164,7 +170,12 @@ user created with the credentials from options "username" and "password".`,
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
go cleanupHandler(listener, sigc)
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server)
assetsFs, err := fs.Sub(frontend.Assets(), "dist")
if err != nil {
panic(err)
}
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
checkErr(err)
defer listener.Close()
@@ -243,6 +254,9 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
server.ResizePreview = !disablePreviewResize
_, disableTypeDetectionByHeader := getParamB(flags, "disable-type-detection-by-header")
server.TypeDetectionByHeader = !disableTypeDetectionByHeader
_, disableExec := getParamB(flags, "disable-exec")
server.EnableExec = !disableExec
@@ -285,7 +299,7 @@ func setupLog(logMethod string) {
case "stderr":
log.SetOutput(os.Stderr)
case "":
log.SetOutput(ioutil.Discard)
log.SetOutput(io.Discard)
default:
log.SetOutput(&lumberjack.Logger{
Filename: logMethod,
+2 -2
View File
@@ -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
}
@@ -44,7 +44,7 @@ including 'index_end'.`,
i, err := strconv.Atoi(args[0])
checkErr(err)
f := i
if len(args) == 2 { //nolint:mnd
if len(args) == 2 { //nolint:gomnd
f, err = strconv.Atoi(args[1])
checkErr(err)
}
+2 -2
View File
@@ -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
}
+1 -1
View File
@@ -15,7 +15,7 @@ var usersAddCmd = &cobra.Command{
Use: "add <username> <password>",
Short: "Create a new user",
Long: `Create a new user and add it to the database.`,
Args: cobra.ExactArgs(2), //nolint:mnd
Args: cobra.ExactArgs(2), //nolint:gomnd
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
s, err := d.store.Settings.Get()
checkErr(err)
+1 -1
View File
@@ -67,7 +67,7 @@ list or set it to 0.`,
// with the new username. If there is, print an error and cancel the
// operation
if user.Username != onDB.Username {
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:shadow
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
}
}
+1 -1
View File
@@ -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:shadow
if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet,gomnd
return false, err
}
return false, nil
+34
View File
@@ -0,0 +1,34 @@
module.exports = {
rules: {
'body-leading-blank': [1, 'always'],
'body-max-line-length': [2, 'always', 100],
'footer-leading-blank': [1, 'always'],
'footer-max-line-length': [2, 'always', 100],
'header-max-length': [2, 'always', 100],
'scope-case': [2, 'always', 'lower-case'],
'subject-case': [
2,
'never',
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
],
'subject-full-stop': [2, 'never', '.'],
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'revert',
'refactor',
'build',
'ci',
'test',
'chore',
'docs',
],
],
},
};
+28
View File
@@ -0,0 +1,28 @@
SHELL := /bin/bash
DATE ?= $(shell date +%FT%T%z)
BASE_PATH := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
VERSION ?= $(shell git describe --tags --always --match=v* 2> /dev/null || \
cat $(CURDIR)/.version 2> /dev/null || echo v0)
VERSION_HASH = $(shell git rev-parse HEAD)
BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
go = GOGC=off go
MODULE = $(shell env GO111MODULE=on go list -m)
# printing
# $Q (quiet) is used in the targets as a replacer for @.
# This macro helps to print the command for debugging by setting V to 1. Example `make test-unit V=1`
V = 0
Q = $(if $(filter 1,$V),,@)
# $M is a macro to print a colored ▶ character. Example `$(info $(M) running coverage tests…)` will print "▶ running coverage tests…"
M = $(shell printf "\033[34;1m▶\033[0m")
GREEN := $(shell tput -Txterm setaf 2)
YELLOW := $(shell tput -Txterm setaf 3)
WHITE := $(shell tput -Txterm setaf 7)
CYAN := $(shell tput -Txterm setaf 6)
RESET := $(shell tput -Txterm sgr0)
define global_option
printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n" $(1) $(2)
endef
+4 -4
View File
@@ -6,7 +6,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"io"
"os"
"path/filepath"
"sync"
@@ -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
}
@@ -55,7 +55,7 @@ func (f *FileCache) Load(ctx context.Context, key string) (value []byte, exist b
}
defer r.Close()
value, err = ioutil.ReadAll(r)
value, err = io.ReadAll(r)
if err != nil {
return nil, false, err
}
+8
View File
@@ -0,0 +1,8 @@
{
"port": 80,
"baseURL": "",
"address": "",
"log": "stdout",
"database": "/database/filebrowser.db",
"root": "/srv"
}
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/with-contenv bash
# make folders
mkdir -p /database
# copy config
if [ ! -f "/config/settings.json" ]; then
cp -a /defaults/settings.json /config/settings.json
fi
# permissions
chown abc:abc \
/config/settings.json \
/database \
/srv
@@ -0,0 +1,3 @@
#!/usr/bin/with-contenv bash
exec s6-setuidgid abc filebrowser -c /config/settings.json -d /database/filebrowser.db;
+1 -1
View File
@@ -5,4 +5,4 @@
"log": "stdout",
"database": "/database.db",
"root": "/srv"
}
}
+1
View File
@@ -17,4 +17,5 @@ var (
ErrPermissionDenied = errors.New("permission denied")
ErrInvalidRequestParams = errors.New("invalid request params")
ErrSourceIsParent = errors.New("source is parent")
ErrRootUserDeletion = errors.New("user with id 1 can't be deleted")
)
+139 -50
View File
@@ -34,19 +34,24 @@ 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"`
Checksums map[string]string `json:"checksums,omitempty"`
Token string `json:"token,omitempty"`
}
// FileOptions are the options when getting a file info.
type FileOptions struct {
Fs afero.Fs
Path string
Modify bool
Expand bool
Checker rules.Checker
Fs afero.Fs
Path string
Modify bool
Expand bool
ReadHeader bool
Token string
Checker rules.Checker
Content bool
}
// NewFileInfo creates a File object from a path and a given user. This File
@@ -57,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(),
@@ -71,23 +137,10 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
IsDir: info.IsDir(),
Size: info.Size(),
Extension: filepath.Ext(info.Name()),
Token: opts.Token,
}
if opts.Expand {
if file.IsDir {
if err := file.readListing(opts.Checker); err != nil { //nolint:shadow
return nil, err
}
return file, nil
}
err = file.detectType(opts.Modify, 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
@@ -132,9 +185,22 @@ func (i *FileInfo) Checksum(algo string) error {
return nil
}
func (i *FileInfo) RealPath() string {
if realPathFs, ok := i.Fs.(interface {
RealPath(name string) (fPath string, err error)
}); ok {
realPath, err := realPathFs.RealPath(i.Path)
if err == nil {
return realPath
}
}
return i.Path
}
//nolint:goconst
//TODO: use constants
func (i *FileInfo) detectType(modify, saveContent bool) error {
func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
if IsNamedPipe(i.Mode) {
i.Type = "blob"
return nil
@@ -143,25 +209,16 @@ func (i *FileInfo) detectType(modify, saveContent bool) error {
// imagine the situation where a file in a dir with thousands
// of files couldn't be opened: we'd have immediately
// a 500 even though it doesn't matter. So we just log it.
reader, err := i.Fs.Open(i.Path)
if err != nil {
log.Print(err)
i.Type = "blob"
return nil
}
defer reader.Close()
buffer := make([]byte, 512)
n, err := reader.Read(buffer)
if err != nil && err != io.EOF {
log.Print(err)
i.Type = "blob"
return nil
}
mimetype := mime.TypeByExtension(i.Extension)
if mimetype == "" {
mimetype = http.DetectContentType(buffer[:n])
var buffer []byte
if readHeader {
buffer = i.readFirstBytes()
if mimetype == "" {
mimetype = http.DetectContentType(buffer)
}
}
switch {
@@ -175,10 +232,10 @@ func (i *FileInfo) detectType(modify, saveContent bool) error {
case strings.HasPrefix(mimetype, "image"):
i.Type = "image"
return nil
case isBinary(buffer[:n], n) || i.Size > 10*1024*1024: // 10 MB
i.Type = "blob"
case strings.HasSuffix(mimetype, "pdf"):
i.Type = "pdf"
return nil
default:
case (strings.HasPrefix(mimetype, "text") || !isBinary(buffer)) && i.Size <= 10*1024*1024: // 10 MB
i.Type = "text"
if !modify {
@@ -194,11 +251,34 @@ func (i *FileInfo) detectType(modify, saveContent bool) error {
i.Content = string(content)
}
return nil
default:
i.Type = "blob"
}
return nil
}
func (i *FileInfo) readFirstBytes() []byte {
reader, err := i.Fs.Open(i.Path)
if err != nil {
log.Print(err)
i.Type = "blob"
return nil
}
defer reader.Close()
buffer := make([]byte, 512) //nolint:gomnd
n, err := reader.Read(buffer)
if err != nil && err != io.EOF {
log.Print(err)
i.Type = "blob"
return nil
}
return buffer[:n]
}
func (i *FileInfo) detectSubtitles() {
if i.Type != "video" {
return
@@ -207,15 +287,21 @@ func (i *FileInfo) detectSubtitles() {
i.Subtitles = []string{}
ext := filepath.Ext(i.Path)
// TODO: detect multiple languages. Base.Lang.vtt
fPath := strings.TrimSuffix(i.Path, ext) + ".vtt"
if _, err := i.Fs.Stat(fPath); err == nil {
i.Subtitles = append(i.Subtitles, fPath)
// detect multiple languages. Base*.vtt
// TODO: give subtitles descriptive names (lang) and track attributes
parentDir := strings.TrimRight(i.Path, i.Name)
dir, err := afero.ReadDir(i.Fs, parentDir)
if err == nil {
base := strings.TrimSuffix(i.Name, ext)
for _, f := range dir {
if !f.IsDir() && strings.HasPrefix(f.Name(), base) && strings.HasSuffix(f.Name(), ".vtt") {
i.Subtitles = append(i.Subtitles, path.Join(parentDir, f.Name()))
}
}
}
}
func (i *FileInfo) readListing(checker rules.Checker) error {
func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
afs := &afero.Afero{Fs: i.Fs}
dir, err := afs.ReadDir(i.Path)
if err != nil {
@@ -236,7 +322,9 @@ func (i *FileInfo) readListing(checker rules.Checker) 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)
@@ -252,6 +340,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
ModTime: f.ModTime(),
Mode: f.Mode(),
IsDir: f.IsDir(),
IsSymlink: isSymlink,
Extension: filepath.Ext(name),
Path: fPath,
}
@@ -261,7 +350,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
} else {
listing.NumFiles++
err := file.detectType(true, false)
err := file.detectType(true, false, readHeader)
if err != nil {
return err
}
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"unicode/utf8"
)
func isBinary(content []byte, _ int) bool {
func isBinary(content []byte) bool {
maybeStr := string(content)
runeCnt := utf8.RuneCount(content)
runeIndex := 0
+27 -8
View File
@@ -9,6 +9,25 @@ import (
"github.com/spf13/afero"
)
// MoveFile moves file from src to dst.
// By default the rename filesystem system call is used. If src and dst point to different volumes
// the file copy is used as a fallback
func MoveFile(fs afero.Fs, src, dst string) error {
if fs.Rename(src, dst) == nil {
return nil
}
// fallback
err := CopyFile(fs, src, dst)
if err != nil {
_ = fs.Remove(dst)
return err
}
if err := fs.Remove(src); err != nil {
return err
}
return nil
}
// CopyFile copies a file from source to dest and returns
// an error if any.
func CopyFile(fs afero.Fs, source, dest string) error {
@@ -21,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
}
@@ -39,14 +58,14 @@ func CopyFile(fs afero.Fs, source, dest string) error {
return err
}
// Copy the mode if the user can't
// open the file.
// Copy the mode
info, err := fs.Stat(source)
if err != nil {
err = fs.Chmod(dest, info.Mode())
if err != nil {
return err
}
return err
}
err = fs.Chmod(dest, info.Mode())
if err != nil {
return err
}
return nil
+13
View File
@@ -0,0 +1,13 @@
//go:build !dev
// +build !dev
package frontend
import "embed"
//go:embed dist/*
var assets embed.FS
func Assets() embed.FS {
return assets
}
+15
View File
@@ -0,0 +1,15 @@
//go:build dev
// +build dev
package frontend
import (
"io/fs"
"os"
)
var assets fs.FS = os.DirFS("frontend")
func Assets() fs.FS {
return assets
}
+2 -4
View File
@@ -1,5 +1,3 @@
module.exports = {
presets: [
'@vue/app'
]
}
presets: ["@vue/app"],
};
+4
View File
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
+19944 -5881
View File
File diff suppressed because it is too large Load Diff
+20 -10
View File
@@ -4,35 +4,44 @@
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"watch": "vue-cli-service build --watch",
"lint": "vue-cli-service lint --fix"
"build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean",
"lint": "npx vue-cli-service lint --no-fix --max-warnings=0",
"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",
"material-design-icons": "^3.0.1",
"material-icons": "^1.10.5",
"moment": "^2.24.0",
"normalize.css": "^8.0.1",
"noty": "^3.2.0-beta",
"qrcode.vue": "^1.7.0",
"utif": "^3.1.0",
"vue": "^2.6.10",
"vue-i18n": "^8.15.3",
"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": {
@@ -42,7 +51,8 @@
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
"eslint:recommended",
"@vue/prettier"
],
"rules": {},
"parserOptions": {
@@ -57,6 +67,6 @@
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
"not ie < 11"
]
}
+8 -8
View File
@@ -16,7 +16,7 @@
<!-- Add to home screen for Android and modern mobile browsers -->
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
<meta name="theme-color" content="#2979ff">
<meta name="theme-color" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
<!-- Add to home screen for Safari on iOS/iPadOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
@@ -26,11 +26,11 @@
<!-- Add to home screen for Windows -->
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png">
<meta name="msapplication-TileColor" content="#2979ff">
<meta name="msapplication-TileColor" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
<!-- Inject Some Variables and generate the manifest json -->
<script>
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
window.FileBrowser = JSON.parse('[{[ .Json ]}]');
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
var dynamicManifest = {
@@ -51,7 +51,7 @@
"start_url": window.location.origin + window.FileBrowser.BaseURL,
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#455a64"
"theme_color": window.FileBrowser.Color || "#455a64"
}
const stringManifest = JSON.stringify(dynamicManifest);
@@ -77,7 +77,7 @@
opacity: 0;
}
.spinner {
#loading .spinner {
width: 70px;
text-align: center;
position: fixed;
@@ -87,7 +87,7 @@
transform: translate(-50%, -50%);
}
.spinner > div {
#loading .spinner > div {
width: 18px;
height: 18px;
background-color: #333;
@@ -97,12 +97,12 @@
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.spinner .bounce1 {
#loading .spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.spinner .bounce2 {
#loading .spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
+16 -6
View File
@@ -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);
}
@@ -69,13 +69,16 @@ nav > div {
border-color: var(--divider);
}
#breadcrumbs {
.breadcrumbs {
border-color: var(--divider);
color: var(--textPrimary) !important;
}
#breadcrumbs span {
.breadcrumbs span {
color: var(--textPrimary) !important;
}
.breadcrumbs a:hover {
background-color: rgba(255, 255, 255, .1);
}
#listing .item {
background: var(--surfacePrimary);
@@ -114,13 +117,20 @@ nav > div {
background: var(--surfaceSecondary);
}
.dashboard #nav ul li {
color: var(--textSecondary);
}
.dashboard #nav ul li:hover {
background: var(--surfaceSecondary);
}
.card h3,
.dashboard #nav,
.dashboard p label {
color: var(--textPrimary);
}
.card#share ul li input,
.card#share ul li select,
.card#share input,
.card#share select,
.input {
background: var(--surfaceSecondary);
color: var(--textPrimary);
@@ -138,7 +148,7 @@ nav > div {
background: #147A41;
}
.dashboard #nav li,
.dashboard #nav .wrapper,
.collapsible {
border-color: var(--divider);
}
+12 -9
View File
@@ -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>
+11 -11
View File
@@ -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;
}
+83 -71
View File
@@ -1,144 +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 !== null) {
url += `algo=${format}&`
if (format) {
url += `algo=${format}&`;
}
url += `auth=${store.state.jwt}`
window.open(url)
if (store.state.jwt) {
url += `auth=${store.state.jwt}&`;
}
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 = removePrefix(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];
}
+8 -14
View File
@@ -1,15 +1,9 @@
import * as files from './files'
import * as share from './share'
import * as users from './users'
import * as settings from './settings'
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,
commands,
search
}
export { files, share, users, settings, pub, commands, search };
+61
View File
@@ -0,0 +1,61 @@
import { fetchURL, removePrefix } from "./utils";
import { baseURL } from "@/utils/constants";
export async function fetch(url, password = "") {
url = removePrefix(url);
const res = await fetchURL(`/api/public/share${url}`, {
headers: { "X-SHARE-PASSWORD": encodeURIComponent(password) },
});
if (res.status === 200) {
let data = await res.json();
data.url = `/share${url}`;
if (data.isDir) {
if (!data.url.endsWith("/")) data.url += "/";
data.items = data.items.map((item, index) => {
item.index = index;
item.url = `${data.url}${encodeURIComponent(item.name)}`;
if (item.isDir) {
item.url += "/";
}
return item;
});
}
return data;
} else {
throw new Error(res.status);
}
}
export function download(format, hash, token, ...files) {
let url = `${baseURL}/api/public/dl/${hash}`;
if (files.length === 1) {
url += encodeURIComponent(files[0]) + "?";
} else {
let arg = "";
for (let file of files) {
arg += encodeURIComponent(file) + ",";
}
arg = arg.substring(0, arg.length - 1);
arg = encodeURIComponent(arg);
url += `/?files=${arg}&`;
}
if (format) {
url += `algo=${format}&`;
}
if (token) {
url += `token=${token}&`;
}
window.open(url);
}
+16 -16
View File
@@ -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);
}
}
}
+8 -8
View File
@@ -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);
}
}
+20 -16
View File
@@ -1,32 +1,36 @@
import { fetchURL, fetchJSON, removePrefix } from './utils'
import { fetchURL, fetchJSON, removePrefix } from "./utils";
export async function getHash(hash) {
return fetchJSON(`/api/public/share/${hash}`)
export async function list() {
return fetchJSON("/api/shares");
}
export async function 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, 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 });
}
return fetchJSON(url, {
method: 'POST'
})
method: "POST",
body: body,
});
}
+24 -25
View File
@@ -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);
}
}
+32 -30
View File
@@ -1,45 +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) {
if (url.startsWith('/files')) {
url = url.slice(6)
}
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;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+75
View File
@@ -0,0 +1,75 @@
<template>
<div class="breadcrumbs">
<component
:is="element"
:to="base || ''"
:aria-label="$t('files.home')"
:title="$t('files.home')"
>
<i class="material-icons">home</i>
</component>
<span v-for="(link, index) in items" :key="index">
<span class="chevron"
><i class="material-icons">keyboard_arrow_right</i></span
>
<component :is="element" :to="link.url">{{ link.name }}</component>
</span>
</div>
</template>
<script>
export default {
name: "breadcrumbs",
props: ["base", "noLink"],
computed: {
items() {
const relativePath = this.$route.path.replace(this.base, "");
let parts = relativePath.split("/");
if (parts[0] === "") {
parts.shift();
}
if (parts[parts.length - 1] === "") {
parts.pop();
}
let breadcrumbs = [];
for (let i = 0; i < parts.length; i++) {
if (i === 0) {
breadcrumbs.push({
name: decodeURIComponent(parts[i]),
url: this.base + "/" + parts[i] + "/",
});
} else {
breadcrumbs.push({
name: decodeURIComponent(parts[i]),
url: breadcrumbs[i - 1].url + parts[i] + "/",
});
}
}
if (breadcrumbs.length > 3) {
while (breadcrumbs.length !== 4) {
breadcrumbs.shift();
}
breadcrumbs[0].name = "...";
}
return breadcrumbs;
},
element() {
if (this.noLink !== undefined) {
return "span";
}
return "router-link";
},
},
};
</script>
<style></style>
-184
View File
@@ -1,184 +0,0 @@
<template>
<header v-if="!isEditor && !isPreview">
<div>
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action">
<i class="material-icons">menu</i>
</button>
<img :src="logoURL" alt="File Browser">
<search v-if="isLogged"></search>
</div>
<div>
<template v-if="isLogged">
<button @click="openSearch" :aria-label="$t('buttons.search')" :title="$t('buttons.search')" class="search-button action">
<i class="material-icons">search</i>
</button>
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
<i class="material-icons">more_vert</i>
</button>
<!-- Menu that shows on listing AND mobile when there are files selected -->
<div id="file-selection" v-if="isMobile && isListing">
<span v-if="selectedCount > 0">{{ selectedCount }} selected</span>
<share-button v-show="showShareButton"></share-button>
<rename-button v-show="showRenameButton"></rename-button>
<copy-button v-show="showCopyButton"></copy-button>
<move-button v-show="showMoveButton"></move-button>
<delete-button v-show="showDeleteButton"></delete-button>
</div>
<!-- This buttons are shown on a dropdown on mobile phones -->
<div id="dropdown" :class="{ active: showMore }">
<div v-if="!isListing || !isMobile">
<share-button v-show="showShareButton"></share-button>
<rename-button v-show="showRenameButton"></rename-button>
<copy-button v-show="showCopyButton"></copy-button>
<move-button v-show="showMoveButton"></move-button>
<delete-button v-show="showDeleteButton"></delete-button>
</div>
<shell-button v-if="isExecEnabled && user.perm.execute" />
<switch-button v-show="isListing"></switch-button>
<download-button v-show="showDownloadButton"></download-button>
<upload-button v-show="showUpload"></upload-button>
<info-button v-show="isFiles"></info-button>
<button v-show="isListing" @click="toggleMultipleSelection" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action" >
<i class="material-icons">check_circle</i>
<span>{{ $t('buttons.select') }}</span>
</button>
</div>
</template>
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
</div>
</header>
</template>
<script>
import Search from './Search'
import InfoButton from './buttons/Info'
import DeleteButton from './buttons/Delete'
import RenameButton from './buttons/Rename'
import UploadButton from './buttons/Upload'
import DownloadButton from './buttons/Download'
import SwitchButton from './buttons/SwitchView'
import MoveButton from './buttons/Move'
import CopyButton from './buttons/Copy'
import ShareButton from './buttons/Share'
import ShellButton from './buttons/Shell'
import {mapGetters, mapState} from 'vuex'
import { logoURL, enableExec } from '@/utils/constants'
import * as api from '@/api'
import buttons from '@/utils/buttons'
export default {
name: 'header-layout',
components: {
Search,
InfoButton,
DeleteButton,
ShareButton,
RenameButton,
DownloadButton,
CopyButton,
UploadButton,
SwitchButton,
MoveButton,
ShellButton
},
data: function () {
return {
width: window.innerWidth,
pluginData: {
api,
buttons,
'store': this.$store,
'router': this.$router
}
}
},
created () {
window.addEventListener('resize', () => {
this.width = window.innerWidth
})
},
computed: {
...mapGetters([
'selectedCount',
'isFiles',
'isEditor',
'isPreview',
'isListing',
'isLogged'
]),
...mapState([
'req',
'user',
'loading',
'reload',
'multiple'
]),
logoURL: () => logoURL,
isExecEnabled: () => enableExec,
isMobile () {
return this.width <= 736
},
showUpload () {
return this.isListing && this.user.perm.create
},
showDownloadButton () {
return this.isFiles && this.user.perm.download
},
showDeleteButton () {
return this.isFiles && (this.isListing
? (this.selectedCount !== 0 && this.user.perm.delete)
: this.user.perm.delete)
},
showRenameButton () {
return this.isFiles && (this.isListing
? (this.selectedCount === 1 && this.user.perm.rename)
: this.user.perm.rename)
},
showShareButton () {
return this.isFiles && (this.isListing
? (this.selectedCount === 1 && this.user.perm.share)
: this.user.perm.share)
},
showMoveButton () {
return this.isFiles && (this.isListing
? (this.selectedCount > 0 && this.user.perm.rename)
: this.user.perm.rename)
},
showCopyButton () {
return this.isFiles && (this.isListing
? (this.selectedCount > 0 && this.user.perm.create)
: this.user.perm.create)
},
showMore () {
return this.isFiles && this.$store.state.show === 'more'
},
showOverlay () {
return this.showMore
}
},
methods: {
openSidebar () {
this.$store.commit('showHover', 'sidebar')
},
openMore () {
this.$store.commit('showHover', 'more')
},
openSearch () {
this.$store.commit('showHover', 'search')
},
toggleMultipleSelection () {
this.$store.commit('multiple', !this.multiple)
this.resetPrompts()
},
resetPrompts () {
this.$store.commit('closeHovers')
}
}
}
</script>
+73 -68
View File
@@ -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>
+66 -56
View File
@@ -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>
+93 -33
View File
@@ -1,82 +1,142 @@
<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')">
<button
class="action"
@click="toRoot"
:aria-label="$t('sidebar.myFiles')"
:title="$t('sidebar.myFiles')"
>
<i class="material-icons">folder</i>
<span>{{ $t('sidebar.myFiles') }}</span>
</router-link>
<span>{{ $t("sidebar.myFiles") }}</span>
</button>
<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')">
<button
class="action"
@click="toSettings"
:aria-label="$t('sidebar.settings')"
:title="$t('sidebar.settings')"
>
<i class="material-icons">settings_applications</i>
<span>{{ $t('sidebar.settings') }}</span>
</router-link>
<span>{{ $t("sidebar.settings") }}</span>
</button>
<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')
toRoot() {
this.$router.push({ path: "/files/" }, () => {});
this.$store.commit("closeHovers");
},
logout: auth.logout
}
}
toSettings() {
this.$router.push({ path: "/settings" }, () => {});
this.$store.commit("closeHovers");
},
help() {
this.$store.commit("showHover", "help");
},
logout: auth.logout,
},
};
</script>
-17
View File
@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" id="copy-button">
<i class="material-icons">content_copy</i>
<span>{{ $t('buttons.copyFile') }}</span>
</button>
</template>
<script>
export default {
name: 'copy-button',
methods: {
show: function () {
this.$store.commit('showHover', 'copy')
}
}
}
</script>
@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
<i class="material-icons">delete</i>
<span>{{ $t('buttons.delete') }}</span>
</button>
</template>
<script>
export default {
name: 'delete-button',
methods: {
show: function () {
this.$store.commit('showHover', 'delete')
}
}
}
</script>
@@ -1,35 +0,0 @@
<template>
<button @click="download" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" id="download-button" class="action">
<i class="material-icons">file_download</i>
<span>{{ $t('buttons.download') }}</span>
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
</button>
</template>
<script>
import {mapGetters, mapState} from 'vuex'
import { files as api } from '@/api'
export default {
name: 'download-button',
computed: {
...mapState(['req', 'selected']),
...mapGetters(['isListing', 'selectedCount'])
},
methods: {
download: function () {
if (!this.isListing) {
api.download(null, this.$route.path)
return
}
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
api.download(null, this.req.items[this.selected[0]].url)
return
}
this.$store.commit('showHover', 'download')
}
}
}
</script>
-17
View File
@@ -1,17 +0,0 @@
<template>
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="show">
<i class="material-icons">info</i>
<span>{{ $t('buttons.info') }}</span>
</button>
</template>
<script>
export default {
name: 'info-button',
methods: {
show: function () {
this.$store.commit('showHover', 'info')
}
}
}
</script>
-17
View File
@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
<i class="material-icons">forward</i>
<span>{{ $t('buttons.moveFile') }}</span>
</button>
</template>
<script>
export default {
name: 'move-button',
methods: {
show: function () {
this.$store.commit('showHover', 'move')
}
}
}
</script>
@@ -1,22 +0,0 @@
<template>
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="$emit('change-size')">
<i class="material-icons">{{ this.icon }}</i>
<span>{{ $t('buttons.info') }}</span>
</button>
</template>
<script>
export default {
name: 'preview-size-button',
props: [ 'size' ],
computed: {
icon () {
if (this.size) {
return 'photo_size_select_large'
}
return 'hd'
}
}
}
</script>
@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" id="rename-button">
<i class="material-icons">mode_edit</i>
<span>{{ $t('buttons.rename') }}</span>
</button>
</template>
<script>
export default {
name: 'rename-button',
methods: {
show: function () {
this.$store.commit('showHover', 'rename')
}
}
}
</script>
-17
View File
@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.share')" :title="$t('buttons.share')" class="action">
<i class="material-icons">share</i>
<span>{{ $t('buttons.share') }}</span>
</button>
</template>
<script>
export default {
name: 'share-button',
methods: {
show () {
this.$store.commit('showHover', 'share')
}
}
}
</script>
-17
View File
@@ -1,17 +0,0 @@
<template>
<button @click="show" :aria-label="$t('buttons.shell')" :title="$t('buttons.shell')" class="action">
<i class="material-icons">code</i>
<span>{{ $t('buttons.shell') }}</span>
</button>
</template>
<script>
export default {
name: 'shell-button',
methods: {
show: function () {
this.$store.commit('toggleShell')
}
}
}
</script>
@@ -1,40 +0,0 @@
<template>
<button @click="change" :aria-label="$t('buttons.switchView')" :title="$t('buttons.switchView')" class="action" id="switch-view-button">
<i class="material-icons">{{ icon }}</i>
<span>{{ $t('buttons.switchView') }}</span>
</button>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import { users as api } from '@/api'
export default {
name: 'switch-button',
computed: {
...mapState(['user']),
icon: function () {
if (this.user.viewMode === 'mosaic') return 'view_list'
return 'view_module'
}
},
methods: {
...mapMutations([ 'updateUser', 'closeHovers' ]),
change: async function () {
this.closeHovers()
const data = {
id: this.user.id,
viewMode: (this.icon === 'view_list') ? 'list' : 'mosaic'
}
try {
await api.update(data, ['viewMode'])
this.updateUser(data)
} catch (e) {
this.$showError(e)
}
}
}
}
</script>
@@ -1,21 +0,0 @@
<template>
<button @click="upload" :aria-label="$t('buttons.upload')" :title="$t('buttons.upload')" class="action" id="upload-button">
<i class="material-icons">file_upload</i>
<span>{{ $t('buttons.upload') }}</span>
</button>
</template>
<script>
export default {
name: 'upload-button',
methods: {
upload: function () {
if (typeof(DataTransferItem.prototype.webkitGetAsEntry) !== 'undefined') {
this.$store.commit('showHover', 'upload')
} else {
document.getElementById('upload-input').click();
}
}
}
}
</script>
-132
View File
@@ -1,132 +0,0 @@
<template>
<div id="editor-container">
<div class="bar">
<button @click="back" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close" class="action">
<i class="material-icons">close</i>
</button>
<div class="title">
<span>{{ req.name }}</span>
</div>
<button @click="save" v-show="user.perm.modify" :aria-label="$t('buttons.save')" :title="$t('buttons.save')" id="save-button" class="action">
<i class="material-icons">save</i>
</button>
</div>
<div id="breadcrumbs">
<span><i class="material-icons">home</i></span>
<span v-for="(link, index) in breadcrumbs" :key="index">
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
<span>{{ link.name }}</span>
</span>
</div>
<form id="editor"></form>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { files as api } from '@/api'
import buttons from '@/utils/buttons'
import url from '@/utils/url'
import ace from 'ace-builds/src-min-noconflict/ace.js'
import modelist from 'ace-builds/src-min-noconflict/ext-modelist.js'
import 'ace-builds/webpack-resolver'
import { theme } from '@/utils/constants'
export default {
name: 'editor',
data: function () {
return {}
},
computed: {
...mapState(['req', 'user']),
breadcrumbs () {
let parts = this.$route.path.split('/')
if (parts[0] === '') {
parts.shift()
}
if (parts[parts.length - 1] === '') {
parts.pop()
}
let breadcrumbs = []
for (let i = 0; i < parts.length; i++) {
breadcrumbs.push({ name: decodeURIComponent(parts[i]) })
}
breadcrumbs.shift()
if (breadcrumbs.length > 3) {
while (breadcrumbs.length !== 4) {
breadcrumbs.shift()
}
breadcrumbs[0].name = '...'
}
return breadcrumbs
}
},
created () {
window.addEventListener('keydown', this.keyEvent)
},
beforeDestroy () {
window.removeEventListener('keydown', this.keyEvent)
this.editor.destroy();
},
mounted: function () {
const fileContent = this.req.content || '';
this.editor = ace.edit('editor', {
value: fileContent,
showPrintMargin: false,
readOnly: this.req.type === 'textImmutable',
theme: 'ace/theme/chrome',
mode: modelist.getModeForPath(this.req.name).mode,
wrap: true
})
if (theme == 'dark') {
this.editor.setTheme("ace/theme/twilight");
}
},
methods: {
back () {
let uri = url.removeLastDir(this.$route.path) + '/'
this.$router.push({ path: uri })
},
keyEvent (event) {
if (!event.ctrlKey && !event.metaKey) {
return
}
if (String.fromCharCode(event.which).toLowerCase() !== 's') {
return
}
event.preventDefault()
this.save()
},
async save () {
const button = 'save'
buttons.loading('save')
try {
await api.put(this.$route.path, this.editor.getValue())
buttons.success(button)
} catch (e) {
buttons.done(button)
this.$showError(e)
}
}
}
}
</script>
+164 -117
View File
@@ -10,39 +10,33 @@
@mouseup="mouseUp"
@wheel="wheelMove"
>
<img :src="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 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 {
@@ -50,183 +44,236 @@ export default {
lastX: null,
lastY: null,
inDrag: false,
touches: 0,
lastTouchDistance: 0,
moveDisabled: false,
disabledTimer: null,
imageLoaded: false,
position: {
center: { x: 0, y: 0 },
relative: { x: 0, y: 0 }
}
}
relative: { x: 0, y: 0 },
},
maxScale: 4,
minScale: 0.25,
};
},
mounted() {
let container = this.$refs.container
this.classList.forEach(className => container.classList.add(className))
if (!this.decodeUTIF()) {
this.$refs.imgex.src = this.src;
}
let container = this.$refs.container;
this.classList.forEach((className) => container.classList.add(className));
// set width and height if they are zero
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()
}
},
methods: {
onLoad() {
let img = this.$refs.imgex
this.imageLoaded = true
if (img === undefined) {
return
if (!this.decodeUTIF()) {
this.$refs.imgex.src = this.src;
}
img.classList.remove('image-ex-img-center')
this.setCenter()
img.classList.add('image-ex-img-ready')
this.scale = 1;
this.setZoom();
this.setCenter();
},
},
methods: {
// Modified from UTIF.replaceIMG
decodeUTIF() {
const sufs = ["tif", "tiff", "dng", "cr2", "nef"];
let suff = document.location.pathname.split(".").pop().toLowerCase();
if (sufs.indexOf(suff) == -1) return false;
let xhr = new XMLHttpRequest();
UTIF._xhrs.push(xhr);
UTIF._imgs.push(this.$refs.imgex);
xhr.open("GET", this.src);
xhr.responseType = "arraybuffer";
xhr.onload = UTIF._imgLoaded;
xhr.send();
return true;
},
onLoad() {
let img = this.$refs.imgex;
document.addEventListener('mouseup', this.onMouseUp)
this.imageLoaded = true;
if (img === undefined) {
return;
}
img.classList.remove("image-ex-img-center");
this.setCenter();
img.classList.add("image-ex-img-ready");
document.addEventListener("mouseup", this.onMouseUp);
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
event.preventDefault()
this.lastX = null;
this.lastY = null;
this.lastTouchDistance = null;
if (event.targetTouches.length < 2) {
setTimeout(() => {
this.touches = 0;
}, 300);
this.touches++;
if (this.touches > 1) {
this.zoomAuto(event);
}
}
event.preventDefault();
},
zoomAuto(event) {
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
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 {
-464
View File
@@ -1,464 +0,0 @@
<template>
<div v-if="(req.numDirs + req.numFiles) == 0">
<h2 class="message">
<i class="material-icons">sentiment_dissatisfied</i>
<span>{{ $t('files.lonely') }}</span>
</h2>
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
<input style="display:none" type="file" id="upload-folder-input" @change="uploadInput($event)" webkitdirectory multiple>
</div>
<div v-else id="listing"
:class="user.viewMode">
<div>
<div class="item header">
<div></div>
<div>
<p :class="{ active: nameSorted }" class="name"
role="button"
tabindex="0"
@click="sort('name')"
:title="$t('files.sortByName')"
:aria-label="$t('files.sortByName')">
<span>{{ $t('files.name') }}</span>
<i class="material-icons">{{ nameIcon }}</i>
</p>
<p :class="{ active: sizeSorted }" class="size"
role="button"
tabindex="0"
@click="sort('size')"
:title="$t('files.sortBySize')"
:aria-label="$t('files.sortBySize')">
<span>{{ $t('files.size') }}</span>
<i class="material-icons">{{ sizeIcon }}</i>
</p>
<p :class="{ active: modifiedSorted }" class="modified"
role="button"
tabindex="0"
@click="sort('modified')"
:title="$t('files.sortByLastModified')"
:aria-label="$t('files.sortByLastModified')">
<span>{{ $t('files.lastModified') }}</span>
<i class="material-icons">{{ modifiedIcon }}</i>
</p>
</div>
</div>
</div>
<h2 v-if="req.numDirs > 0">{{ $t('files.folders') }}</h2>
<div v-if="req.numDirs > 0">
<item v-for="(item) in dirs"
:key="base64(item.name)"
v-bind:index="item.index"
v-bind:name="item.name"
v-bind:isDir="item.isDir"
v-bind:url="item.url"
v-bind:modified="item.modified"
v-bind:type="item.type"
v-bind:size="item.size">
</item>
</div>
<h2 v-if="req.numFiles > 0">{{ $t('files.files') }}</h2>
<div v-if="req.numFiles > 0">
<item v-for="(item) in files"
:key="base64(item.name)"
v-bind:index="item.index"
v-bind:name="item.name"
v-bind:isDir="item.isDir"
v-bind:url="item.url"
v-bind:modified="item.modified"
v-bind:type="item.type"
v-bind:size="item.size">
</item>
</div>
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
<input style="display:none" type="file" id="upload-folder-input" @change="uploadInput($event)" webkitdirectory multiple>
<div :class="{ active: $store.state.multiple }" id="multiple-selection">
<p>{{ $t('files.multipleSelectionEnabled') }}</p>
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" :title="$t('files.clear')" :aria-label="$t('files.clear')" class="action">
<i class="material-icons">clear</i>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import Item from './ListingItem'
import css from '@/utils/css'
import { users, files as api } from '@/api'
import * as upload from '@/utils/upload'
export default {
name: 'listing',
components: { Item },
data: function () {
return {
showLimit: 50,
dragCounter: 0
}
},
computed: {
...mapState(['req', 'selected', 'user', 'show']),
nameSorted () {
return (this.req.sorting.by === 'name')
},
sizeSorted () {
return (this.req.sorting.by === 'size')
},
modifiedSorted () {
return (this.req.sorting.by === 'modified')
},
ascOrdered () {
return this.req.sorting.asc
},
items () {
const dirs = []
const files = []
this.req.items.forEach((item) => {
if (item.isDir) {
dirs.push(item)
} else {
files.push(item)
}
})
return { dirs, files }
},
dirs () {
return this.items.dirs.slice(0, this.showLimit)
},
files () {
let showLimit = this.showLimit - this.items.dirs.length
if (showLimit < 0) showLimit = 0
return this.items.files.slice(0, showLimit)
},
nameIcon () {
if (this.nameSorted && !this.ascOrdered) {
return 'arrow_upward'
}
return 'arrow_downward'
},
sizeIcon () {
if (this.sizeSorted && this.ascOrdered) {
return 'arrow_downward'
}
return 'arrow_upward'
},
modifiedIcon () {
if (this.modifiedSorted && this.ascOrdered) {
return 'arrow_downward'
}
return 'arrow_upward'
}
},
mounted: function () {
// Check the columns size for the first time.
this.resizeEvent()
// Add the needed event listeners to the window and document.
window.addEventListener('keydown', this.keyEvent)
window.addEventListener('resize', this.resizeEvent)
window.addEventListener('scroll', this.scrollEvent)
document.addEventListener('dragover', this.preventDefault)
document.addEventListener('dragenter', this.dragEnter)
document.addEventListener('dragleave', this.dragLeave)
document.addEventListener('drop', this.drop)
},
beforeDestroy () {
// Remove event listeners before destroying this page.
window.removeEventListener('keydown', this.keyEvent)
window.removeEventListener('resize', this.resizeEvent)
window.removeEventListener('scroll', this.scrollEvent)
document.removeEventListener('dragover', this.preventDefault)
document.removeEventListener('dragenter', this.dragEnter)
document.removeEventListener('dragleave', this.dragLeave)
document.removeEventListener('drop', this.drop)
},
methods: {
...mapMutations([ 'updateUser', 'addSelected' ]),
base64: function (name) {
return window.btoa(unescape(encodeURIComponent(name)))
},
keyEvent (event) {
if (this.show !== null) {
return
}
if (!event.ctrlKey && !event.metaKey) {
return
}
let key = String.fromCharCode(event.which).toLowerCase()
switch (key) {
case 'f':
event.preventDefault()
this.$store.commit('showHover', 'search')
break
case 'c':
case 'x':
this.copyCut(event, key)
break
case 'v':
this.paste(event)
break
case 'a':
event.preventDefault()
for (let file of this.items.files) {
if (this.$store.state.selected.indexOf(file.index) === -1) {
this.addSelected(file.index)
}
}
for (let dir of this.items.dirs) {
if (this.$store.state.selected.indexOf(dir.index) === -1) {
this.addSelected(dir.index)
}
}
break
}
},
preventDefault (event) {
// Wrapper around prevent default.
event.preventDefault()
},
copyCut (event, key) {
if (event.target.tagName.toLowerCase() === 'input') {
return
}
let items = []
for (let i of this.selected) {
items.push({
from: this.req.items[i].url,
name: encodeURIComponent(this.req.items[i].name)
})
}
if (items.length == 0) {
return
}
this.$store.commit('updateClipboard', {
key: key,
items: items,
path: this.$route.path
})
},
paste (event) {
if (event.target.tagName.toLowerCase() === 'input') {
return
}
let items = []
for (let item of this.$store.state.clipboard.items) {
const from = item.from.endsWith('/') ? item.from.slice(0, -1) : item.from
const to = this.$route.path + item.name
items.push({ from, to, name: item.name })
}
if (items.length === 0) {
return
}
let action = (overwrite, rename) => {
api.copy(items, overwrite, rename).then(() => {
this.$store.commit('setReload', true)
}).catch(this.$showError)
}
if (this.$store.state.clipboard.key === 'x') {
action = (overwrite, rename) => {
api.move(items, overwrite, rename).then(() => {
this.$store.commit('resetClipboard')
this.$store.commit('setReload', true)
}).catch(this.$showError)
}
}
if (this.$store.state.clipboard.path == this.$route.path) {
action(false, true)
return
}
let conflict = upload.checkConflict(items, this.req.items)
let overwrite = false
let rename = false
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace-rename',
confirm: (event, option) => {
overwrite = option == 'overwrite'
rename = option == 'rename'
event.preventDefault()
this.$store.commit('closeHovers')
action(overwrite, rename)
}
})
return
}
action(overwrite, rename)
},
resizeEvent () {
// Update the columns size based on the window width.
let columns = Math.floor(document.querySelector('main').offsetWidth / 300)
let items = css(['#listing.mosaic .item', '.mosaic#listing .item'])
if (columns === 0) columns = 1
items.style.width = `calc(${100 / columns}% - 1em)`
},
scrollEvent () {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
this.showLimit += 50
}
},
dragEnter () {
this.dragCounter++
// When the user starts dragging an item, put every
// file on the listing with 50% opacity.
let items = document.getElementsByClassName('item')
Array.from(items).forEach(file => {
file.style.opacity = 0.5
})
},
dragLeave () {
this.dragCounter--
if (this.dragCounter == 0) {
this.resetOpacity()
}
},
drop: async function (event) {
event.preventDefault()
this.dragCounter = 0
this.resetOpacity()
let dt = event.dataTransfer
let el = event.target
if (dt.files.length <= 0) return
for (let i = 0; i < 5; i++) {
if (el !== null && !el.classList.contains('item')) {
el = el.parentElement
}
}
let base = ''
if (el !== null && el.classList.contains('item') && el.dataset.dir === 'true') {
base = el.querySelector('.name').innerHTML + '/'
}
let files = await upload.scanFiles(dt)
let path = this.$route.path.endsWith('/') ? this.$route.path + base : this.$route.path + '/' + base
let items = this.req.items
if (base !== '') {
try {
items = (await api.fetch(path)).items
} catch (error) {
this.$showError(error)
}
}
let conflict = upload.checkConflict(files, items)
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace',
confirm: (event) => {
event.preventDefault()
this.$store.commit('closeHovers')
upload.handleFiles(files, path, true)
}
})
return
}
upload.handleFiles(files, path)
},
uploadInput (event) {
this.$store.commit('closeHovers')
let files = event.currentTarget.files
let folder_upload = files[0].webkitRelativePath !== undefined && files[0].webkitRelativePath !== ''
if (folder_upload) {
for (let i = 0; i < files.length; i++) {
let file = files[i]
files[i].fullPath = file.webkitRelativePath
}
}
let path = this.$route.path.endsWith('/') ? this.$route.path : this.$route.path + '/'
let conflict = upload.checkConflict(files, this.req.items)
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace',
confirm: (event) => {
event.preventDefault()
this.$store.commit('closeHovers')
upload.handleFiles(files, path, true)
}
})
return
}
upload.handleFiles(files, path)
},
resetOpacity () {
let items = document.getElementsByClassName('item')
Array.from(items).forEach(file => {
file.style.opacity = 1
})
},
async sort (by) {
let asc = false
if (by === 'name') {
if (this.nameIcon === 'arrow_upward') {
asc = true
}
} else if (by === 'size') {
if (this.sizeIcon === 'arrow_upward') {
asc = true
}
} else if (by === 'modified') {
if (this.modifiedIcon === 'arrow_upward') {
asc = true
}
}
try {
await users.update({ id: this.user.id, sorting: { by, asc } }, ['sorting'])
} catch (e) {
this.$showError(e)
}
this.$store.commit('setReload', true)
}
}
}
</script>
+148 -126
View File
@@ -1,20 +1,24 @@
<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"
:data-type="type"
:aria-label="name"
:aria-selected="isSelected"
>
<div>
<img v-if="type==='image' && isThumbsEnabled" v-lazy="thumbnailUrl">
<i v-else class="material-icons">{{ icon }}</i>
<img
v-if="readOnly == undefined && type === 'image' && isThumbsEnabled"
v-lazy="thumbnailUrl"
/>
<i v-else class="material-icons"></i>
</div>
<div>
@@ -31,196 +35,214 @@
</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'],
props: [
"name",
"isDir",
"url",
"type",
"size",
"modified",
"index",
"readOnly",
],
computed: {
...mapState(['user', 'selected', 'req', 'user', 'jwt']),
...mapGetters(['selectedCount']),
isSelected () {
return (this.selected.indexOf(this.index) !== -1)
...mapState(["user", "selected", "req", "jwt"]),
...mapGetters(["selectedCount"]),
singleClick() {
return this.readOnly == undefined && this.user.singleClick;
},
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'
isSelected() {
return this.selected.indexOf(this.index) !== -1;
},
isDraggable () {
return this.user.perm.rename
isDraggable() {
return this.readOnly == undefined && this.user.perm.rename;
},
canDrop () {
if (!this.isDir) 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\//, '')
return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true`
thumbnailUrl() {
const path = this.url.replace(/^\/files\//, "");
// reload the image when the file is replaced
const key = Date.parse(this.modified);
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()
if (this.readOnly == undefined && this.user.dateFormat) {
return moment(this.modified).format("L LT");
}
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.user.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.user.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.user.singleClick && !event.ctrlKey && !this.$store.state.multiple) this.resetSelected()
this.addSelected(this.index)
},
dblclick: function () {
if (!this.user.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>
-236
View File
@@ -1,236 +0,0 @@
<template>
<div id="previewer">
<div class="bar">
<button @click="back" class="action" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close">
<i class="material-icons">close</i>
</button>
<div class="title">{{ this.name }}</div>
<preview-size-button v-if="isResizeEnabled && this.req.type === 'image'" @change-size="toggleSize" v-bind:size="fullSize" :disabled="loading"></preview-size-button>
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
<i class="material-icons">more_vert</i>
</button>
<div id="dropdown" :class="{ active : showMore }">
<rename-button :disabled="loading" v-if="user.perm.rename"></rename-button>
<delete-button :disabled="loading" v-if="user.perm.delete"></delete-button>
<download-button :disabled="loading" v-if="user.perm.download"></download-button>
<info-button :disabled="loading"></info-button>
</div>
</div>
<div class="loading" v-if="loading">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
</div>
<button class="action" @click="prev" v-show="hasPrevious" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')">
<i class="material-icons">chevron_left</i>
</button>
<button class="action" @click="next" v-show="hasNext" :aria-label="$t('buttons.next')" :title="$t('buttons.next')">
<i class="material-icons">chevron_right</i>
</button>
<template v-if="!loading">
<div class="preview">
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
<audio v-else-if="req.type == 'audio'" :src="raw" autoplay controls></audio>
<video v-else-if="req.type == 'video'" :src="raw" autoplay controls>
<track
kind="captions"
v-for="(sub, index) in subtitles"
:key="index"
:src="sub"
:label="'Subtitle ' + index" :default="index === 0">
Sorry, your browser doesn't support embedded videos,
but don't worry, you can <a :href="download">download it</a>
and watch it with your favorite video player!
</video>
<object v-else-if="req.extension.toLowerCase() == '.pdf'" class="pdf" :data="raw"></object>
<a v-else-if="req.type == 'blob'" :href="download">
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2>
</a>
</div>
</template>
<div v-show="showMore" @click="resetPrompts" class="overlay"></div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import { baseURL, resizePreview } from '@/utils/constants'
import { files as api } from '@/api'
import PreviewSizeButton from '@/components/buttons/PreviewSize'
import InfoButton from '@/components/buttons/Info'
import DeleteButton from '@/components/buttons/Delete'
import RenameButton from '@/components/buttons/Rename'
import DownloadButton from '@/components/buttons/Download'
import ExtendedImage from './ExtendedImage'
const mediaTypes = [
"image",
"video",
"audio",
"blob"
]
export default {
name: 'preview',
components: {
PreviewSizeButton,
InfoButton,
DeleteButton,
RenameButton,
DownloadButton,
ExtendedImage
},
data: function () {
return {
previousLink: '',
nextLink: '',
listing: null,
name: '',
subtitles: [],
fullSize: false
}
},
computed: {
...mapState(['req', 'user', 'oldReq', 'jwt', 'loading', 'show']),
hasPrevious () {
return (this.previousLink !== '')
},
hasNext () {
return (this.nextLink !== '')
},
download () {
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
},
previewUrl () {
if (this.req.type === 'image' && !this.fullSize) {
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}`
}
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
},
raw () {
return `${this.previewUrl}&inline=true`
},
showMore () {
return this.$store.state.show === 'more'
},
isResizeEnabled () {
return resizePreview
}
},
watch: {
$route: function () {
this.updatePreview()
}
},
async mounted () {
window.addEventListener('keydown', this.key)
this.$store.commit('setPreviewMode', true)
this.listing = this.oldReq.items
this.$root.$on('preview-deleted', this.deleted)
this.updatePreview()
},
beforeDestroy () {
window.removeEventListener('keydown', this.key)
this.$store.commit('setPreviewMode', false)
this.$root.$off('preview-deleted', this.deleted)
},
methods: {
deleted () {
this.listing = this.listing.filter(item => item.name !== this.name)
if (this.hasNext) {
this.next()
} else if (!this.hasPrevious && !this.hasNext) {
this.back()
} else {
this.prev()
}
},
back () {
this.$store.commit('setPreviewMode', false)
let uri = url.removeLastDir(this.$route.path) + '/'
this.$router.push({ path: uri })
},
prev () {
this.$router.push({ path: this.previousLink })
},
next () {
this.$router.push({ path: this.nextLink })
},
key (event) {
if (this.show !== null) {
return
}
if (event.which === 13 || event.which === 39) { // right arrow
if (this.hasNext) this.next()
} else if (event.which === 37) { // left arrow
if (this.hasPrevious) this.prev()
}
},
async updatePreview () {
if (this.req.subtitles) {
this.subtitles = this.req.subtitles.map(sub => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`)
}
let dirs = this.$route.fullPath.split("/")
this.name = decodeURIComponent(dirs[dirs.length - 1])
if (!this.listing) {
try {
const path = url.removeLastDir(this.$route.path)
const res = await api.fetch(path)
this.listing = res.items
} catch (e) {
this.$showError(e)
}
}
this.previousLink = ''
this.nextLink = ''
for (let i = 0; i < this.listing.length; i++) {
if (this.listing[i].name !== this.name) {
continue
}
for (let j = i - 1; j >= 0; j--) {
if (mediaTypes.includes(this.listing[j].type)) {
this.previousLink = this.listing[j].url
break
}
}
for (let j = i + 1; j < this.listing.length; j++) {
if (mediaTypes.includes(this.listing[j].type)) {
this.nextLink = this.listing[j].url
break
}
}
return
}
},
openMore () {
this.$store.commit('showHover', 'more')
},
resetPrompts () {
this.$store.commit('closeHovers')
},
toggleSize () {
this.fullSize = !this.fullSize
}
}
}
</script>
+25
View File
@@ -0,0 +1,25 @@
<template>
<button @click="action" :aria-label="label" :title="label" class="action">
<i class="material-icons">{{ icon }}</i>
<span>{{ label }}</span>
<span v-if="counter > 0" class="counter">{{ counter }}</span>
</button>
</template>
<script>
export default {
name: "action",
props: ["icon", "label", "counter", "show"],
methods: {
action: function () {
if (this.show) {
this.$store.commit("showHover", this.show);
}
this.$emit("action");
},
},
};
</script>
<style></style>
@@ -0,0 +1,58 @@
<template>
<header>
<img v-if="showLogo !== undefined" :src="logoURL" />
<action
v-if="showMenu !== undefined"
class="menu-button"
icon="menu"
:label="$t('buttons.toggleSidebar')"
@action="openSidebar()"
/>
<slot />
<div id="dropdown" :class="{ active: this.$store.state.show === 'more' }">
<slot name="actions" />
</div>
<action
v-if="this.$slots.actions"
id="more"
icon="more_vert"
:label="$t('buttons.more')"
@action="$store.commit('showHover', 'more')"
/>
<div
class="overlay"
v-show="this.$store.state.show == 'more'"
@click="$store.commit('closeHovers')"
/>
</header>
</template>
<script>
import { logoURL } from "@/utils/constants";
import Action from "@/components/header/Action";
export default {
name: "header-bar",
props: ["showLogo", "showMenu"],
components: {
Action,
},
data: function () {
return {
logoURL,
};
},
methods: {
openSidebar() {
this.$store.commit("showHover", "sidebar");
},
},
};
</script>
<style></style>
+65 -54
View File
@@ -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>
+44 -32
View File
@@ -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'])
...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.$root.$emit('preview-deleted')
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>
+27 -33
View File
@@ -1,49 +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 class="button button--block" @click="download('zip')" v-focus>zip</button>
<button class="button button--block" @click="download('tar')" v-focus>tar</button>
<button class="button button--block" @click="download('targz')" v-focus>tar.gz</button>
<button class="button button--block" @click="download('tarbz2')" v-focus>tar.bz2</button>
<button class="button button--block" @click="download('tarxz')" v-focus>tar.xz</button>
<button class="button button--block" @click="download('tarlz4')" v-focus>tar.lz4</button>
<button class="button button--block" @click="download('tarsz')" v-focus>tar.sz</button>
<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 {mapGetters, mapState} from 'vuex'
import { files as api } from '@/api'
import { mapState } from "vuex";
export default {
name: 'download',
computed: {
...mapState(['selected', 'req']),
...mapGetters(['selectedCount'])
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",
},
};
},
methods: {
download: function (format) {
if (this.selectedCount === 0) {
api.download(format, this.$route.path)
} else {
let files = []
for (let i of this.selected) {
files.push(this.req.items[i].url)
}
api.download(format, ...files)
}
this.$store.commit('closeHovers')
}
}
}
computed: mapState(["showConfirm"]),
};
</script>
+61 -51
View File
@@ -1,128 +1,138 @@
<template>
<div>
<ul class="file-list">
<li @click="select"
<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' ]),
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);
},
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>
+17 -14
View File
@@ -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>
+93 -40
View File
@@ -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]]).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>

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