diff --git a/.gitignore b/.gitignore
index 09552ad9..df5f555f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 .DS_Store
 node_modules/
-dist/
+dist_dev/
 npm-debug.log*
 yarn-debug.log*
 yarn-error.log*
diff --git a/_assets/_old/js/common_old.js b/_assets/_old/js/common_old.js
index 9970fb8d..3c64bfad 100644
--- a/_assets/_old/js/common_old.js
+++ b/_assets/_old/js/common_old.js
@@ -9,21 +9,7 @@ var selectedItems = []
 var overlay
 var clickOverlay
 
-// Removes an element, if exists, from an array
-Array.prototype.removeElement = function (element) {
-  var i = this.indexOf(element)
-  if (i !== -1) {
-    this.splice(i, 1)
-  }
-}
 
-// Replaces an element inside an array by another
-Array.prototype.replaceElement = function (oldElement, newElement) {
-  var i = this.indexOf(oldElement)
-  if (i !== -1) {
-    this[i] = newElement
-  }
-}
 
 // Sends a costum event to itself
 Document.prototype.sendCostumEvent = function (text) {
@@ -37,34 +23,6 @@ Document.prototype.getCookie = function (name) {
 }
 
 
-function getCSSRule (rules) {
-  for (let i = 0; i < rules.length; i++) {
-    rules[i] = rules[i].toLowerCase()
-  }
-
-  let result = null
-  let find = Array.prototype.find
-
-  find.call(document.styleSheets, styleSheet => {
-    result = find.call(styleSheet.cssRules, cssRule => {
-      let found = false
-
-      if (cssRule instanceof CSSStyleRule) {
-        for (let i = 0; i < rules.length; i++) {
-          if (cssRule.selectorText.toLowerCase() === rules[i]) {
-            found = true
-          }
-        }
-      }
-
-      return found
-    })
-
-    return result != null
-  })
-
-  return result
-}
 
 /* * * * * * * * * * * * * * * *
  *                             *
@@ -127,106 +85,7 @@ buttons.setDone = function (name, success = true) {
  *            WEBDAV           *
  *                             *
  * * * * * * * * * * * * * * * */
-var webdav = {}
 
-webdav.convertURL = function (url) {
-  return window.location.origin + url.replace(data.baseURL + '/', data.webdavURL + '/')
-}
-
-webdav.move = function (oldLink, newLink) {
-  return new Promise((resolve, reject) => {
-    let request = new window.XMLHttpRequest()
-    let destination = newLink.replace(data.baseURL + '/', data.webdavURL + '/')
-
-    destination = window.location.origin + destination.substring(data.baseURL.length)
-
-    request.open('MOVE', webdav.convertURL(oldLink), true)
-    request.setRequestHeader('Destination', destination)
-    request.onload = () => {
-      if (request.status === 201 || request.status === 204) {
-        resolve()
-      } else {
-        reject(request.statusText)
-      }
-    }
-    request.onerror = () => reject(request.statusText)
-    request.send()
-  })
-}
-
-webdav.put = function (link, body, headers = {}) {
-  return new Promise((resolve, reject) => {
-    let request = new window.XMLHttpRequest()
-    request.open('PUT', webdav.convertURL(link), true)
-
-    for (let key in headers) {
-      request.setRequestHeader(key, headers[key])
-    }
-
-    request.onload = () => {
-      if (request.status == 201) {
-        resolve()
-      } else {
-        reject(request.statusText)
-      }
-    }
-    request.onerror = () => reject(request.statusText)
-    request.send(body)
-  })
-}
-
-webdav.propfind = function (link, body, headers = {}) {
-  return new Promise((resolve, reject) => {
-    let request = new window.XMLHttpRequest()
-    request.open('PROPFIND', webdav.convertURL(link), true)
-
-    for (let key in headers) {
-      request.setRequestHeader(key, headers[key])
-    }
-
-    request.onload = () => {
-      if (request.status < 300) {
-        resolve(request.responseText)
-      } else {
-        reject(request.statusText)
-      }
-    }
-    request.onerror = () => reject(request.statusText)
-    request.send(body)
-  })
-}
-
-webdav.delete = function (link) {
-  return new Promise((resolve, reject) => {
-    let request = new window.XMLHttpRequest()
-    request.open('DELETE', webdav.convertURL(link), true)
-    request.onload = () => {
-      if (request.status === 204) {
-        resolve()
-      } else {
-        reject(request.statusText)
-      }
-    }
-    request.onerror = () => reject(request.statusText)
-    request.send()
-  })
-}
-
-webdav.new = function (link) {
-  return new Promise((resolve, reject) => {
-    let request = new window.XMLHttpRequest()
-    request.open((link.endsWith('/') ? 'MKCOL' : 'PUT'), webdav.convertURL(link), true)
-    request.onload = () => {
-      if (request.status === 201) {
-        resolve()
-      } else {
-        reject(request.statusText)
-      }
-    }
-    request.onerror = () => reject(request.statusText)
-    request.send()
-  })
-}
 
 /* * * * * * * * * * * * * * * *
  *                             *
diff --git a/_assets/_old/js/listing.js b/_assets/_old/js/listing.js
index 19906ba0..6b233d90 100644
--- a/_assets/_old/js/listing.js
+++ b/_assets/_old/js/listing.js
@@ -21,80 +21,6 @@ var listing = {
       }
     }
   },
-  itemDragStart: function (event) {
-    let el = event.target
-
-    for (let i = 0; i < 5; i++) {
-      if (!el.classList.contains('item')) {
-        el = el.parentElement
-      }
-    }
-
-    event.dataTransfer.setData('id', el.id)
-    event.dataTransfer.setData('name', el.querySelector('.name').innerHTML)
-  },
-  itemDragOver: function (event) {
-    event.preventDefault()
-    let el = event.target
-
-    for (let i = 0; i < 5; i++) {
-      if (!el.classList.contains('item')) {
-        el = el.parentElement
-      }
-    }
-
-    el.style.opacity = 1
-  },
-  itemDrop: function (e) {
-    e.preventDefault()
-
-    let el = e.target,
-      id = e.dataTransfer.getData('id'),
-      name = e.dataTransfer.getData('name')
-
-    if (id == '' || name == '') return
-
-    for (let i = 0; i < 5; i++) {
-      if (!el.classList.contains('item')) {
-        el = el.parentElement
-      }
-    }
-
-    if (el.id === id) return
-
-    let oldLink = document.getElementById(id).dataset.url,
-      newLink = el.dataset.url + name
-
-    webdav.move(oldLink, newLink)
-      .then(() => listing.reload())
-      .catch(e => console.log(e))
-  },
-  documentDrop: function (event) {
-    event.preventDefault()
-    let dt = event.dataTransfer,
-      files = dt.files,
-      el = event.target,
-      items = document.getElementsByClassName('item')
-
-    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') {
-        listing.handleFiles(files, el.querySelector('.name').innerHTML + '/')
-        return
-      }
-
-      listing.handleFiles(files, '')
-    } else {
-      Array.from(items).forEach(file => {
-        file.style.opacity = 1
-      })
-    }
-  },
   rename: function (event) {
     if (!selectedItems.length || selectedItems.length > 1) {
       return false
@@ -150,67 +76,9 @@ var listing = {
     return false
   },
   handleFiles: function (files, base) {
-    buttons.setLoading('upload')
-
-    let promises = []
-
-    for (let file of files) {
-      promises.push(webdav.put(window.location.pathname + base + file.name, file))
-    }
-
-    Promise.all(promises)
-      .then(() => {
-        listing.reload()
-        buttons.setDone('upload')
-      })
-      .catch(e => {
-        console.log(e)
-        buttons.setDone('upload', false)
-      })
-
-    return false
   }
 }
 
-listing.unselectAll = function () {
-  let items = document.getElementsByClassName('item')
-  Array.from(items).forEach(link => {
-    link.setAttribute('aria-selected', false)
-  })
-
-  selectedItems = []
-
-  listing.handleSelectionChange()
-  return false
-}
-
-listing.handleSelectionChange = function (event) {
-  listing.redefineDownloadURLs()
-
-  let selectedNumber = selectedItems.length,
-    fileAction = document.getElementById('file-only')
-
-  if (selectedNumber) {
-    fileAction.classList.remove('disabled')
-
-    if (selectedNumber > 1) {
-      buttons.rename.classList.add('disabled')
-      buttons.info.classList.add('disabled')
-    }
-
-    if (selectedNumber == 1) {
-      buttons.info.classList.remove('disabled')
-      buttons.rename.classList.remove('disabled')
-    }
-
-    return false
-  }
-
-  buttons.info.classList.remove('disabled')
-  fileAction.classList.add('disabled')
-  return false
-}
-
 listing.redefineDownloadURLs = function () {
   let files = ''
 
@@ -228,28 +96,6 @@ listing.redefineDownloadURLs = function () {
   })
 }
 
-listing.openItem = function (event) {
-  window.location = event.currentTarget.dataset.url
-}
-
-listing.selectItem = function (event) {
-  let el = event.currentTarget
-
-  if (selectedItems.length != 0) event.preventDefault()
-  if (selectedItems.indexOf(el.id) == -1) {
-    if (!event.ctrlKey && !listing.selectMultiple) listing.unselectAll()
-
-    el.setAttribute('aria-selected', true)
-    selectedItems.push(el.id)
-  } else {
-    el.setAttribute('aria-selected', false)
-    selectedItems.removeElement(el.id)
-  }
-
-  listing.handleSelectionChange()
-  return false
-}
-
 listing.newFileButton = function (event) {
   event.preventDefault()
 
@@ -284,13 +130,6 @@ listing.newFilePrompt = function (event) {
   return false
 }
 
-listing.updateColumns = function (event) {
-  let columns = Math.floor(document.getElementById('listing').offsetWidth / 300),
-    items = getCSSRule(['#listing.mosaic .item', '.mosaic#listing .item'])
-
-  items.style.width = `calc(${100/columns}% - 1em)`
-}
-
 listing.addDoubleTapEvent = function () {
   let items = document.getElementsByClassName('item'),
     touches = {
@@ -343,10 +182,6 @@ window.addEventListener('keydown', (event) => {
   }
 })
 
-window.addEventListener('resize', () => {
-  listing.updateColumns()
-})
-
 listing.selectMoveFolder = function (event) {
   if (event.target.getAttribute('aria-selected') === 'true') {
     event.target.setAttribute('aria-selected', false)
@@ -542,25 +377,6 @@ document.addEventListener('DOMContentLoaded', event => {
       document.getElementById('upload-input').click()
     })
 
-    buttons.new.addEventListener('click', listing.newFileButton)
-
-    // Drag and Drop
-    document.addEventListener('dragover', function (event) {
-      event.preventDefault()
-    }, false)
-
-    document.addEventListener('dragenter', (event) => {
-      Array.from(items).forEach(file => {
-        file.style.opacity = 0.5
-      })
-    }, false)
-
-    document.addEventListener('dragend', (event) => {
-      Array.from(items).forEach(file => {
-        file.style.opacity = 1
-      })
-    }, false)
-
-    document.addEventListener('drop', listing.documentDrop, false)
+    buttons.new.addEventListener('click', listing.newFileButton)    
   }
 })
diff --git a/_assets/_old/templates/listing.tmpl b/_assets/_old/templates/listing.tmpl
deleted file mode 100644
index 26cc5313..00000000
--- a/_assets/_old/templates/listing.tmpl
+++ /dev/null
@@ -1,17 +0,0 @@
-{{ define "item" }}
-<div ondragstart="listing.itemDragStart(event)" 
-    {{ if .IsDir}}ondragover="listing.itemDragOver(event)" ondrop="listing.itemDrop(event)"{{ end }} 
-
-    onclick="listing.selectItem(event)"
-    ondblclick="listing.openItem(event)"
-    data-dir="{{ .IsDir }}" 
-    data-url="{{ .URL }}">
-    
-    <div>
-
-        <p class="modified">
-        <time datetime="{{.HumanModTime "2006-01-02T15:04:05Z"}}">{{.HumanModTime "2 Jan 2006 03:04 PM"}}</time>
-        </p>
-    </div>
-</div>
-{{ end }}
diff --git a/_assets/dist_dev/templates/index.html b/_assets/dist_dev/templates/index.html
index 62e97061..66d27fa3 100644
--- a/_assets/dist_dev/templates/index.html
+++ b/_assets/dist_dev/templates/index.html
@@ -21,9 +21,9 @@
     <meta name="msapplication-TileImage" content="{{ .BaseURL }}/_/img/icons/msapplication-icon-144x144.png">
     <meta name="msapplication-TileColor" content="#000000">
     
-    <link rel="preload" href="{{ .BaseURL }}/_/js/app.3746baae0d5525e95c4e.js" as="script">
-    <link rel="preload" href="{{ .BaseURL }}/_/js/vendor.89d687258dcaf46efc29.js" as="script">
-    <link rel="preload" href="{{ .BaseURL }}/_/js/manifest.83ada933c74b4b19b61c.js" as="script">
+    <link rel="preload" href="{{ .BaseURL }}/_/js/vendor.429043e9736e4450f715.js" as="script">
+    <link rel="preload" href="{{ .BaseURL }}/_/js/app.fcde30b3eb6e988cd456.js" as="script">
+    <link rel="preload" href="{{ .BaseURL }}/_/js/manifest.f2713ae24188290498c4.js" as="script">
   
     
     {{- if ne .User.StyleSheet "" -}}
@@ -32,13 +32,12 @@
   </head>
   <body>
     <script>
-    var data = {
+    var info = {
         user: JSON.parse('{{ Marshal .User }}'),
+        page: JSON.parse('{{ Marshal . }}'),
         webdavURL: "{{ .WebDavURL }}",
         baseURL: "{{.BaseURL}}"
     }
-
-    var page = JSON.parse('{{ Marshal . }}')
     </script>
     <div id="app">
       <!-- TODO: loading -->
@@ -61,5 +60,5 @@ self.addEventListener('activate', () => {
     }
   });
 });</script>
-  <script type="text/javascript" src="{{ .BaseURL }}/_/js/manifest.83ada933c74b4b19b61c.js"></script><script type="text/javascript" src="{{ .BaseURL }}/_/js/vendor.89d687258dcaf46efc29.js"></script><script type="text/javascript" src="{{ .BaseURL }}/_/js/app.3746baae0d5525e95c4e.js"></script></body>
+  <script type="text/javascript" src="{{ .BaseURL }}/_/js/manifest.f2713ae24188290498c4.js"></script><script type="text/javascript" src="{{ .BaseURL }}/_/js/vendor.429043e9736e4450f715.js"></script><script type="text/javascript" src="{{ .BaseURL }}/_/js/app.fcde30b3eb6e988cd456.js"></script></body>
 </html>
diff --git a/_assets/index.html b/_assets/index.html
index eb878d99..4160b27e 100644
--- a/_assets/index.html
+++ b/_assets/index.html
@@ -32,13 +32,12 @@
   </head>
   <body>
     <script>
-    var data = {
+    var info = {
         user: JSON.parse('{{ Marshal .User }}'),
+        page: JSON.parse('{{ Marshal . }}'),
         webdavURL: "{{ .WebDavURL }}",
         baseURL: "{{.BaseURL}}"
     }
-
-    var page = JSON.parse('{{ Marshal . }}')
     </script>
     <div id="app">
       <!-- TODO: loading -->
diff --git a/_assets/src/App.vue b/_assets/src/App.vue
index 5166efaf..d227f1fd 100644
--- a/_assets/src/App.vue
+++ b/_assets/src/App.vue
@@ -11,7 +11,7 @@
         <div id="click-overlay"></div>
     </header>
     <nav id="sidebar">
-      <a class="action" :href="baseURL()">
+      <a class="action" :href="baseURL + '/'">
         <i class="material-icons">folder</i>
         <span>My Files</span>
       </a>
@@ -21,10 +21,10 @@
       </div>
     </nav>
     <main>
-      <listing v-if="Kind == 'listing'"></listing> 
+      <listing v-if="page.kind == 'listing'"></listing> 
     </main>
 
-    <preview v-if="Kind == 'preview'"></preview> 
+    <preview v-if="page.kind == 'preview'"></preview> 
 
     <div class="overlay"></div>
     <!-- TODO: show on listing and allowedit -->
@@ -50,17 +50,24 @@
 import Search from './components/Search'
 import Preview from './components/Preview'
 import Listing from './components/Listing'
+import css from './css.js'
+
+function updateColumnSizes () {
+  let columns = Math.floor(document.querySelector('main').offsetWidth / 300)
+  let items = css(['#listing.mosaic .item', '.mosaic#listing .item'])
+
+  items.style.width = `calc(${100 / columns}% - 1em)`
+}
 
 export default {
   name: 'app',
   components: { Search, Preview, Listing },
-  data: function () {
-    return window.page
+  mounted: function () {
+    updateColumnSizes()
+    window.addEventListener('resize', updateColumnSizes)
   },
-  methods: {
-    baseURL: function () {
-      return window.data.baseURL + '/'
-    }
+  data: function () {
+    return window.info
   }
 }
 </script>
diff --git a/_assets/src/array.js b/_assets/src/array.js
new file mode 100644
index 00000000..b06b5f7a
--- /dev/null
+++ b/_assets/src/array.js
@@ -0,0 +1,24 @@
+// Removes an element, if exists, from an array
+function removeElement (array, element) {
+  var i = array.indexOf(element)
+  if (i !== -1) {
+    array.splice(i, 1)
+  }
+
+  return array
+}
+
+// Replaces an element inside an array by another
+function replaceElement (array, oldElement, newElement) {
+  var i = array.indexOf(oldElement)
+  if (i !== -1) {
+    array[i] = newElement
+  }
+
+  return array
+}
+
+export default {
+  remove: removeElement,
+  replace: replaceElement
+}
diff --git a/_assets/src/components/Listing.vue b/_assets/src/components/Listing.vue
index c86a93ee..af9995bb 100644
--- a/_assets/src/components/Listing.vue
+++ b/_assets/src/components/Listing.vue
@@ -1,16 +1,20 @@
 <template>
-    <div id="listing" :class="Data.Display">
+    <div id="listing" 
+      :class="data.display" 
+      @drop="drop"
+      @dragenter="dragEnter" 
+      @dragend="dragEnd">
         <div>
             <div class="item header">
                 <div></div>
                 <div>
-                    <p v-bind:class="{ active: Data.Sort === 'name' }" class="name"><span>Name</span>
-                        <a v-if="Data.Sort === 'name' && Data.Order != 'asc'" href="?sort=name&order=asc"><i class="material-icons">arrow_upward</i></a>
+                    <p v-bind:class="{ active: data.sort === 'name' }" class="name"><span>Name</span>
+                        <a v-if="data.sort === 'name' && data.order != 'asc'" href="?sort=name&order=asc"><i class="material-icons">arrow_upward</i></a>
                         <a v-else href="?sort=name&order=desc"><i class="material-icons">arrow_downward</i></a>
                     </p>
 
-                    <p v-bind:class="{ active: Data.Sort === 'size' }" class="size"><span>Size</span>
-                        <a v-if="Data.Sort === 'size' && Data.Order != 'asc'" href="?sort=size&order=asc"><i class="material-icons">arrow_upward</i></a>
+                    <p v-bind:class="{ active: data.sort === 'size' }" class="size"><span>Size</span>
+                        <a v-if="data.sort === 'size' && data.order != 'asc'" href="?sort=size&order=asc"><i class="material-icons">arrow_upward</i></a>
                         <a v-else href="?sort=size&order=desc"><i class="material-icons">arrow_downward</i></a>
                     </p>
 
@@ -19,37 +23,39 @@
             </div>
         </div>
 
-        <h2 v-if="(Data.NumDirs + Data.NumFiles) == 0" class="message">It feels lonely here :'(</h2>
+        <h2 v-if="(data.numDirs + data.numFiles) == 0" class="message">It feels lonely here :'(</h2>
 
-        <h2 v-if="Data.NumDirs !== 0">Folders</h2>
-        <div v-if="Data.NumDirs !== 0">
+        <h2 v-if="data.numDirs > 0">Folders</h2>
+        <div v-if="data.numDirs > 0">
           <item
-            v-for="(item, index) in Data.Items"
-            v-if="item.IsDir"
-            :key="base64(item.Name)"
-            :id="base64(item.Name)"
-            v-bind:name="item.Name"
-            v-bind:isDir="item.IsDir"
-            v-bind:url="item.URL"
-            v-bind:modified="item.ModTime"  
-            v-bind:type="item.Type"
-            v-bind:size="item.Size">
+            v-for="(item, index) in data.items"
+            v-if="item.isDir"
+            :key="base64(item.name)"
+            :id="base64(item.name)"
+            v-bind:selected="selected"
+            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="Data.NumItems !== 0">Files</h2>
-        <div v-if="Data.NumItems !== 0">
+        <h2 v-if="data.numFiles > 0">Files</h2>
+        <div v-if="data.numFiles > 0">
           <item
-            v-for="(item, index) in Data.Items"
-            v-if="!item.IsDir"
-            :key="base64(item.Name)"
-            :id="base64(item.Name)"
-            v-bind:name="item.Name"
-            v-bind:isDir="item.IsDir"
-            v-bind:modified="item.ModTime"  
-            v-bind:url="item.URL"
-            v-bind:type="item.Type"
-            v-bind:size="item.Size">
+            v-for="(item, index) in data.items"
+            v-if="!item.isDir"
+            :key="base64(item.name)"
+            :id="base64(item.name)"
+            v-bind:selected="selected"
+            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>
 
@@ -61,16 +67,119 @@
 
 <script>
 import Item from './ListingItem'
+import webdav from '../webdav.js'
+import page from '../page.js'
 
 export default {
   name: 'preview',
-  components: { Item },
   data: function () {
-    return window.page
+    return {
+      data: window.info.page.data,
+      selected: [],
+      multiple: false
+    }
+  },
+  components: { Item },
+  mounted: function () {
+    document.addEventListener('dragover', function (event) {
+      event.preventDefault()
+    }, false)
+
+    document.addEventListener('drop', this.drop, false)
+  },
+  beforeUpdate: function () {
+    /*
+      listing.redefineDownloadURLs()
+
+  let selectedNumber = selectedItems.length,
+    fileAction = document.getElementById('file-only')
+
+  if (selectedNumber) {
+    fileAction.classList.remove('disabled')
+
+    if (selectedNumber > 1) {
+      buttons.rename.classList.add('disabled')
+      buttons.info.classList.add('disabled')
+    }
+
+    if (selectedNumber == 1) {
+      buttons.info.classList.remove('disabled')
+      buttons.rename.classList.remove('disabled')
+    }
+
+    return false
+  }
+
+  buttons.info.classList.remove('disabled')
+  fileAction.classList.add('disabled')
+  */
+    console.log('before upding')
   },
   methods: {
     base64: function (name) {
       return window.btoa(name)
+    },
+    dragEnter: function (event) {
+      let items = document.getElementsByClassName('item')
+
+      Array.from(items).forEach(file => {
+        file.style.opacity = 0.5
+      })
+    },
+    dragEnd: function (event) {
+      let items = document.getElementsByClassName('item')
+
+      Array.from(items).forEach(file => {
+        file.style.opacity = 1
+      })
+    },
+    drop: function (event) {
+      event.preventDefault()
+
+      let dt = event.dataTransfer
+      let files = dt.files
+      let el = event.target
+
+      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 {
+        let items = document.getElementsByClassName('item')
+
+        Array.from(items).forEach(file => {
+          file.style.opacity = 1
+        })
+      }
+    },
+    handleFiles: function (files, base) {
+      // buttons.setLoading('upload')
+      let promises = []
+
+      for (let file of files) {
+        promises.push(webdav.put(window.location.pathname + base + file.name, file))
+      }
+
+      Promise.all(promises)
+      .then(() => {
+        page.reload()
+        // buttons.setDone('upload')
+      })
+      .catch(e => {
+        console.log(e)
+        // buttons.setDone('upload', false)
+      })
+
+      return false
     }
   }
 }
diff --git a/_assets/src/components/ListingItem.vue b/_assets/src/components/ListingItem.vue
index d56315c9..fc40f990 100644
--- a/_assets/src/components/ListingItem.vue
+++ b/_assets/src/components/ListingItem.vue
@@ -1,9 +1,12 @@
 <template>
     <div class="item" 
         draggable="true"
-        :id="base64()"
-        :data-dir="isDir" 
-        :data-url="url" >
+        @dragstart="dragStart"
+        @dragover="dragOver"
+        @drop="drop"
+        @click="click"
+        @dblclick="open"
+        :id="base64()">
         <div>
             <i class="material-icons">{{ icon() }}</i>
         </div>
@@ -24,10 +27,13 @@
 <script>
 import filesize from 'filesize'
 import moment from 'moment'
+import webdav from '../webdav.js'
+import page from '../page.js'
+import array from '../array.js'
 
 export default {
   name: 'item',
-  props: ['name', 'isDir', 'url', 'type', 'size', 'modified'],
+  props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'selected'],
   methods: {
     icon: function () {
       if (this.isDir) return 'folder'
@@ -44,6 +50,76 @@ export default {
     },
     base64: function () {
       return window.btoa(this.name)
+    },
+    dragStart: function (event) {
+      let el = event.target
+
+      for (let i = 0; i < 5; i++) {
+        if (!el.classList.contains('item')) {
+          el = el.parentElement
+        }
+      }
+
+      event.dataTransfer.setData('name', el.querySelector('.name').innerHTML)
+      event.dataTransfer.setData('obj-url', this.url)
+    },
+    dragOver: function (event) {
+      if (!this.isDir) return
+
+      event.preventDefault()
+      let el = event.target
+
+      for (let i = 0; i < 5; i++) {
+        if (!el.classList.contains('item')) {
+          el = el.parentElement
+        }
+      }
+
+      el.style.opacity = 1
+    },
+    drop: function (event) {
+      if (!this.isDir) return
+      event.preventDefault()
+
+      let url = event.dataTransfer.getData('obj-url')
+      let name = event.dataTransfer.getData('name')
+
+      if (name === '' || url === '' || url === this.url) return
+
+      webdav.move(url, this.url + name)
+        .then(() => page.reload())
+        .catch(error => console.log(error))
+    },
+    unselectAll: function () {
+      let items = document.getElementsByClassName('item')
+      Array.from(items).forEach(link => {
+        link.setAttribute('aria-selected', false)
+      })
+
+      this.selected.length = 0
+
+      // listing.handleSelectionChange()
+      return false
+    },
+    click: function (event) {
+      let el = event.currentTarget
+
+      if (this.selected.length !== 0) event.preventDefault()
+      if (this.selected.indexOf(el.id) === -1) {
+        if (!event.ctrlKey && !this.multiple) this.unselectAll()
+
+        el.setAttribute('aria-selected', true)
+        this.selected.push(el.id)
+      } else {
+        el.setAttribute('aria-selected', false)
+        this.selected = array.remove(this.selected, el.id)
+      }
+
+      // this.handleSelectionChange()
+      return false
+    },
+    open: function (event) {
+      page.open(this.url)
     }
   }
 }
diff --git a/_assets/src/components/Preview.vue b/_assets/src/components/Preview.vue
index 2e77afb0..8f61374e 100644
--- a/_assets/src/components/Preview.vue
+++ b/_assets/src/components/Preview.vue
@@ -1,23 +1,23 @@
 <template>
     <div id="previewer">
         <div class="bar">
-            <button class="action" aria-label="Close Preview" id="close">
+            <button @click="back" class="action" aria-label="Close Preview" id="close">
                 <i class="material-icons">close</i>
             </button>
             <!-- TODO: add more buttons -->
         </div>
 
         <div class="preview">
-            <img v-if="Data.Type == 'image'" :src="raw()">
-            <audio v-else-if="Data.Type == 'audio'" :src="raw()" controls></audio>
-            <video v-else-if="Data.Type == 'video'" :src="raw()" controls>
+            <img v-if="type == 'image'" :src="raw()">
+            <audio v-else-if="type == 'audio'" :src="raw()" controls></audio>
+            <video v-else-if="type == 'video'" :src="raw()" controls>
                 Sorry, your browser doesn't support embedded videos,
                 but don't worry, you can <a href="?download=true">download it</a>
                 and watch it with your favorite video player!
             </video>
-            <object v-else-if="Data.Extension == '.pdf'" class="pdf" :data="raw()"></object>
-            <a v-else-if="Data.Type == 'blob'" href="?download=true"><h2 class="message">Download <i class="material-icons">file_download</i></h2></a>
-            <pre v-else >{{ Data.Content }}</pre>
+            <object v-else-if="extension == '.pdf'" class="pdf" :data="raw()"></object>
+            <a v-else-if="type == 'blob'" href="?download=true"><h2 class="message">Download <i class="material-icons">file_download</i></h2></a>
+            <pre v-else >{{ content }}</pre>
         </div>
     </div>
 </template>
@@ -26,11 +26,14 @@
 export default {
   name: 'preview',
   data: function () {
-    return window.page
+    return window.info.page.data
   },
   methods: {
     raw: function () {
-      return this.Data.URL + '?raw=true'
+      return this.url + '?raw=true'
+    },
+    back: function (event) {
+      window.history.back()
     }
   }
 }
diff --git a/_assets/src/components/Search.vue b/_assets/src/components/Search.vue
index aa66b6ad..ba349939 100644
--- a/_assets/src/components/Search.vue
+++ b/_assets/src/components/Search.vue
@@ -16,7 +16,6 @@
 </template>
 
 <script>
-
 // Remove the last directory of an url
 var removeLastDirectoryPartOf = function (url) {
   var arr = url.split('/')
@@ -26,7 +25,7 @@ var removeLastDirectoryPartOf = function (url) {
   return (arr.join('/'))
 }
 
-var data = window.data
+var user = window.info.user
 var ssl = window.ssl
 
 export default {
@@ -48,8 +47,8 @@ export default {
   },
   methods: {
     reset: function () {
-      if (data.user.AllowCommands && data.user.Commands.length > 0) {
-        this.box.innerHTML = `Search or use one of your supported commands: ${data.user.Commands.join(', ')}.`
+      if (user.allowCommands && user.commands.length > 0) {
+        this.box.innerHTML = `Search or use one of your supported commands: ${user.commands.join(', ')}.`
       } else {
         this.box.innerHTML = 'Type and press enter to search.'
       }
@@ -58,8 +57,8 @@ export default {
       let value = this.input.value
       let pieces = value.split(' ')
 
-      for (let i = 0; i < data.user.Commands.length; i++) {
-        if (pieces[0] === data.user.Commands[0]) {
+      for (let i = 0; i < user.commands.length; i++) {
+        if (pieces[0] === user.commands[0]) {
           return true
         }
       }
@@ -78,7 +77,7 @@ export default {
         return
       }
 
-      if (!this.supported() || !data.user.AllowCommands) {
+      if (!this.supported() || !user.allowCommands) {
         this.box.innerHTML = 'Press enter to search.'
       } else {
         this.box.innerHTML = 'Press enter to execute.'
@@ -96,7 +95,7 @@ export default {
 
       let protocol = ssl ? 'wss:' : 'ws:'
 
-      if (this.supported() && data.user.AllowCommands) {
+      if (this.supported() && user.allowCommands) {
         let conn = new window.WebSocket(`${protocol}//${url}?command=true`)
 
         conn.onopen = () => {
diff --git a/_assets/src/css.js b/_assets/src/css.js
new file mode 100644
index 00000000..15ab99fe
--- /dev/null
+++ b/_assets/src/css.js
@@ -0,0 +1,28 @@
+export default function getRule (rules) {
+  for (let i = 0; i < rules.length; i++) {
+    rules[i] = rules[i].toLowerCase()
+  }
+
+  let result = null
+  let find = Array.prototype.find
+
+  find.call(document.styleSheets, styleSheet => {
+    result = find.call(styleSheet.cssRules, cssRule => {
+      let found = false
+
+      if (cssRule instanceof window.CSSStyleRule) {
+        for (let i = 0; i < rules.length; i++) {
+          if (cssRule.selectorText.toLowerCase() === rules[i]) {
+            found = true
+          }
+        }
+      }
+
+      return found
+    })
+
+    return result != null
+  })
+
+  return result
+}
diff --git a/_assets/src/main.js b/_assets/src/main.js
index bf8d54d6..fc6d93e6 100644
--- a/_assets/src/main.js
+++ b/_assets/src/main.js
@@ -2,12 +2,40 @@
 // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
 import Vue from 'vue'
 import App from './App'
-
-window.data = (window.data || window.alert('Something is wrong, please refresh!'))
-window.ssl = (window.location.protocol === 'https:')
+// simport page from './page.js'
 
 Vue.config.productionTip = false
 
+window.info = (window.info || window.alert('Something is wrong, please refresh!'))
+window.ssl = (window.location.protocol === 'https:')
+
+// TODO: keep this here?
+document.title = window.info.page.name
+
+// TODO: keep this here?
+window.addEventListener('popstate', (event) => {
+  event.preventDefault()
+  event.stopPropagation()
+
+  window.info.page.kind = ''
+
+  let request = new window.XMLHttpRequest()
+  request.open('GET', event.state.url, true)
+  request.setRequestHeader('Accept', 'application/json')
+
+  request.onload = () => {
+    if (request.status === 200) {
+      window.info.page = JSON.parse(request.responseText)
+      document.title = event.state.name
+    } else {
+      console.log(request.responseText)
+    }
+  }
+
+  request.onerror = (error) => { console.log(error) }
+  request.send()
+})
+
 /* eslint-disable no-new */
 new Vue({
   el: '#app',
diff --git a/_assets/src/page.js b/_assets/src/page.js
new file mode 100644
index 00000000..5a908772
--- /dev/null
+++ b/_assets/src/page.js
@@ -0,0 +1,36 @@
+function open (url, history) {
+  window.info.page.kind = ''
+
+  let request = new window.XMLHttpRequest()
+  request.open('GET', url, true)
+  request.setRequestHeader('Accept', 'application/json')
+
+  request.onload = () => {
+    if (request.status === 200) {
+      window.info.page = JSON.parse(request.responseText)
+
+      if (history) {
+        window.history.pushState({
+          name: window.info.page.name,
+          url: url
+        }, window.info.page.name, url)
+
+        document.title = window.info.page.name
+      }
+    } else {
+      console.log(request.responseText)
+    }
+  }
+
+  request.onerror = (error) => { console.log(error) }
+  request.send()
+}
+
+export default {
+  reload: () => {
+    open(window.location.pathname, false)
+  },
+  open: (url) => {
+    open(url, true)
+  }
+}
diff --git a/_assets/src/webdav.js b/_assets/src/webdav.js
new file mode 100644
index 00000000..72f03854
--- /dev/null
+++ b/_assets/src/webdav.js
@@ -0,0 +1,109 @@
+var info = window.info
+
+function convertURL (url) {
+  return window.location.origin + url.replace(info.baseURL + '/', info.webdavURL + '/')
+}
+
+function move (oldLink, newLink) {
+  return new Promise((resolve, reject) => {
+    let request = new window.XMLHttpRequest()
+
+    oldLink = convertURL(oldLink)
+    newLink = newLink.replace(info.baseURL + '/', info.webdavURL + '/')
+    newLink = window.location.origin + newLink.substring(info.baseURL.length)
+
+    request.open('MOVE', oldLink, true)
+    request.setRequestHeader('Destination', newLink)
+    request.onload = () => {
+      if (request.status === 201 || request.status === 204) {
+        resolve()
+      } else {
+        reject(request.statusText)
+      }
+    }
+    request.onerror = () => reject(request.statusText)
+    request.send()
+  })
+}
+
+function put (link, body, headers = {}) {
+  return new Promise((resolve, reject) => {
+    let request = new window.XMLHttpRequest()
+    request.open('PUT', convertURL(link), true)
+
+    for (let key in headers) {
+      request.setRequestHeader(key, headers[key])
+    }
+
+    request.onload = () => {
+      if (request.status === 201) {
+        resolve()
+      } else {
+        reject(request.statusText)
+      }
+    }
+    request.onerror = () => reject(request.statusText)
+    request.send(body)
+  })
+}
+
+function propfind (link, body, headers = {}) {
+  return new Promise((resolve, reject) => {
+    let request = new window.XMLHttpRequest()
+    request.open('PROPFIND', convertURL(link), true)
+
+    for (let key in headers) {
+      request.setRequestHeader(key, headers[key])
+    }
+
+    request.onload = () => {
+      if (request.status < 300) {
+        resolve(request.responseText)
+      } else {
+        reject(request.statusText)
+      }
+    }
+    request.onerror = () => reject(request.statusText)
+    request.send(body)
+  })
+}
+
+function trash (link) {
+  return new Promise((resolve, reject) => {
+    let request = new window.XMLHttpRequest()
+    request.open('DELETE', convertURL(link), true)
+    request.onload = () => {
+      if (request.status === 204) {
+        resolve()
+      } else {
+        reject(request.statusText)
+      }
+    }
+    request.onerror = () => reject(request.statusText)
+    request.send()
+  })
+}
+
+function create (link) {
+  return new Promise((resolve, reject) => {
+    let request = new window.XMLHttpRequest()
+    request.open((link.endsWith('/') ? 'MKCOL' : 'PUT'), convertURL(link), true)
+    request.onload = () => {
+      if (request.status === 201) {
+        resolve()
+      } else {
+        reject(request.statusText)
+      }
+    }
+    request.onerror = () => reject(request.statusText)
+    request.send()
+  })
+}
+
+export default {
+  create: create,
+  trash: trash,
+  propfind: propfind,
+  put: put,
+  move: move
+}
diff --git a/cmd/filemanager/main.go b/cmd/filemanager/main.go
index 79734a59..360965de 100644
--- a/cmd/filemanager/main.go
+++ b/cmd/filemanager/main.go
@@ -10,16 +10,21 @@ import (
 var m *filemanager.FileManager
 
 func handler(w http.ResponseWriter, r *http.Request) {
-	_, err := m.ServeHTTP(w, r)
+	// TODO: review return codes and return 0 when everything works.
+
+	code, err := m.ServeHTTP(w, r)
 	if err != nil {
 		log.Print(err)
 	}
+
+	if code != 0 {
+		w.WriteHeader(code)
+	}
 }
 
 func main() {
 	m = filemanager.New("D:\\TEST")
 	m.SetBaseURL("/vaca")
-	m.AllowEdit = false
 	m.Commands = []string{"git"}
 	http.HandleFunc("/", handler)
 	http.ListenAndServe(":8080", nil)
diff --git a/file.go b/file.go
index cdbb7c0b..16ee5033 100644
--- a/file.go
+++ b/file.go
@@ -31,48 +31,48 @@ type fileInfo struct {
 	// Used to store the file's content temporarily.
 	content []byte
 	// The name of the file.
-	Name string
+	Name string `json:"name"`
 	// The Size of the file.
-	Size int64
+	Size int64 `json:"size"`
 	// The absolute URL.
-	URL string
+	URL string `json:"url"`
 	// The extension of the file.
-	Extension string
+	Extension string `json:"extension"`
 	// The last modified time.
-	ModTime time.Time
+	ModTime time.Time `json:"modified"`
 	// The File Mode.
-	Mode os.FileMode
+	Mode os.FileMode `json:"mode"`
 	// Indicates if this file is a directory.
-	IsDir bool
+	IsDir bool `json:"isDir"`
 	// Absolute path.
-	Path string
+	Path string `json:"path"`
 	// Relative path to user's virtual File System.
-	VirtualPath string
+	VirtualPath string `json:"virtualPath"`
 	// Indicates the file content type: video, text, image, music or blob.
-	Type string
+	Type string `json:"type"`
 	// Stores the content of a text file.
-	Content string
+	Content string `json:"content"`
 }
 
 // A listing is the context used to fill out a template.
 type listing struct {
 	// The name of the directory (the last element of the path).
-	Name string
+	Name string `json:"-"`
 	// The full path of the request relatively to a File System.
-	Path string
+	Path string `json:"-"`
 	// The items (files and folders) in the path.
-	Items []fileInfo
+	Items []fileInfo `json:"items"`
 	// The number of directories in the listing.
-	NumDirs int
+	NumDirs int `json:"numDirs"`
 	// The number of files (items that aren't directories) in the listing.
-	NumFiles int
+	NumFiles int `json:"numFiles"`
 	// Which sorting order is used.
-	Sort string
+	Sort string `json:"sort"`
 	// And which order.
-	Order string
+	Order string `json:"order"`
 	// If ≠0 then Items have been limited to that many elements.
-	ItemsLimitedTo int
-	Display        string
+	ItemsLimitedTo int    `json:"ItemsLimitedTo"`
+	Display        string `json:"display"`
 }
 
 // getInfo gets the file information and, in case of error, returns the
diff --git a/filemanager.go b/filemanager.go
index 72cdc420..18e4729f 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -69,12 +69,12 @@ type User struct {
 	StyleSheet string `json:"-"`
 
 	// These indicate if the user can perform certain actions.
-	AllowNew      bool // Create files and folders
-	AllowEdit     bool // Edit/rename files
-	AllowCommands bool // Execute commands
+	AllowNew      bool `json:"allowNew"`      // Create files and folders
+	AllowEdit     bool `json:"allowEdit"`     // Edit/rename files
+	AllowCommands bool `json:"allowCommands"` // Execute commands
 
 	// Commands is the list of commands the user can execute.
-	Commands []string
+	Commands []string `json:"commands"`
 }
 
 // Rule is a dissalow/allow rule.
diff --git a/page.go b/page.go
index 165a81db..24251d3e 100644
--- a/page.go
+++ b/page.go
@@ -35,10 +35,10 @@ type page struct {
 	BaseURL   string `json:"-"`
 	WebDavURL string `json:"-"`
 
-	Name string
-	Path string
-	Kind string // listing, editor or preview
-	Data interface{}
+	Name string      `json:"name"`
+	Path string      `json:"path"`
+	Kind string      `json:"kind"` // listing, editor or preview
+	Data interface{} `json:"data"`
 }
 
 // breadcrumbItem contains the Name and the URL of a breadcrumb piece.
diff --git a/serve.go b/serve.go
index 28890190..93adf300 100644
--- a/serve.go
+++ b/serve.go
@@ -95,14 +95,14 @@ func serveListing(c *requestContext, w http.ResponseWriter, r *http.Request) (in
 		listing.ItemsLimitedTo = limit
 	}
 
+	listing.Display = displayMode(w, r, cookieScope)
+	c.pg.Data = listing
+
 	// If it's a JSON request, print only the items... in JSON! (such a surprise -_-)
 	if strings.Contains(r.Header.Get("Accept"), "application/json") {
-		c.pg.Data = listing.Items
 		return c.pg.PrintJSON(w)
 	}
 
-	listing.Display = displayMode(w, r, cookieScope)
-	c.pg.Data = listing
 	return c.pg.PrintHTML(w, c.fm.templates)
 }