@@ -230,7 +229,7 @@ export default {
if (columns === 0) columns = 1
items.style.width = `calc(${100 / columns}% - 1em)`
},
- dragEnter: function (event) {
+ dragEnter (event) {
// When the user starts dragging an item, put every
// file on the listing with 50% opacity.
let items = document.getElementsByClassName('item')
@@ -239,51 +238,85 @@ export default {
file.style.opacity = 0.5
})
},
- dragEnd: function (event) {
+ dragEnd (event) {
this.resetOpacity()
},
drop: function (event) {
event.preventDefault()
+ this.resetOpacity()
let dt = event.dataTransfer
let files = dt.files
let el = event.target
+ if (files.length <= 0) return
+
for (let i = 0; i < 5; i++) {
if (el !== null && !el.classList.contains('item')) {
el = el.parentElement
}
}
- if (files.length > 0) {
- if (el !== null && el.classList.contains('item') && el.dataset.dir === 'true') {
- this.handleFiles(files, el.querySelector('.name').innerHTML + '/')
- return
- }
-
- this.handleFiles(files, '')
- } else {
- this.resetOpacity()
+ let base = ''
+ if (el !== null && el.classList.contains('item') && el.dataset.dir === 'true') {
+ base = el.querySelector('.name').innerHTML + '/'
}
+
+ if (base !== '') {
+ api.fetch(this.$route.path + base)
+ .then(req => {
+ this.checkConflict(files, req.items, base)
+ })
+ .catch(error => { console.log(error) })
+
+ return
+ }
+
+ this.checkConflict(files, this.req.items, base)
},
- uploadInput: function (event) {
- this.handleFiles(event.currentTarget.files, '')
+ checkConflict (files, items, base) {
+ let conflict = false
+ for (let i = 0; i < files.length; i++) {
+ let res = items.findIndex(function hasConflict (element) {
+ return (element.name === this)
+ }, files[i].name)
+
+ if (res >= 0) {
+ conflict = true
+ break
+ }
+ }
+
+ if (!conflict) {
+ this.handleFiles(files, base)
+ return
+ }
+
+ this.$store.commit('showHover', {
+ prompt: 'replace',
+ confirm: (event) => {
+ event.preventDefault()
+ this.$store.commit('closeHovers')
+ this.handleFiles(files, base, true)
+ }
+ })
},
- resetOpacity: function () {
+ uploadInput (event) {
+ this.checkConflict(event.currentTarget.files, this.req.items, '')
+ },
+ resetOpacity () {
let items = document.getElementsByClassName('item')
Array.from(items).forEach(file => {
file.style.opacity = 1
})
},
- handleFiles: function (files, base) {
- this.resetOpacity()
-
+ handleFiles (files, base, overwrite = false) {
buttons.loading('upload')
let promises = []
for (let file of files) {
- promises.push(api.post(this.$route.path + base + file.name, file))
+ promises.push(api.post(this.$route.path + base + file.name, file, overwrite))
}
Promise.all(promises)
diff --git a/assets/src/components/files/ListingItem.vue b/assets/src/components/files/ListingItem.vue
index 81c7be12..eb67d095 100644
--- a/assets/src/components/files/ListingItem.vue
+++ b/assets/src/components/files/ListingItem.vue
@@ -9,6 +9,7 @@
@click="click"
@dblclick="open"
@touchstart="touchstart"
+ :data-dir="isDir"
:aria-label="name"
:aria-selected="isSelected">
diff --git a/assets/src/components/prompts/Prompts.vue b/assets/src/components/prompts/Prompts.vue
index f42d7369..781bbe16 100644
--- a/assets/src/components/prompts/Prompts.vue
+++ b/assets/src/components/prompts/Prompts.vue
@@ -11,6 +11,7 @@
+
+
+
diff --git a/assets/src/i18n/en.yaml b/assets/src/i18n/en.yaml
index 2157ad95..2326a176 100644
--- a/assets/src/i18n/en.yaml
+++ b/assets/src/i18n/en.yaml
@@ -13,6 +13,7 @@ buttons:
new: New
next: Next
ok: OK
+ replace: Replace
previous: Previous
rename: Rename
reportIssue: Report Issue
@@ -84,6 +85,10 @@ prompts:
newFileMessage: Write the name of the new file.
numberDirs: Number of directories
numberFiles: Number of files
+ replace: Replace
+ replaceMessage: >
+ One of the files you're trying to upload is conflicting because of its name.
+ Do you wish to replace the existing one?
rename: Rename
renameMessage: Insert a new name for
show: Show
diff --git a/assets/src/i18n/pt.yaml b/assets/src/i18n/pt.yaml
index 8edf4160..eb758cdd 100644
--- a/assets/src/i18n/pt.yaml
+++ b/assets/src/i18n/pt.yaml
@@ -15,6 +15,7 @@ buttons:
ok: OK
previous: Anterior
rename: Renomear
+ replace: Substituir
reportIssue: Reportar Erro
save: Guardar
search: Pesquisar
@@ -86,6 +87,10 @@ prompts:
numberFiles: Número de ficheiros
rename: Renomear
renameMessage: Insira um novo nome para
+ replace: Substituir
+ replaceMessage: >
+ Já existe um ficheiro com nome igual a um dos que está a tentar enviar. Deseja
+ substituir?
show: Mostrar
size: Tamanho
settings:
diff --git a/assets/src/store/index.js b/assets/src/store/index.js
index 6d851b91..9c68cb6a 100644
--- a/assets/src/store/index.js
+++ b/assets/src/store/index.js
@@ -20,7 +20,8 @@ const state = {
selected: [],
multiple: false,
show: null,
- showMessage: null
+ showMessage: null,
+ showConfirm: null
}
export default new Vuex.Store({
diff --git a/assets/src/store/mutations.js b/assets/src/store/mutations.js
index 481845f2..d88ac91b 100644
--- a/assets/src/store/mutations.js
+++ b/assets/src/store/mutations.js
@@ -13,6 +13,7 @@ const mutations = {
state.show = value.prompt
state.showMessage = value.message
+ state.showConfirm = value.confirm
},
showError: (state, value) => {
state.show = 'error'
diff --git a/assets/src/utils/api.js b/assets/src/utils/api.js
index 04fff2cd..3ada5927 100644
--- a/assets/src/utils/api.js
+++ b/assets/src/utils/api.js
@@ -4,9 +4,11 @@ const ssl = (window.location.protocol === 'https:')
export function removePrefix (url) {
if (url.startsWith('/files')) {
- return url.slice(6)
+ url = url.slice(6)
}
+ if (url === '') url = '/'
+ if (url[0] !== '/') url = '/' + url
return url
}
@@ -54,7 +56,7 @@ export function rm (url) {
})
}
-export function post (url, content = '') {
+export function post (url, content = '', overwrite = false) {
url = removePrefix(url)
return new Promise((resolve, reject) => {
@@ -62,15 +64,23 @@ export function post (url, content = '') {
request.open('POST', `${store.state.baseURL}/api/resource${url}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
+ if (overwrite) {
+ request.setRequestHeader('Action', `override`)
+ }
+
request.onload = () => {
if (request.status === 200) {
resolve(request.responseText)
+ } else if (request.status === 409) {
+ reject(request.status)
} else {
reject(request.responseText)
}
}
- request.onerror = (error) => reject(error)
+ request.onerror = (error) => {
+ reject(error)
+ }
request.send(content)
})
}
diff --git a/resource.go b/resource.go
index 6eff1db6..792c5687 100644
--- a/resource.go
+++ b/resource.go
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io"
+ "io/ioutil"
"net/http"
"net/url"
"os"
@@ -155,6 +156,12 @@ func resourcePostPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Re
return http.StatusForbidden, nil
}
+ // Discard any invalid upload before returning to avoid connection
+ // reset error.
+ defer func() {
+ io.Copy(ioutil.Discard, r.Body)
+ }()
+
// Checks if the current request is for a directory and not a file.
if strings.HasSuffix(r.URL.Path, "/") {
// If the method is PUT, we return 405 Method not Allowed, because
@@ -171,7 +178,7 @@ func resourcePostPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Re
// If using POST method, we are trying to create a new file so it is not
// desirable to override an already existent file. Thus, we check
// if the file already exists. If so, we just return a 409 Conflict.
- if r.Method == http.MethodPost {
+ if r.Method == http.MethodPost && r.Header.Get("Action") != "override" {
if _, err := c.User.FileSystem.Stat(r.URL.Path); err == nil {
return http.StatusConflict, errors.New("There is already a file on that path")
}