Fix ViewMode related bugs:

- The user will no longer lost their 'ViewMode' option after being updated in the settings.
- The console will not output errors due tot he scroll function when Mosaic mode is on.


Former-commit-id: 97aa6abdc8b864dc7a55dbf03a2e58895ea7613f [formerly ff9e6ff0898f32bd106b644b2e9002b5de45281c] [formerly 556cc12bd5ff1d91776c81f48dd1dceb8bb627b4 [formerly 51104c5ee7]]
Former-commit-id: dd63b2b818a7bd4960a7243866d6b2829f4c03a8 [formerly 45855d70eaa9a2b060d3a89cb70388040ea8e6d9]
Former-commit-id: 6e0ebf10d7fe3e2c234abc9c0ffd447cfbdd7455
pull/726/head
Henrique Dias 2017-10-31 18:23:31 +00:00
parent c716d126eb
commit 59a0daa293
7 changed files with 290 additions and 271 deletions

View File

@ -1,231 +1,231 @@
<template> <template>
<div> <div>
<div id="breadcrumbs"> <div id="breadcrumbs">
<router-link to="/files/" :aria-label="$t('files.home')" :title="$t('files.home')"> <router-link to="/files/" :aria-label="$t('files.home')" :title="$t('files.home')">
<i class="material-icons">home</i> <i class="material-icons">home</i>
</router-link> </router-link>
<span v-for="link in breadcrumbs" :key="link.name"> <span v-for="link in breadcrumbs" :key="link.name">
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span> <span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
<router-link :to="link.url">{{ link.name }}</router-link> <router-link :to="link.url">{{ link.name }}</router-link>
</span> </span>
</div> </div>
<div v-if="error"> <div v-if="error">
<not-found v-if="error.message === '404'"></not-found> <not-found v-if="error.message === '404'"></not-found>
<forbidden v-else-if="error.message === '403'"></forbidden> <forbidden v-else-if="error.message === '403'"></forbidden>
<internal-error v-else></internal-error> <internal-error v-else></internal-error>
</div> </div>
<editor v-else-if="isEditor"></editor> <editor v-else-if="isEditor"></editor>
<listing :class="{ multiple }" v-else-if="isListing"></listing> <listing :class="{ multiple }" v-else-if="isListing"></listing>
<preview v-else-if="isPreview"></preview> <preview v-else-if="isPreview"></preview>
<div v-else> <div v-else>
<h2 class="message"> <h2 class="message">
<span>{{ $t('files.loading') }}</span> <span>{{ $t('files.loading') }}</span>
</h2> </h2>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import Forbidden from './errors/403' import Forbidden from './errors/403'
import NotFound from './errors/404' import NotFound from './errors/404'
import InternalError from './errors/500' import InternalError from './errors/500'
import Preview from '@/components/files/Preview' import Preview from '@/components/files/Preview'
import Listing from '@/components/files/Listing' import Listing from '@/components/files/Listing'
import Editor from '@/components/files/Editor' import Editor from '@/components/files/Editor'
import * as api from '@/utils/api' import * as api from '@/utils/api'
import { mapGetters, mapState, mapMutations } from 'vuex' import { mapGetters, mapState, mapMutations } from 'vuex'
export default { export default {
name: 'files', name: 'files',
components: { components: {
Forbidden, Forbidden,
NotFound, NotFound,
InternalError, InternalError,
Preview, Preview,
Listing, Listing,
Editor Editor
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
'selectedCount' 'selectedCount'
]), ]),
...mapState([ ...mapState([
'req', 'req',
'user', 'user',
'reload', 'reload',
'multiple', 'multiple',
'loading' 'loading'
]), ]),
isListing () { isListing () {
return this.req.kind === 'listing' && !this.loading return this.req.kind === 'listing' && !this.loading
}, },
isPreview () { isPreview () {
return this.req.kind === 'preview' && !this.loading return this.req.kind === 'preview' && !this.loading
}, },
isEditor () { isEditor () {
return this.req.kind === 'editor' && !this.loading return this.req.kind === 'editor' && !this.loading
}, },
breadcrumbs () { breadcrumbs () {
let parts = this.$route.path.split('/') let parts = this.$route.path.split('/')
if (parts[0] === '') { if (parts[0] === '') {
parts.shift() parts.shift()
} }
if (parts[parts.length - 1] === '') { if (parts[parts.length - 1] === '') {
parts.pop() parts.pop()
} }
let breadcrumbs = [] let breadcrumbs = []
for (let i = 0; i < parts.length; i++) { for (let i = 0; i < parts.length; i++) {
if (i === 0) { if (i === 0) {
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: '/' + parts[i] + '/' }) breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: '/' + parts[i] + '/' })
} else { } else {
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' }) breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' })
} }
} }
breadcrumbs.shift() breadcrumbs.shift()
if (breadcrumbs.length > 3) { if (breadcrumbs.length > 3) {
while (breadcrumbs.length !== 4) { while (breadcrumbs.length !== 4) {
breadcrumbs.shift() breadcrumbs.shift()
} }
breadcrumbs[0].name = '...' breadcrumbs[0].name = '...'
} }
return breadcrumbs return breadcrumbs
} }
}, },
data: function () { data: function () {
return { return {
error: null error: null
} }
}, },
created () { created () {
this.fetchData() this.fetchData()
}, },
watch: { watch: {
'$route': 'fetchData', '$route': 'fetchData',
'reload': function () { 'reload': function () {
this.fetchData() this.fetchData()
} }
}, },
mounted () { mounted () {
window.addEventListener('keydown', this.keyEvent) window.addEventListener('keydown', this.keyEvent)
window.addEventListener('scroll', this.scroll) window.addEventListener('scroll', this.scroll)
}, },
beforeDestroy () { beforeDestroy () {
window.removeEventListener('keydown', this.keyEvent) window.removeEventListener('keydown', this.keyEvent)
window.removeEventListener('scroll', this.scroll) window.removeEventListener('scroll', this.scroll)
}, },
destroyed () { destroyed () {
this.$store.commit('updateRequest', {}) this.$store.commit('updateRequest', {})
}, },
methods: { methods: {
...mapMutations([ 'setLoading' ]), ...mapMutations([ 'setLoading' ]),
fetchData () { fetchData () {
// Reset view information. // Reset view information.
this.$store.commit('setReload', false) this.$store.commit('setReload', false)
this.$store.commit('resetSelected') this.$store.commit('resetSelected')
this.$store.commit('multiple', false) this.$store.commit('multiple', false)
this.$store.commit('closeHovers') this.$store.commit('closeHovers')
// Set loading to true and reset the error. // Set loading to true and reset the error.
this.setLoading(true) this.setLoading(true)
this.error = null this.error = null
let url = this.$route.path let url = this.$route.path
if (url === '') url = '/' if (url === '') url = '/'
if (url[0] !== '/') url = '/' + url if (url[0] !== '/') url = '/' + url
api.fetch(url) api.fetch(url)
.then((req) => { .then((req) => {
if (!url.endsWith('/') && req.url.endsWith('/')) { if (!url.endsWith('/') && req.url.endsWith('/')) {
window.history.replaceState(window.history.state, document.title, window.location.pathname + '/') window.history.replaceState(window.history.state, document.title, window.location.pathname + '/')
} }
this.$store.commit('updateRequest', req) this.$store.commit('updateRequest', req)
document.title = req.name document.title = req.name
this.setLoading(false) this.setLoading(false)
}) })
.catch(error => { .catch(error => {
this.setLoading(false) this.setLoading(false)
this.error = error this.error = error
}) })
}, },
keyEvent (event) { keyEvent (event) {
// Esc! // Esc!
if (event.keyCode === 27) { if (event.keyCode === 27) {
this.$store.commit('closeHovers') this.$store.commit('closeHovers')
// If we're on a listing, unselect all // If we're on a listing, unselect all
// files and folders. // files and folders.
if (this.req.kind === 'listing') { if (this.req.kind === 'listing') {
this.$store.commit('resetSelected') this.$store.commit('resetSelected')
} }
} }
// Del! // Del!
if (event.keyCode === 46) { if (event.keyCode === 46) {
if (this.req.kind === 'editor' || if (this.req.kind === 'editor' ||
this.$route.name !== 'Files' || this.$route.name !== 'Files' ||
this.loading || this.loading ||
!this.user.allowEdit || !this.user.allowEdit ||
(this.req.kind === 'listing' && this.selectedCount === 0)) return (this.req.kind === 'listing' && this.selectedCount === 0)) return
this.$store.commit('showHover', 'delete') this.$store.commit('showHover', 'delete')
} }
// F1! // F1!
if (event.keyCode === 112) { if (event.keyCode === 112) {
event.preventDefault() event.preventDefault()
this.$store.commit('showHover', 'help') this.$store.commit('showHover', 'help')
} }
// F2! // F2!
if (event.keyCode === 113) { if (event.keyCode === 113) {
if (this.req.kind === 'editor' || if (this.req.kind === 'editor' ||
this.$route.name !== 'Files' || this.$route.name !== 'Files' ||
this.loading || this.loading ||
!this.user.allowEdit || !this.user.allowEdit ||
(this.req.kind === 'listing' && this.selectedCount === 0) || (this.req.kind === 'listing' && this.selectedCount === 0) ||
(this.req.kind === 'listing' && this.selectedCount > 1)) return (this.req.kind === 'listing' && this.selectedCount > 1)) return
this.$store.commit('showHover', 'rename') this.$store.commit('showHover', 'rename')
} }
// CTRL + S // CTRL + S
if (event.ctrlKey || event.metaKey) { if (event.ctrlKey || event.metaKey) {
if (String.fromCharCode(event.which).toLowerCase() === 's') { if (String.fromCharCode(event.which).toLowerCase() === 's') {
event.preventDefault() event.preventDefault()
if (this.req.kind !== 'editor') { if (this.req.kind !== 'editor') {
document.getElementById('download-button').click() document.getElementById('download-button').click()
} }
} }
} }
}, },
scroll (event) { scroll (event) {
if (this.req.kind !== 'listing' || this.$store.state.req.display === 'mosaic') return if (this.req.kind !== 'listing' || this.$store.state.user.viewMode === 'mosaic') return
let top = 112 - window.scrollY let top = 112 - window.scrollY
if (top < 64) { if (top < 64) {
top = 64 top = 64
} }
document.querySelector('#listing.list .item.header').style.top = top + 'px' document.querySelector('#listing.list .item.header').style.top = top + 'px'
}, },
openSidebar () { openSidebar () {
this.$store.commit('showHover', 'sidebar') this.$store.commit('showHover', 'sidebar')
}, },
openSearch () { openSearch () {
this.$store.commit('showHover', 'search') this.$store.commit('showHover', 'search')
} }
} }
} }
</script> </script>

View File

@ -7,10 +7,21 @@
</div> </div>
<div class="card-content"> <div class="card-content">
<p>
<label for="username">{{ $t('settings.username') }}</label>
<input type="text" v-model="username" id="username">
</p>
<p>
<label for="password">{{ $t('settings.password') }}</label>
<input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password">
</p>
<p>
<label for="scope">{{ $t('settings.scope') }}</label>
<input type="text" v-model="filesystem" id="scope">
</p>
<p><label for="username">{{ $t('settings.username') }}</label><input type="text" v-model="username" id="username"></p>
<p><label for="password">{{ $t('settings.password') }}</label><input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password"></p>
<p><label for="scope">{{ $t('settings.scope') }}</label><input type="text" v-model="filesystem" id="scope"></p>
<p> <p>
<label for="locale">{{ $t('settings.language') }}</label> <label for="locale">{{ $t('settings.language') }}</label>
<languages id="locale" :selected.sync="locale"></languages> <languages id="locale" :selected.sync="locale"></languages>
@ -91,6 +102,7 @@ export default {
components: { Languages }, components: { Languages },
data: () => { data: () => {
return { return {
originalUser: null,
id: 0, id: 0,
admin: false, admin: false,
allowNew: false, allowNew: false,
@ -141,6 +153,7 @@ export default {
} }
getUser(user).then(user => { getUser(user).then(user => {
this.originalUser = user
this.id = user.ID this.id = user.ID
this.admin = user.admin this.admin = user.admin
this.allowCommands = user.allowCommands this.allowCommands = user.allowCommands
@ -242,23 +255,21 @@ export default {
}) })
}, },
parseForm () { parseForm () {
let user = { let user = this.originalUser
ID: this.id, user.username = this.username
username: this.username, user.password = this.password
password: this.password, user.lockPassword = this.lockPassword
lockPassword: this.lockPassword, user.filesystem = this.filesystem
filesystem: this.filesystem, user.admin = this.admin
admin: this.admin, user.allowCommands = this.allowCommands
allowCommands: this.allowCommands, user.allowNew = this.allowNew
allowNew: this.allowNew, user.allowEdit = this.allowEdit
allowEdit: this.allowEdit, user.allowPublish = this.allowPublish
allowPublish: this.allowPublish, user.permissions = this.permissions
permissions: this.permissions, user.css = this.css
css: this.css, user.locale = this.locale
locale: this.locale, user.commands = this.commands.split(' ')
commands: this.commands.split(' '), user.rules = []
rules: []
}
let rules = this.rules.split('\n') let rules = this.rules.split('\n')

View File

@ -39,7 +39,7 @@ var (
ErrEmptyScope = errors.New("scope is empty") ErrEmptyScope = errors.New("scope is empty")
ErrWrongDataType = errors.New("wrong data type") ErrWrongDataType = errors.New("wrong data type")
ErrInvalidUpdateField = errors.New("invalid field to update") ErrInvalidUpdateField = errors.New("invalid field to update")
ErrInvalidOption = errors.New("Invalid option") ErrInvalidOption = errors.New("invalid option")
) )
// FileManager is a file manager instance. It should be creating using the // FileManager is a file manager instance. It should be creating using the

View File

@ -1,7 +1,6 @@
package http package http
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@ -14,22 +13,23 @@ import (
fm "github.com/hacdias/filemanager" fm "github.com/hacdias/filemanager"
) )
const reCaptchaAPI = "https://www.google.com/recaptcha/api/siteverify"
type cred struct { type cred struct {
Password string `json:"password"` Password string `json:"password"`
Username string `json:"username"` Username string `json:"username"`
Recaptcha string `json:"recaptcha"` ReCaptcha string `json:"recaptcha"`
} }
// recaptcha checks the recaptcha code. // reCaptcha checks the reCaptcha code.
func recaptcha(secret string, response string) (bool, error) { func reCaptcha(secret string, response string) (bool, error) {
api := "https://www.google.com/recaptcha/api/siteverify"
body := url.Values{} body := url.Values{}
body.Set("secret", secret) body.Set("secret", secret)
body.Add("response", response) body.Add("response", response)
client := &http.Client{} client := &http.Client{}
resp, err := client.Post(api, "application/x-www-form-urlencoded", bytes.NewBufferString(body.Encode()))
resp, err := client.Post(reCaptchaAPI, "application/x-www-form-urlencoded", strings.NewReader(body.Encode()))
if err != nil { if err != nil {
return false, err return false, err
} }
@ -73,7 +73,7 @@ func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, er
// If ReCaptcha is enabled, check the code. // If ReCaptcha is enabled, check the code.
if len(c.ReCaptchaSecret) > 0 { if len(c.ReCaptchaSecret) > 0 {
ok, err := recaptcha(c.ReCaptchaSecret, cred.Recaptcha) ok, err := reCaptcha(c.ReCaptchaSecret, cred.ReCaptcha)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return http.StatusForbidden, err return http.StatusForbidden, err
@ -179,6 +179,7 @@ func validateAuth(c *fm.Context, r *http.Request) (bool, *fm.User) {
keyFunc := func(token *jwt.Token) (interface{}, error) { keyFunc := func(token *jwt.Token) (interface{}, error) {
return c.Key, nil return c.Key, nil
} }
var claims claims var claims claims
token, err := request.ParseFromRequestWithClaims(r, token, err := request.ParseFromRequestWithClaims(r,
extractor{}, extractor{},

View File

@ -17,21 +17,13 @@ import (
// downloadHandler creates an archive in one of the supported formats (zip, tar, // downloadHandler creates an archive in one of the supported formats (zip, tar,
// tar.gz or tar.bz2) and sends it to be downloaded. // tar.gz or tar.bz2) and sends it to be downloaded.
func downloadHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) { func downloadHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
query := r.URL.Query().Get("format")
// If the file isn't a directory, serve it using http.ServeFile. We display it // If the file isn't a directory, serve it using http.ServeFile. We display it
// inline if it is requested. // inline if it is requested.
if !c.File.IsDir { if !c.File.IsDir {
if r.URL.Query().Get("inline") == "true" { return downloadFileHandler(c, w, r)
w.Header().Set("Content-Disposition", "inline")
} else {
w.Header().Set("Content-Disposition", "attachment; filename=\""+c.File.Name+"\"")
}
http.ServeFile(w, r, c.File.Path)
return 0, nil
} }
query := r.URL.Query().Get("format")
files := []string{} files := []string{}
names := strings.Split(r.URL.Query().Get("files"), ",") names := strings.Split(r.URL.Query().Get("files"), ",")
@ -111,3 +103,14 @@ func downloadHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
_, err = io.Copy(w, file) _, err = io.Copy(w, file)
return 0, err return 0, err
} }
func downloadFileHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
if r.URL.Query().Get("inline") == "true" {
w.Header().Set("Content-Disposition", "inline")
} else {
w.Header().Set("Content-Disposition", "attachment; filename=\""+c.File.Name+"\"")
}
http.ServeFile(w, r, c.File.Path)
return 0, nil
}

View File

@ -160,6 +160,11 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
u.Rules = []*fm.Rule{} u.Rules = []*fm.Rule{}
} }
// If the view mode is empty, initialize with the default one.
if u.ViewMode == "" {
u.ViewMode = c.DefaultUser.ViewMode
}
// Initialize commands if not initialized. // Initialize commands if not initialized.
if u.Commands == nil { if u.Commands == nil {
u.Commands = []string{} u.Commands = []string{}

View File

@ -1 +0,0 @@
9536ea589ebfb34b4ccbf549776ff8b4c70d6dd6