Support replace feature; close #188; multiple bug fixes on upload
							parent
							
								
									a117f83256
								
							
						
					
					
						commit
						6ee846ef0e
					
				|  | @ -8,7 +8,6 @@ | |||
|   </div> | ||||
|   <div v-else id="listing" | ||||
|     :class="req.display" | ||||
|     @drop="drop" | ||||
|     @dragenter="dragEnter" | ||||
|     @dragend="dragEnd"> | ||||
|     <div> | ||||
|  | @ -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) | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
|   @click="click" | ||||
|   @dblclick="open" | ||||
|   @touchstart="touchstart" | ||||
|   :data-dir="isDir" | ||||
|   :aria-label="name" | ||||
|   :aria-selected="isSelected"> | ||||
|     <div> | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ | |||
|     <copy v-else-if="showCopy"></copy> | ||||
|     <error v-else-if="showError"></error> | ||||
|     <success v-else-if="showSuccess"></success> | ||||
|     <replace v-else-if="showReplace"></replace> | ||||
| 
 | ||||
|     <template v-for="plugin in plugins"> | ||||
|       <form class="prompt" | ||||
|  | @ -54,6 +55,7 @@ import Error from './Error' | |||
| import Success from './Success' | ||||
| import NewFile from './NewFile' | ||||
| import NewDir from './NewDir' | ||||
| import Replace from './Replace' | ||||
| import { mapState } from 'vuex' | ||||
| import buttons from '@/utils/buttons' | ||||
| import api from '@/utils/api' | ||||
|  | @ -71,7 +73,8 @@ export default { | |||
|     Copy, | ||||
|     NewFile, | ||||
|     NewDir, | ||||
|     Help | ||||
|     Help, | ||||
|     Replace | ||||
|   }, | ||||
|   data: function () { | ||||
|     return { | ||||
|  | @ -96,6 +99,7 @@ export default { | |||
|     showNewFile: function () { return this.show === 'newFile' }, | ||||
|     showNewDir: function () { return this.show === 'newDir' }, | ||||
|     showDownload: function () { return this.show === 'download' }, | ||||
|     showReplace: function () { return this.show === 'replace' }, | ||||
|     showOverlay: function () { | ||||
|       return (this.show !== null && this.show !== 'search' && this.show !== 'more') | ||||
|     } | ||||
|  |  | |||
|  | @ -0,0 +1,26 @@ | |||
| <template> | ||||
|   <div class="prompt"> | ||||
|     <h3>{{ $t('prompts.replace') }}</h3> | ||||
|     <p>{{ $t('prompts.replaceMessage') }}</p> | ||||
| 
 | ||||
|     <div> | ||||
|       <button class="ok" | ||||
|         @click="showConfirm" | ||||
|         :aria-label="$t('buttons.replace')" | ||||
|         :title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button> | ||||
|       <button class="cancel" | ||||
|         @click="$store.commit('closeHovers')" | ||||
|         :aria-label="$t('buttons.cancel')" | ||||
|         :title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import { mapState } from 'vuex' | ||||
| 
 | ||||
| export default { | ||||
|   name: 'replace', | ||||
|   computed: mapState(['showConfirm']) | ||||
| } | ||||
| </script> | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -20,7 +20,8 @@ const state = { | |||
|   selected: [], | ||||
|   multiple: false, | ||||
|   show: null, | ||||
|   showMessage: null | ||||
|   showMessage: null, | ||||
|   showConfirm: null | ||||
| } | ||||
| 
 | ||||
| export default new Vuex.Store({ | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ const mutations = { | |||
| 
 | ||||
|     state.show = value.prompt | ||||
|     state.showMessage = value.message | ||||
|     state.showConfirm = value.confirm | ||||
|   }, | ||||
|   showError: (state, value) => { | ||||
|     state.show = 'error' | ||||
|  |  | |||
|  | @ -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) | ||||
|   }) | ||||
| } | ||||
|  |  | |||
|  | @ -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") | ||||
| 		} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Henrique Dias
						Henrique Dias