Compare commits

...

11 Commits

Author SHA1 Message Date
Oleg Lobanov
1529e796df chore(release): 2.7.0 2020-09-11 19:21:08 +02:00
Oleg Lobanov
d4b904b92b chore: pass docker password via stdin 2020-09-11 18:57:14 +02:00
Oleg Lobanov
12d4177823 build: bump go version to 1.15.2 (#1081) 2020-09-11 18:07:01 +02:00
Oleg Lobanov
8142b32f38 feat: put selected files in the root of the archive (closes #1065) 2020-09-11 16:54:22 +02:00
Oleg Lobanov
c5abbb4e1c chore: fix lint errors 2020-09-11 16:02:16 +02:00
Oleg Lobanov
65ac73414f feat: add --socket-perm flag to control unix socket file permissions (closes #1060) 2020-09-11 15:59:06 +02:00
Oleg Lobanov
ede4213c8e chore: fix french translation (closes #1071) 2020-09-11 15:16:58 +02:00
Agneev Mukherjee
b60d291490 chore: fix URLs for assets (#1074) 2020-09-05 15:23:42 +02:00
Oleg Lobanov
b9ede79888 Merge pull request #1066 from ramiresviana/preview-mobile-dropdown 2020-08-25 16:33:57 +02:00
Ramires Viana
3d2cb838d1 feat: preview size button 2020-08-25 14:14:15 +00:00
Ramires Viana
778734419d feat: preview mobile dropdown 2020-08-18 12:47:23 +00:00
15 changed files with 206 additions and 21 deletions

View File

@@ -23,7 +23,7 @@ jobs:
- '*' - '*'
test: test:
docker: docker:
- image: circleci/golang:1.14.6 - image: circleci/golang:1.15.2
steps: steps:
- checkout - checkout
- run: - run:
@@ -31,7 +31,7 @@ jobs:
command: go test ./... command: go test ./...
build-go: build-go:
docker: docker:
- image: circleci/golang:1.14.6 - image: circleci/golang:1.15.2
steps: steps:
- attach_workspace: - attach_workspace:
at: '~/project' at: '~/project'
@@ -49,12 +49,12 @@ jobs:
- '*' - '*'
release: release:
docker: docker:
- image: circleci/golang:1.14.6 - image: circleci/golang:1.15.2
steps: steps:
- attach_workspace: - attach_workspace:
at: '~/project' at: '~/project'
- setup_remote_docker - setup_remote_docker
- run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD - run: echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin
- run: curl -sL https://git.io/goreleaser | bash - run: curl -sL https://git.io/goreleaser | bash
- run: docker logout - run: docker logout
workflows: workflows:

View File

@@ -2,6 +2,16 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [2.7.0](https://github.com/filebrowser/filebrowser/compare/v2.6.2...v2.7.0) (2020-09-11)
### Features
* add --socket-perm flag to control unix socket file permissions (closes [#1060](https://github.com/filebrowser/filebrowser/issues/1060)) ([65ac734](https://github.com/filebrowser/filebrowser/commit/65ac73414fadc4686c94803a93ff319e8f7ce9d1))
* preview mobile dropdown ([7787344](https://github.com/filebrowser/filebrowser/commit/778734419de314d4cb64d07109bbab73f8e2e42a))
* preview size button ([3d2cb83](https://github.com/filebrowser/filebrowser/commit/3d2cb838d111ee61047599f49e76de80c821f341))
* put selected files in the root of the archive (closes [#1065](https://github.com/filebrowser/filebrowser/issues/1065)) ([8142b32](https://github.com/filebrowser/filebrowser/commit/8142b32f3865eccd3331328e0d087f805d186ed5))
### [2.6.2](https://github.com/filebrowser/filebrowser/compare/v2.6.1...v2.6.2) (2020-08-05) ### [2.6.2](https://github.com/filebrowser/filebrowser/compare/v2.6.1...v2.6.2) (2020-08-05)
### [2.6.1](https://github.com/filebrowser/filebrowser/compare/v2.6.0...v2.6.1) (2020-07-28) ### [2.6.1](https://github.com/filebrowser/filebrowser/compare/v2.6.0...v2.6.1) (2020-07-28)

View File

@@ -58,6 +58,7 @@ func addServerFlags(flags *pflag.FlagSet) {
flags.StringP("key", "k", "", "tls key") flags.StringP("key", "k", "", "tls key")
flags.StringP("root", "r", ".", "root to prepend to relative paths") flags.StringP("root", "r", ".", "root to prepend to relative paths")
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)") flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
flags.Uint32("socket-perm", 0666, "unix socket file permissions")
flags.StringP("baseurl", "b", "", "base url") flags.StringP("baseurl", "b", "", "base url")
flags.String("cache-dir", "", "file cache directory (disabled if empty)") flags.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")
@@ -143,6 +144,10 @@ user created with the credentials from options "username" and "password".`,
case server.Socket != "": case server.Socket != "":
listener, err = net.Listen("unix", server.Socket) listener, err = net.Listen("unix", server.Socket)
checkErr(err) checkErr(err)
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
checkErr(err)
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
checkErr(err)
case server.TLSKey != "" && server.TLSCert != "": case server.TLSKey != "" && server.TLSCert != "":
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:shadow cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:shadow
checkErr(err) checkErr(err)

View File

@@ -3,6 +3,7 @@ package fileutils
import ( import (
"io" "io"
"os" "os"
"path"
"path/filepath" "path/filepath"
"github.com/spf13/afero" "github.com/spf13/afero"
@@ -50,3 +51,59 @@ func CopyFile(fs afero.Fs, source, dest string) error {
return nil return nil
} }
// CommonPrefix returns common directory path of provided files
func CommonPrefix(sep byte, paths ...string) string {
// Handle special cases.
switch len(paths) {
case 0:
return ""
case 1:
return path.Clean(paths[0])
}
// Note, we treat string as []byte, not []rune as is often
// done in Go. (And sep as byte, not rune). This is because
// most/all supported OS' treat paths as string of non-zero
// bytes. A filename may be displayed as a sequence of Unicode
// runes (typically encoded as UTF-8) but paths are
// not required to be valid UTF-8 or in any normalized form
// (e.g. "é" (U+00C9) and "é" (U+0065,U+0301) are different
// file names.
c := []byte(path.Clean(paths[0]))
// We add a trailing sep to handle the case where the
// common prefix directory is included in the path list
// (e.g. /home/user1, /home/user1/foo, /home/user1/bar).
// path.Clean will have cleaned off trailing / separators with
// the exception of the root directory, "/" (in which case we
// make it "//", but this will get fixed up to "/" bellow).
c = append(c, sep)
// Ignore the first path since it's already in c
for _, v := range paths[1:] {
// Clean up each path before testing it
v = path.Clean(v) + string(sep)
// Find the first non-common byte and truncate c
if len(v) < len(c) {
c = c[:len(v)]
}
for i := 0; i < len(c); i++ {
if v[i] != c[i] {
c = c[:i]
break
}
}
}
// Remove trailing non-separator characters and the final separator
for i := len(c) - 1; i >= 0; i-- {
if c[i] == sep {
c = c[:i]
break
}
}
return string(c)
}

46
fileutils/file_test.go Normal file
View File

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

View File

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

View File

@@ -1,5 +1,5 @@
<template> <template>
<header v-if="!isEditor"> <header v-if="!isEditor && !isPreview">
<div> <div>
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action"> <button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action">
<i class="material-icons">menu</i> <i class="material-icons">menu</i>
@@ -108,6 +108,7 @@ export default {
'selectedCount', 'selectedCount',
'isFiles', 'isFiles',
'isEditor', 'isEditor',
'isPreview',
'isListing', 'isListing',
'isLogged' 'isLogged'
]), ]),

View File

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

View File

@@ -77,6 +77,13 @@ export default {
window.removeEventListener('resize', this.onResize) window.removeEventListener('resize', this.onResize)
document.removeEventListener('mouseup', this.onMouseUp) document.removeEventListener('mouseup', this.onMouseUp)
}, },
watch: {
src: function () {
this.scale = 1
this.setZoom()
this.setCenter()
}
},
methods: { methods: {
onLoad() { onLoad() {
let img = this.$refs.imgex let img = this.$refs.imgex

View File

@@ -9,10 +9,17 @@
<span>{{ this.name }}</span> <span>{{ this.name }}</span>
</div> </div>
<rename-button :disabled="loading" v-if="user.perm.rename"></rename-button> <preview-size-button v-if="isResizeEnabled && this.req.type === 'image'" @change-size="toggleSize" v-bind:size="fullSize" :disabled="loading"></preview-size-button>
<delete-button :disabled="loading" v-if="user.perm.delete"></delete-button> <button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
<download-button :disabled="loading" v-if="user.perm.download"></download-button> <i class="material-icons">more_vert</i>
<info-button :disabled="loading"></info-button> </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>
<div class="loading" v-if="loading"> <div class="loading" v-if="loading">
@@ -51,14 +58,17 @@
</a> </a>
</div> </div>
</template> </template>
<div v-show="showMore" @click="resetPrompts" class="overlay"></div>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import url from '@/utils/url' import url from '@/utils/url'
import { baseURL } from '@/utils/constants' import { baseURL, resizePreview } from '@/utils/constants'
import { files as api } from '@/api' import { files as api } from '@/api'
import PreviewSizeButton from '@/components/buttons/PreviewSize'
import InfoButton from '@/components/buttons/Info' import InfoButton from '@/components/buttons/Info'
import DeleteButton from '@/components/buttons/Delete' import DeleteButton from '@/components/buttons/Delete'
import RenameButton from '@/components/buttons/Rename' import RenameButton from '@/components/buttons/Rename'
@@ -75,6 +85,7 @@ const mediaTypes = [
export default { export default {
name: 'preview', name: 'preview',
components: { components: {
PreviewSizeButton,
InfoButton, InfoButton,
DeleteButton, DeleteButton,
RenameButton, RenameButton,
@@ -87,7 +98,8 @@ export default {
nextLink: '', nextLink: '',
listing: null, listing: null,
name: '', name: '',
subtitles: [] subtitles: [],
fullSize: false
} }
}, },
computed: { computed: {
@@ -102,13 +114,19 @@ export default {
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}` return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
}, },
previewUrl () { previewUrl () {
if (this.req.type === 'image') { if (this.req.type === 'image' && !this.fullSize) {
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}` 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}` return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
}, },
raw () { raw () {
return `${this.previewUrl}&inline=true` return `${this.previewUrl}&inline=true`
},
showMore () {
return this.$store.state.show === 'more'
},
isResizeEnabled () {
return resizePreview
} }
}, },
watch: { watch: {
@@ -189,6 +207,15 @@ export default {
return return
} }
},
openMore () {
this.$store.commit('showHover', 'more')
},
resetPrompts () {
this.$store.commit('closeHovers')
},
toggleSize () {
this.fullSize = !this.fullSize
} }
} }
} }

View File

@@ -51,7 +51,7 @@
"home": "Accueil", "home": "Accueil",
"lastModified": "Dernière modification", "lastModified": "Dernière modification",
"loading": "Chargement...", "loading": "Chargement...",
"lonely": "Il semble qu'il n'y ai rien par ici...", "lonely": "Il semble qu'il n'y ait rien par ici...",
"metadata": "Metadonnées", "metadata": "Metadonnées",
"multipleSelectionEnabled": "Sélection multiple activée", "multipleSelectionEnabled": "Sélection multiple activée",
"name": "Nom", "name": "Nom",

View File

@@ -3,6 +3,7 @@ const getters = {
isFiles: state => !state.loading && state.route.name === 'Files', isFiles: state => !state.loading && state.route.name === 'Files',
isListing: (state, getters) => getters.isFiles && state.req.isDir, isListing: (state, getters) => getters.isFiles && state.req.isDir,
isEditor: (state, getters) => getters.isFiles && (state.req.type === 'text' || state.req.type === 'textImmutable'), isEditor: (state, getters) => getters.isFiles && (state.req.type === 'text' || state.req.type === 'textImmutable'),
isPreview: state => state.previewMode,
selectedCount: state => state.selected.length, selectedCount: state => state.selected.length,
progress : state => { progress : state => {
if (state.upload.progress.length == 0) { if (state.upload.progress.length == 0) {

View File

@@ -12,6 +12,7 @@ const authMethod = window.FileBrowser.AuthMethod
const loginPage = window.FileBrowser.LoginPage const loginPage = window.FileBrowser.LoginPage
const theme = window.FileBrowser.Theme const theme = window.FileBrowser.Theme
const enableThumbs = window.FileBrowser.EnableThumbs const enableThumbs = window.FileBrowser.EnableThumbs
const resizePreview = window.FileBrowser.ResizePreview
export { export {
name, name,
@@ -26,5 +27,6 @@ export {
authMethod, authMethod,
loginPage, loginPage,
theme, theme,
enableThumbs enableThumbs,
resizePreview
} }

View File

@@ -11,6 +11,7 @@ import (
"github.com/mholt/archiver" "github.com/mholt/archiver"
"github.com/filebrowser/filebrowser/v2/files" "github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/fileutils"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
) )
@@ -97,7 +98,7 @@ var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data)
return rawDirHandler(w, r, d, file) return rawDirHandler(w, r, d, file)
}) })
func addFile(ar archiver.Writer, d *data, path string) error { func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
// Checks are always done with paths with "/" as path separator. // Checks are always done with paths with "/" as path separator.
path = strings.Replace(path, "\\", "/", -1) path = strings.Replace(path, "\\", "/", -1)
if !d.Check(path) { if !d.Check(path) {
@@ -115,10 +116,12 @@ func addFile(ar archiver.Writer, d *data, path string) error {
} }
defer file.Close() defer file.Close()
filename := strings.TrimPrefix(path, commonPath)
filename = strings.TrimPrefix(filename, "/")
err = ar.Write(archiver.File{ err = ar.Write(archiver.File{
FileInfo: archiver.FileInfo{ FileInfo: archiver.FileInfo{
FileInfo: info, FileInfo: info,
CustomName: strings.TrimPrefix(path, "/"), CustomName: filename,
}, },
ReadCloser: file, ReadCloser: file,
}) })
@@ -133,7 +136,7 @@ func addFile(ar archiver.Writer, d *data, path string) error {
} }
for _, name := range names { for _, name := range names {
err = addFile(ar, d, filepath.Join(path, name)) err = addFile(ar, d, filepath.Join(path, name), commonPath)
if err != nil { if err != nil {
return err return err
} }
@@ -167,8 +170,10 @@ func rawDirHandler(w http.ResponseWriter, r *http.Request, d *data, file *files.
} }
defer ar.Close() defer ar.Close()
commonDir := fileutils.CommonPrefix('/', filenames...)
for _, fname := range filenames { for _, fname := range filenames {
err = addFile(ar, d, fname) err = addFile(ar, d, fname, commonDir)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }

View File

@@ -40,6 +40,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, box *
"ReCaptcha": false, "ReCaptcha": false,
"Theme": d.settings.Branding.Theme, "Theme": d.settings.Branding.Theme,
"EnableThumbs": d.server.EnableThumbnails, "EnableThumbs": d.server.EnableThumbnails,
"ResizePreview": d.server.ResizePreview,
} }
if d.settings.Branding.Files != "" { if d.settings.Branding.Files != "" {