From 346412eb2a7926111cb974eec6e6aa6cb7a20758 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Wed, 28 Jun 2017 22:20:28 +0100
Subject: [PATCH] Improvements :)

Former-commit-id: c1c1881302a241fdc7140e6aabeb9b49977bd7c6 [formerly 84bb454c2f34baffd9dfa91645b8aff149e52620] [formerly 29e258c7a16db1ca8a3fde7c5e4e3cffc47899a6 [formerly 84ddad027fed623021092d56872ff138bc5ea416]]
Former-commit-id: 0018a51df5bc801b783a3ffe17d9f33c504ce094 [formerly 0072c425cd4754e38f30007ab9f5272ea4b40370]
Former-commit-id: d298f006e58ef9e4987def4bc354818062b30fcd
---
 _assets/_old/js/common_old.js          | 119 -------------------
 _assets/_old/js/listing.js             |  23 ----
 _assets/_old/templates/templates.tmpl  |  43 -------
 _assets/index.html                     |  17 ++-
 _assets/src/App.vue                    |  79 +++++++++++--
 _assets/src/components/Help.vue        |  38 ++++++
 _assets/src/components/InfoButton.vue  |   8 +-
 _assets/src/components/InfoPrompt.vue  | 114 ++++++++++++++++++
 _assets/src/components/Listing.vue     |  36 +++---
 _assets/src/components/ListingItem.vue |  19 ++-
 _assets/src/components/Preview.vue     |   7 +-
 _assets/src/components/Search.vue      |  28 ++---
 _assets/src/css/header.css             |  23 ++--
 _assets/src/css/prompts.css            | 139 ++++++++++++++++++++++
 _assets/src/css/styles.css             | 158 +------------------------
 _assets/src/main.js                    |  11 +-
 _assets/src/page.js                    |  26 +++-
 _assets/src/webdav.js                  |   8 +-
 editor.go                              |   3 +-
 file.go                                |  11 +-
 serve.go                               |   2 +-
 21 files changed, 467 insertions(+), 445 deletions(-)
 create mode 100644 _assets/src/components/Help.vue
 create mode 100644 _assets/src/components/InfoPrompt.vue
 create mode 100644 _assets/src/css/prompts.css

diff --git a/_assets/_old/js/common_old.js b/_assets/_old/js/common_old.js
index 4b9e8cac..4fc09a5e 100644
--- a/_assets/_old/js/common_old.js
+++ b/_assets/_old/js/common_old.js
@@ -125,95 +125,6 @@ function logoutEvent (event) {
   }
 }
 
-function getHash (event, hash) {
-  event.preventDefault()
-
-  let request = new window.XMLHttpRequest()
-  let link
-
-  if (selectedItems.length) {
-    link = document.getElementById(selectedItems[0]).dataset.url
-  } else {
-    link = window.location.pathname
-  }
-
-  request.open('GET', `${link}?checksum=${hash}`, true)
-
-  request.onload = () => {
-    if (request.status >= 300) {
-      console.log(request.statusText)
-      return
-    }
-    event.target.parentElement.innerHTML = request.responseText
-  }
-  request.onerror = (e) => console.log(e)
-  request.send()
-}
-
-function infoEvent (event) {
-  event.preventDefault()
-  if (event.currentTarget.classList.contains('disabled')) {
-    return
-  }
-
-  let dir = false
-  let link
-
-  if (selectedItems.length) {
-    link = document.getElementById(selectedItems[0]).dataset.url
-    dir = document.getElementById(selectedItems[0]).dataset.dir
-  } else {
-    if (document.getElementById('listing') !== null) {
-      dir = true
-    }
-
-    link = window.location.pathname
-  }
-
-  buttons.setLoading('info', false)
-
-  webdav.propfind(link)
-    .then((text) => {
-      let parser = new window.DOMParser()
-      let xml = parser.parseFromString(text, 'text/xml')
-      let clone = document.importNode(templates.info.content, true)
-
-      let value = xml.getElementsByTagName('displayname')
-      if (value.length > 0) {
-        clone.getElementById('display_name').innerHTML = value[0].innerHTML
-      } else {
-        clone.getElementById('display_name').innerHTML = xml.getElementsByTagName('D:displayname')[0].innerHTML
-      }
-
-      value = xml.getElementsByTagName('getcontentlength')
-      if (value.length > 0) {
-        clone.getElementById('content_length').innerHTML = value[0].innerHTML
-      } else {
-        clone.getElementById('content_length').innerHTML = xml.getElementsByTagName('D:getcontentlength')[0].innerHTML
-      }
-
-      value = xml.getElementsByTagName('getlastmodified')
-      if (value.length > 0) {
-        clone.getElementById('last_modified').innerHTML = value[0].innerHTML
-      } else {
-        clone.getElementById('last_modified').innerHTML = xml.getElementsByTagName('D:getlastmodified')[0].innerHTML
-      }
-
-      if (dir === true || dir === 'true') {
-        clone.querySelector('.file-only').style.display = 'none'
-      }
-
-      document.querySelector('body').appendChild(clone)
-      document.querySelector('.overlay').classList.add('active')
-      document.querySelector('.prompt').classList.add('active')
-      buttons.setDone('info', true)
-    })
-    .catch(e => {
-      buttons.setDone('info', false)
-      console.log(e)
-    })
-}
-
 function deleteOnSingleFile () {
   closePrompt()
   buttons.setLoading('delete')
@@ -279,36 +190,6 @@ function deleteEvent (event) {
   return false
 }
 
-function closeHelp (event) {
-  event.preventDefault()
-
-  document.querySelector('.help').classList.remove('active')
-  document.querySelector('.overlay').classList.remove('active')
-}
-
-function openHelp (event) {
-  closePrompt(event)
-
-  document.querySelector('.help').classList.add('active')
-  document.querySelector('.overlay').classList.add('active')
-}
-
-window.addEventListener('keydown', (event) => {
-  if (event.keyCode === 27) {
-    if (document.querySelector('.help.active')) {
-      closeHelp(event)
-    }
-  }
-
-  if (event.keyCode === 46) {
-    deleteEvent(event)
-  }
-
-  if (event.keyCode === 112) {
-    event.preventDefault()
-    openHelp(event)
-  }
-})
 
 /* * * * * * * * * * * * * * * *
  *                             *
diff --git a/_assets/_old/js/listing.js b/_assets/_old/js/listing.js
index 6b233d90..097b3f65 100644
--- a/_assets/_old/js/listing.js
+++ b/_assets/_old/js/listing.js
@@ -159,29 +159,6 @@ listing.addDoubleTapEvent = function () {
   })
 }
 
-// Keydown events
-window.addEventListener('keydown', (event) => {
-  if (event.keyCode == 27) {
-    listing.unselectAll()
-
-    if (document.querySelectorAll('.prompt').length) {
-      closePrompt(event)
-    }
-  }
-
-  if (event.keyCode == 113) {
-    listing.rename()
-  }
-
-  if (event.ctrlKey || event.metaKey) {
-    switch (String.fromCharCode(event.which).toLowerCase()) {
-      case 's':
-        event.preventDefault()
-        window.location = '?download=true'
-    }
-  }
-})
-
 listing.selectMoveFolder = function (event) {
   if (event.target.getAttribute('aria-selected') === 'true') {
     event.target.setAttribute('aria-selected', false)
diff --git a/_assets/_old/templates/templates.tmpl b/_assets/_old/templates/templates.tmpl
index a0910314..ebc2e42a 100644
--- a/_assets/_old/templates/templates.tmpl
+++ b/_assets/_old/templates/templates.tmpl
@@ -11,26 +11,6 @@
     </form>
 </template>
 
-<template id="info-template">
-    <div class="prompt">
-        <h3>File Information</h3>
-        <p><strong>Display Name:</strong> <span id="display_name"></span></p>
-        <p><strong>Size:</strong> <span id="content_length"></span> Bytes</p>
-        <p><strong>Last Modified:</strong> <span id="last_modified"></span></p>
-
-        <section class="file-only">
-            <p><strong>MD5:</strong> <code id="md5"><a href="#" onclick="getHash(event, 'md5')">show</a></code></p>
-            <p><strong>SHA1:</strong> <code id="sha1"><a href="#" onclick="getHash(event, 'sha1')">show</a></code></p>
-            <p><strong>SHA256:</strong> <code id="sha256"><a href="#" onclick="getHash(event, 'sha256')">show</a></code></p>
-            <p><strong>SHA512:</strong> <code id="sha512"><a href="#" onclick="getHash(event, 'sha512')">show</a></code></p>
-        </section>
-
-        <div>
-            <button type="submit" onclick="closePrompt(event);" class="ok">OK</button>
-        </div>
-    </div>
-</template>
-
 <template id="message-template">
     <div class="prompt">
         <h3></h3>
@@ -60,28 +40,5 @@
     </form>
 </template>
 
-<div class="help">
-    <h3>Help</h3>
 
-    <ul>
-        <li><strong>F1</strong> - this information</li>
-        <li><strong>F2</strong> - rename file</li>
-        <li><strong>DEL</strong> - delete selected items</li>
-        <li><strong>ESC</strong> - clear selection and/or close the prompt</li>
-        <li><strong>CTRL + S</strong> - save a file or download the directory where you are</li>
-        <li><strong>CTRL + Click</strong> - select multiple files or directories</li>
-        <li><strong>Double click</strong> - open a file or directory</li>
-        <li><strong>Click</strong> - select file or directory</li>
-    </ul>
-
-    <p>Not available yet</p>
-
-    <ul>
-        <li><strong>Alt + Click</strong> - select a group of files</li>
-    </ul>
-
-    <div>
-        <button type="submit" onclick="closeHelp(event);" class="ok">OK</button>
-    </div>
-</div>
 {{ end }}
\ No newline at end of file
diff --git a/_assets/index.html b/_assets/index.html
index bc1687bb..fe6e4260 100644
--- a/_assets/index.html
+++ b/_assets/index.html
@@ -33,10 +33,19 @@
   <body>
     <script>
     var info = {
-        user: JSON.parse('{{ Marshal .User }}'),
-        page: JSON.parse('{{ Marshal . }}'),
-        webdavURL: "{{ .WebDavURL }}",
-        baseURL: "{{.BaseURL}}"
+      user: JSON.parse('{{ Marshal .User }}'),
+      req: JSON.parse('{{ Marshal . }}'),
+      webdavURL: "{{ .WebDavURL }}",
+      baseURL: "{{.BaseURL}}",
+      ssl: (window.location.protocol === 'https:'),
+      showInfo: false,
+      showHelp: false,
+      showDelete: false,
+      showRename: false,
+      listing: {
+        selected: [],
+        multiple: false
+      }
     }
     </script>
     <div id="app">
diff --git a/_assets/src/App.vue b/_assets/src/App.vue
index 137eb2da..fb07443f 100644
--- a/_assets/src/App.vue
+++ b/_assets/src/App.vue
@@ -1,11 +1,11 @@
 <template>
   <div id="app">
     <header>
-      <div id="first-bar">
+      <div>
         <img src="./assets/logo.svg" alt="File Manager">
         <search></search>
       </div>
-      <div id="second-bar">
+      <div>
         <info-button></info-button>
       </div>
       <!-- <div id="click-overlay"></div> -->
@@ -21,12 +21,12 @@
       </div>
     </nav>
     <main>
-      <listing v-if="page.kind == 'listing'"></listing> 
+      <listing v-if="req.kind == 'listing'"></listing> 
     </main>
 
-    <preview v-if="page.kind == 'preview'"></preview> 
+    <preview v-if="req.kind == 'preview'"></preview> 
 
-    <div class="overlay"></div>
+    
     <!-- TODO: show on listing and allowedit -->
     <div class="floating">
         <div tabindex="0" role="button" class="action" id="new">
@@ -42,15 +42,22 @@
         </div>
     </div>
 
-     <footer>Served with <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.</footer>
+    <info-prompt v-show="showInfo" :class="{ active: showInfo }"></info-prompt>
+    <help v-show="showHelp" :class="{ active: showHelp }"></help>
+
+    <div v-show="showOverlay()" class="overlay" :class="{ active: showOverlay() }"></div>
+
+    <footer>Served with <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.</footer>
   </div>
 </template>
 
 <script>
 import Search from './components/Search'
 import Preview from './components/Preview'
+import Help from './components/Help'
 import Listing from './components/Listing'
 import InfoButton from './components/InfoButton'
+import InfoPrompt from './components/InfoPrompt'
 import css from './css.js'
 
 function updateColumnSizes () {
@@ -60,9 +67,62 @@ function updateColumnSizes () {
   items.style.width = `calc(${100 / columns}% - 1em)`
 }
 
+window.addEventListener('keydown', (event) => {
+  // Esc!
+  if (event.keyCode === 27) {
+    window.info.showHelp = false
+    window.info.showInfo = false
+    window.info.showDelete = false
+    window.info.showRename = false
+
+    // Unselect all files and folders.
+    if (window.info.req.kind === 'listing') {
+      let items = document.getElementsByClassName('item')
+      Array.from(items).forEach(link => {
+        link.setAttribute('aria-selected', false)
+      })
+
+      window.info.listing.selected.length = 0
+    }
+
+    return
+  }
+
+  // Del!
+  if (event.keyCode === 46) {
+    window.info.showDelete = true
+  }
+
+  // F1!
+  if (event.keyCode === 112) {
+    event.preventDefault()
+    window.info.showHelp = true
+  }
+
+  // F2!
+  if (event.keyCode === 113) {
+    window.info.showRename = true
+  }
+
+  // CTRL + S
+  if (event.ctrlKey || event.metaKey) {
+    switch (String.fromCharCode(event.which).toLowerCase()) {
+      case 's':
+        event.preventDefault()
+
+        if (window.info.req.kind !== 'editor') {
+          window.location = '?download=true'
+          return
+        }
+
+        // TODO: save file on editor!
+    }
+  }
+})
+
 export default {
   name: 'app',
-  components: { Search, Preview, Listing, InfoButton },
+  components: { Search, Preview, Listing, InfoButton, InfoPrompt, Help },
   mounted: function () {
     updateColumnSizes()
     window.addEventListener('resize', updateColumnSizes)
@@ -70,6 +130,11 @@ export default {
   },
   data: function () {
     return window.info
+  },
+  methods: {
+    showOverlay: function () {
+      return this.showInfo || this.showHelp
+    }
   }
 }
 </script>
diff --git a/_assets/src/components/Help.vue b/_assets/src/components/Help.vue
new file mode 100644
index 00000000..0669739b
--- /dev/null
+++ b/_assets/src/components/Help.vue
@@ -0,0 +1,38 @@
+<template>
+    <div class="prompt help">
+        <h3>Help</h3>
+
+        <ul>
+            <li><strong>F1</strong> - this information</li>
+            <li><strong>F2</strong> - rename file</li>
+            <li><strong>DEL</strong> - delete selected items</li>
+            <li><strong>ESC</strong> - clear selection and/or close the prompt</li>
+            <li><strong>CTRL + S</strong> - save a file or download the directory where you are</li>
+            <li><strong>CTRL + Click</strong> - select multiple files or directories</li>
+            <li><strong>Double click</strong> - open a file or directory</li>
+            <li><strong>Click</strong> - select file or directory</li>
+        </ul>
+
+        <p>Not available yet</p>
+
+        <ul>
+            <li><strong>Alt + Click</strong> - select a group of files</li>
+        </ul>
+
+        <div>
+            <button type="submit" @click="close" class="ok">OK</button>
+        </div>
+    </div>
+</template>
+
+<script>
+export default {
+  name: 'help',
+  methods: {
+    close: function (event) {
+      window.info.showHelp = false
+    }
+  }
+}
+</script>
+
diff --git a/_assets/src/components/InfoButton.vue b/_assets/src/components/InfoButton.vue
index 9ea8a7e4..4e101b6d 100644
--- a/_assets/src/components/InfoButton.vue
+++ b/_assets/src/components/InfoButton.vue
@@ -1,5 +1,5 @@
 <template>
-    <button title="Info" aria-label="Info" class="action" id="info">
+    <button @click="show" title="Info" aria-label="Info" class="action" id="info">
         <i class="material-icons">info</i>
         <span>Info</span>
     </button>
@@ -8,8 +8,10 @@
 <script>
 export default {
   name: 'info-button',
-  data: function () {
-    return window.info.page.data
+  methods: {
+    show: function (event) {
+      window.info.showInfo = true
+    }
   }
 }
 </script>
diff --git a/_assets/src/components/InfoPrompt.vue b/_assets/src/components/InfoPrompt.vue
new file mode 100644
index 00000000..4ce2712e
--- /dev/null
+++ b/_assets/src/components/InfoPrompt.vue
@@ -0,0 +1,114 @@
+<template>
+    <div class="prompt">
+        <h3>File Information</h3>
+
+        <p v-show="listing.selected.length > 1">{{ listing.selected.length }} files selected.</p>
+
+        <p v-show="listing.selected.length < 2"><strong>Display Name:</strong> {{ name() }}</p>
+        <p><strong>Size:</strong> <span id="content_length"></span>{{ humanSize() }}</p>
+        <p v-show="listing.selected.length < 2"><strong>Last Modified:</strong> {{ humanTime() }}</p>
+
+        <section v-show="dir() && listing.selected.length === 0">
+          <p><strong>Number of files:</strong> {{ req.data.numFiles }}</p>
+          <p><strong>Number of directories:</strong> {{ req.data.numDirs }}</p>
+        </section>
+
+        <section v-show="!dir()">
+            <p><strong>MD5:</strong> <code><a @click="checksum($event, 'md5')">show</a></code></p>
+            <p><strong>SHA1:</strong> <code><a @click="checksum($event, 'sha1')">show</a></code></p>
+            <p><strong>SHA256:</strong> <code><a @click="checksum($event, 'sha256')">show</a></code></p>
+            <p><strong>SHA512:</strong> <code><a @click="checksum($event, 'sha512')">show</a></code></p>
+        </section>
+
+        <div>
+            <button type="submit" @click="close" class="ok">OK</button>
+        </div>
+    </div>
+</template>
+
+<script>
+import filesize from 'filesize'
+import moment from 'moment'
+
+export default {
+  name: 'info-prompt',
+  data: function () {
+    return window.info
+  },
+  methods: {
+    humanSize: function () {
+      if (this.listing.selected.length === 0 || this.req.kind !== 'listing') {
+        return filesize(this.req.data.size)
+      }
+
+      var sum = 0
+
+      for (let i = 0; i < this.listing.selected.length; i++) {
+        sum += this.req.data.items[this.listing.selected[i]].size
+      }
+
+      return filesize(sum)
+    },
+    humanTime: function () {
+      if (this.listing.selected.length === 0) {
+        return moment(this.req.data.modified).fromNow()
+      }
+
+      return moment(this.req.data.items[this.listing.selected[0]]).fromNow()
+    },
+    name: function () {
+      if (this.listing.selected.length === 0) {
+        return this.req.data.name
+      }
+
+      return this.req.data.items[this.listing.selected[0]].name
+    },
+    dir: function () {
+      if (this.listing.selected.length > 1) {
+        // Don't show when multiple selected.
+        return true
+      }
+
+      if (this.listing.selected.length === 0) {
+        return this.req.data.isDir
+      }
+
+      return this.req.data.items[this.listing.selected[0]].isDir
+    },
+    checksum: function (event, hash) {
+      event.preventDefault()
+
+      let request = new window.XMLHttpRequest()
+      let link
+
+      if (this.listing.selected.length) {
+        link = this.req.data.items[this.listing.selected[0]].url
+      } else {
+        link = window.location.pathname
+      }
+
+      request.open('GET', `${link}?checksum=${hash}`, true)
+
+      request.onload = () => {
+        if (request.status >= 300) {
+          console.log(request.statusText)
+          return
+        }
+
+        event.target.innerHTML = request.responseText
+      }
+
+      request.onerror = (e) => console.log(e)
+      request.send()
+    },
+    close: function () {
+      this.showInfo = false
+
+      let checksums = this.$el.querySelectorAll('a')
+      for (let i = 0; i < checksums.length; i++) {
+        checksums[i].innerHTML = 'show'
+      }
+    }
+  }
+}
+</script>
diff --git a/_assets/src/components/Listing.vue b/_assets/src/components/Listing.vue
index af9995bb..0d3b96b3 100644
--- a/_assets/src/components/Listing.vue
+++ b/_assets/src/components/Listing.vue
@@ -1,6 +1,6 @@
 <template>
     <div id="listing" 
-      :class="data.display" 
+      :class="req.data.display" 
       @drop="drop"
       @dragenter="dragEnter" 
       @dragend="dragEnd">
@@ -8,13 +8,13 @@
             <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: req.data.sort === 'name' }" class="name"><span>Name</span>
+                        <a v-if="req.data.sort === 'name' && req.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: req.data.sort === 'size' }" class="size"><span>Size</span>
+                        <a v-if="req.data.sort === 'size' && req.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>
 
@@ -23,16 +23,15 @@
             </div>
         </div>
 
-        <h2 v-if="(data.numDirs + data.numFiles) == 0" class="message">It feels lonely here :'(</h2>
+        <h2 v-if="(req.data.numDirs + req.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="req.data.numDirs > 0">Folders</h2>
+        <div v-if="req.data.numDirs > 0">
           <item
-            v-for="(item, index) in data.items"
+            v-for="(item, index) in req.data.items"
             v-if="item.isDir"
             :key="base64(item.name)"
-            :id="base64(item.name)"
-            v-bind:selected="selected"
+            v-bind:index="index"
             v-bind:name="item.name"
             v-bind:isDir="item.isDir"
             v-bind:url="item.url"
@@ -42,14 +41,13 @@
           </item>
         </div>
 
-        <h2 v-if="data.numFiles > 0">Files</h2>
-        <div v-if="data.numFiles > 0">
+        <h2 v-if="req.data.numFiles > 0">Files</h2>
+        <div v-if="req.data.numFiles > 0">
           <item
-            v-for="(item, index) in data.items"
+            v-for="(item, index) in req.data.items"
             v-if="!item.isDir"
             :key="base64(item.name)"
-            :id="base64(item.name)"
-            v-bind:selected="selected"
+            v-bind:index="index"
             v-bind:name="item.name"
             v-bind:isDir="item.isDir"
             v-bind:url="item.url"
@@ -73,11 +71,7 @@ import page from '../page.js'
 export default {
   name: 'preview',
   data: function () {
-    return {
-      data: window.info.page.data,
-      selected: [],
-      multiple: false
-    }
+    return window.info
   },
   components: { Item },
   mounted: function () {
diff --git a/_assets/src/components/ListingItem.vue b/_assets/src/components/ListingItem.vue
index fc40f990..2715a19f 100644
--- a/_assets/src/components/ListingItem.vue
+++ b/_assets/src/components/ListingItem.vue
@@ -33,7 +33,10 @@ import array from '../array.js'
 
 export default {
   name: 'item',
-  props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'selected'],
+  props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index'],
+  data: function () {
+    return window.info.listing
+  },
   methods: {
     icon: function () {
       if (this.isDir) return 'folder'
@@ -97,22 +100,18 @@ export default {
       })
 
       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 (this.selected.indexOf(this.index) === -1) {
         if (!event.ctrlKey && !this.multiple) this.unselectAll()
 
-        el.setAttribute('aria-selected', true)
-        this.selected.push(el.id)
+        this.$el.setAttribute('aria-selected', true)
+        this.selected.push(this.index)
       } else {
-        el.setAttribute('aria-selected', false)
-        this.selected = array.remove(this.selected, el.id)
+        this.$el.setAttribute('aria-selected', false)
+        this.selected = array.remove(this.selected, this.index)
       }
 
       // this.handleSelectionChange()
diff --git a/_assets/src/components/Preview.vue b/_assets/src/components/Preview.vue
index 8f61374e..aedfbf17 100644
--- a/_assets/src/components/Preview.vue
+++ b/_assets/src/components/Preview.vue
@@ -23,17 +23,20 @@
 </template>
 
 <script>
+import page from '../page'
+
 export default {
   name: 'preview',
   data: function () {
-    return window.info.page.data
+    return window.info.req.data
   },
   methods: {
     raw: function () {
       return this.url + '?raw=true'
     },
     back: function (event) {
-      window.history.back()
+      let url = page.removeLastDir(window.location.pathname)
+      page.open(url)
     }
   }
 }
diff --git a/_assets/src/components/Search.vue b/_assets/src/components/Search.vue
index 399d7113..d6a0ff2f 100644
--- a/_assets/src/components/Search.vue
+++ b/_assets/src/components/Search.vue
@@ -18,17 +18,7 @@
 <script>
 import page from '../page'
 
-// Remove the last directory of an url
-var removeLastDirectoryPartOf = function (url) {
-  var arr = url.split('/')
-  if (arr.pop() === '') {
-    arr.pop()
-  }
-  return (arr.join('/'))
-}
-
-var user = window.info.user
-var ssl = window.ssl
+var $ = window.info
 
 export default {
   name: 'search',
@@ -49,8 +39,8 @@ export default {
   },
   methods: {
     reset: function () {
-      if (user.allowCommands && user.commands.length > 0) {
-        this.box.innerHTML = `Search or use one of your supported commands: ${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.'
       }
@@ -59,8 +49,8 @@ export default {
       let value = this.input.value
       let pieces = value.split(' ')
 
-      for (let i = 0; i < user.commands.length; i++) {
-        if (pieces[0] === user.commands[0]) {
+      for (let i = 0; i < $.user.commands.length; i++) {
+        if (pieces[0] === $.user.commands[0]) {
           return true
         }
       }
@@ -79,7 +69,7 @@ export default {
         return
       }
 
-      if (!this.supported() || !user.allowCommands) {
+      if (!this.supported() || !$.user.allowCommands) {
         this.box.innerHTML = 'Press enter to search.'
       } else {
         this.box.innerHTML = 'Press enter to execute.'
@@ -92,12 +82,12 @@ export default {
       let url = window.location.host + window.location.pathname
 
       if (document.getElementById('editor')) {
-        url = removeLastDirectoryPartOf(url)
+        url = page.removeLastDir(url)
       }
 
-      let protocol = ssl ? 'wss:' : 'ws:'
+      let protocol = $.ssl ? 'wss:' : 'ws:'
 
-      if (this.supported() && user.allowCommands) {
+      if (this.supported() && $.user.allowCommands) {
         let conn = new window.WebSocket(`${protocol}//${url}?command=true`)
 
         conn.onopen = () => {
diff --git a/_assets/src/css/header.css b/_assets/src/css/header.css
index c24f871e..a2c0e331 100644
--- a/_assets/src/css/header.css
+++ b/_assets/src/css/header.css
@@ -16,6 +16,12 @@ header a:hover {
     color: inherit;
 }
 
+header img {
+    height: 2.5em;
+    margin-right: 1em;
+    vertical-align: middle;
+}
+
 header>div {
     display: flex;
     width: 100%;
@@ -32,27 +38,18 @@ header>div div {
     position: relative;
 }
 
-/* * * * * * * * * * * * * * * *
- *            TOP BAR          *
- * * * * * * * * * * * * * * * */
-
-#first-bar {
+header > div:first-child {
     height: 4em;
 }
 
-#first-bar img {
-    height: 2.5em;
-    margin-right: 1em;
-    vertical-align: middle;
+header > div:last-child {
+    justify-content: flex-end;
 }
 
 /* * * * * * * * * * * * * * * *
- *          BOTTOM BAR         *
+ *         MORE??       *
  * * * * * * * * * * * * * * * */
 
-#second-bar {
-    justify-content: flex-end;
-}
 
 #more {
     display: none;
diff --git a/_assets/src/css/prompts.css b/_assets/src/css/prompts.css
new file mode 100644
index 00000000..e4ac6c32
--- /dev/null
+++ b/_assets/src/css/prompts.css
@@ -0,0 +1,139 @@
+.prompt {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    background: #fff;
+    border: 1px solid rgba(0, 0, 0, 0.075);
+    box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
+    padding: 2em;
+    max-width: 25em;
+    width: 90%;
+    max-height: 95%;
+    z-index: 99999;
+}
+
+.overlay {
+    background-color: rgba(0, 0, 0, 0.5);
+    position: fixed;
+    top: 0;
+    left: 0;
+    height: 100%;
+    width: 100%;
+    z-index: 9999;
+}
+
+.overlay.active,
+.prompt.active,
+.help.active {
+    animation: .2s show forwards;
+}
+
+.prompt h3 {
+    margin: 0;
+    font-weight: 500;
+    font-size: 1.5em;
+}
+
+.prompt p {
+    font-size: .9em;
+    color: rgba(0, 0, 0, 0.8);
+    margin: .5em 0 1em;
+}
+
+.prompt input {
+    width: 100%;
+    border: 1px solid #dadada;
+    line-height: 1;
+    padding: .3em;
+}
+
+.prompt code {
+    word-wrap: break-word;
+}
+
+.prompt div {
+    margin-top: 1em;
+    display: flex;
+    justify-content: flex-start;
+    flex-direction: row-reverse;
+}
+
+.prompt .cancel {
+    background-color: #ECEFF1;
+    color: #37474F;
+}
+
+.prompt .cancel:hover {
+    background-color: #e9eaeb;
+}
+
+/* * * * * * * * * * * * * * * *
+ *        PROMPT - MOVE        *
+ * * * * * * * * * * * * * * * */
+
+.prompt .file-list {
+    flex-direction: initial;
+    max-height: 50vh;
+    overflow: auto;
+}
+
+.prompt .file-list ul {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+    width: 100%;
+}
+
+.prompt .file-list ul li {
+    width: 100%;
+    user-select: none;
+}
+
+.prompt .file-list ul li[aria-selected=true] {
+    background: #2196f3 !important;
+    color: #fff !important;
+    transition: .1s ease all;
+}
+
+.prompt .file-list ul li:hover {
+    background-color: #e9eaeb;
+    cursor: pointer;
+}
+
+.prompt .file-list ul li:before {
+    content: "folder";
+    color: #6f6f6f;
+    vertical-align: middle;
+    padding: 0 .25em;
+    line-height: 2em;
+}
+
+.prompt .file-list ul li[aria-selected=true]:before {
+    color: white;
+}
+
+.help {
+    max-width: 24em;
+}
+
+.help ul {
+    padding: 0;
+    margin: 1em 0;
+    list-style: none;
+}
+
+@keyframes show {
+    0% {
+        display: none;
+        opacity: 0;
+    }
+    1% {
+        display: block;
+        opacity: 0;
+    }
+    100% {
+        display: block;
+        opacity: 1;
+    }
+}
diff --git a/_assets/src/css/styles.css b/_assets/src/css/styles.css
index 0f2f86e4..1c9c1598 100644
--- a/_assets/src/css/styles.css
+++ b/_assets/src/css/styles.css
@@ -1,6 +1,7 @@
 @import "./fonts.css";
 @import "./normalize.css";
 @import "./header.css";
+@import "./prompts.css";
 
 body {
     font-family: 'Roboto', sans-serif;
@@ -726,8 +727,7 @@ main {
 #previewer .preview pre,
 #previewer .preview video,
 #previewer .preview img {
-    /* max-height: 80vh; */
-    height: 100%;
+    max-height: 100%;
     margin: 0;
 }
 
@@ -778,162 +778,8 @@ main {
  *            PROMPT           *
  * * * * * * * * * * * * * * * */
 
-.overlay,
-.prompt,
-.help {
-    opacity: 0;
-    z-index: -1;
-    transition: .1s ease opacity, z-index;
-}
-
-.overlay.active,
-.prompt.active,
-.help.active {
-    z-index: 9999999;
-    opacity: 1;
-}
-
-.overlay {
-    background-color: rgba(0, 0, 0, 0.5);
-    position: fixed;
-    top: 0;
-    left: 0;
-    height: 0;
-    width: 0;
-}
-
-.overlay.active {
-    height: 100%;
-    width: 100%;
-}
-
-.prompt,
-.help {
-    position: fixed;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    z-index: 99999999;
-    background: #fff;
-    border: 1px solid rgba(0, 0, 0, 0.075);
-    box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
-    padding: 2em;
-    max-width: 25em;
-    width: 90%;
-    max-height: 95%;
-}
-
-.prompt h3,
-.help h3 {
-    margin: 0;
-    font-weight: 500;
-    font-size: 1.5em;
-}
-
-.prompt p,
-.help p {
-    font-size: .9em;
-    color: rgba(0, 0, 0, 0.8);
-    margin: .5em 0 1em;
-}
-
-.prompt input {
-    width: 100%;
-    border: 1px solid #dadada;
-    line-height: 1;
-    padding: .3em;
-}
-
-.prompt code {
-    word-wrap: break-word;
-}
-
-.prompt div,
-.help div {
-    margin-top: 1em;
-    display: flex;
-    justify-content: flex-start;
-    flex-direction: row-reverse;
-}
-
-.prompt .cancel {
-    background-color: #ECEFF1;
-    color: #37474F;
-}
-
-.prompt .cancel:hover {
-    background-color: #e9eaeb;
-}
 
 
-/* * * * * * * * * * * * * * * *
- *        PROMPT - MOVE        *
- * * * * * * * * * * * * * * * */
-
-.prompt .file-list {
-    flex-direction: initial;
-    max-height: 50vh;
-    overflow: auto;
-}
-
-.prompt .file-list ul {
-    list-style: none;
-    margin: 0;
-    padding: 0;
-    width: 100%;
-}
-
-.prompt .file-list ul li {
-    width: 100%;
-    user-select: none;
-}
-
-.prompt .file-list ul li[aria-selected=true] {
-    background: #2196f3 !important;
-    color: #fff !important;
-    transition: .1s ease all;
-}
-
-.prompt .file-list ul li:hover {
-    background-color: #e9eaeb;
-    cursor: pointer;
-}
-
-.prompt .file-list ul li:before {
-    content: "folder";
-    color: #6f6f6f;
-    vertical-align: middle;
-    padding: 0 .25em;
-    line-height: 2em;
-}
-
-.prompt .file-list ul li[aria-selected=true]:before {
-    color: white;
-}
-
-
-/* * * * * * * * * * * * * * * *
- *             HELP            *
- * * * * * * * * * * * * * * * */
-
-.help {
-    max-width: 24em;
-    visibility: hidden;
-    top: -100%;
-    left: -100%;
-}
-
-.help.active {
-    visibility: visible;
-    top: 50%;
-    left: 50%;
-}
-
-.help ul {
-    padding: 0;
-    margin: 1em 0;
-    list-style: none;
-}
 
 
 /* * * * * * * * * * * * * * * *
diff --git a/_assets/src/main.js b/_assets/src/main.js
index fc6d93e6..6a9062f1 100644
--- a/_assets/src/main.js
+++ b/_assets/src/main.js
@@ -6,18 +6,19 @@ import App from './App'
 
 Vue.config.productionTip = false
 
-window.info = (window.info || window.alert('Something is wrong, please refresh!'))
-window.ssl = (window.location.protocol === 'https:')
+var $ = (window.info || window.alert('Something is wrong, please refresh!'))
 
 // TODO: keep this here?
-document.title = window.info.page.name
+document.title = $.req.name
 
 // TODO: keep this here?
 window.addEventListener('popstate', (event) => {
   event.preventDefault()
   event.stopPropagation()
 
-  window.info.page.kind = ''
+  $.req.kind = ''
+  $.listing.selected.length = 0
+  $.listing.selected.multiple = false
 
   let request = new window.XMLHttpRequest()
   request.open('GET', event.state.url, true)
@@ -25,7 +26,7 @@ window.addEventListener('popstate', (event) => {
 
   request.onload = () => {
     if (request.status === 200) {
-      window.info.page = JSON.parse(request.responseText)
+      $.req = JSON.parse(request.responseText)
       document.title = event.state.name
     } else {
       console.log(request.responseText)
diff --git a/_assets/src/page.js b/_assets/src/page.js
index 5a908772..8a238066 100644
--- a/_assets/src/page.js
+++ b/_assets/src/page.js
@@ -1,5 +1,10 @@
+var $ = window.info
+
 function open (url, history) {
-  window.info.page.kind = ''
+  // Reset info
+  $.listing.selected.length = 0
+  $.listing.selected.multiple = false
+  $.req.kind = ''
 
   let request = new window.XMLHttpRequest()
   request.open('GET', url, true)
@@ -7,15 +12,15 @@ function open (url, history) {
 
   request.onload = () => {
     if (request.status === 200) {
-      window.info.page = JSON.parse(request.responseText)
+      $.req = JSON.parse(request.responseText)
 
       if (history) {
         window.history.pushState({
-          name: window.info.page.name,
+          name: $.req.name,
           url: url
-        }, window.info.page.name, url)
+        }, $.req.name, url)
 
-        document.title = window.info.page.name
+        document.title = $.req.name
       }
     } else {
       console.log(request.responseText)
@@ -26,11 +31,20 @@ function open (url, history) {
   request.send()
 }
 
+function removeLastDir (url) {
+  var arr = url.split('/')
+  if (arr.pop() === '') {
+    arr.pop()
+  }
+  return (arr.join('/'))
+}
+
 export default {
   reload: () => {
     open(window.location.pathname, false)
   },
   open: (url) => {
     open(url, true)
-  }
+  },
+  removeLastDir: removeLastDir
 }
diff --git a/_assets/src/webdav.js b/_assets/src/webdav.js
index 72f03854..bd40f3a4 100644
--- a/_assets/src/webdav.js
+++ b/_assets/src/webdav.js
@@ -1,7 +1,7 @@
-var info = window.info
+var $ = window.info
 
 function convertURL (url) {
-  return window.location.origin + url.replace(info.baseURL + '/', info.webdavURL + '/')
+  return window.location.origin + url.replace($.baseURL + '/', $.webdavURL + '/')
 }
 
 function move (oldLink, newLink) {
@@ -9,8 +9,8 @@ function move (oldLink, newLink) {
     let request = new window.XMLHttpRequest()
 
     oldLink = convertURL(oldLink)
-    newLink = newLink.replace(info.baseURL + '/', info.webdavURL + '/')
-    newLink = window.location.origin + newLink.substring(info.baseURL.length)
+    newLink = newLink.replace($.baseURL + '/', $.webdavURL + '/')
+    newLink = window.location.origin + newLink.substring($.baseURL.length)
 
     request.open('MOVE', oldLink, true)
     request.setRequestHeader('Destination', newLink)
diff --git a/editor.go b/editor.go
index a0efa5a3..49518eb4 100644
--- a/editor.go
+++ b/editor.go
@@ -13,6 +13,7 @@ import (
 
 // editor contains the information to fill the editor template.
 type editor struct {
+	*fileInfo
 	Class       string `json:"class"`
 	Mode        string `json:"mode"`
 	Visual      bool   `json:"visual"`
@@ -28,7 +29,7 @@ func getEditor(r *http.Request, i *fileInfo) (*editor, error) {
 	var err error
 
 	// Create a new editor variable and set the mode
-	e := &editor{}
+	e := &editor{fileInfo: i}
 	e.Mode = editorMode(i.Name)
 	e.Class = editorClass(e.Mode)
 
diff --git a/file.go b/file.go
index 16ee5033..7df7cd37 100644
--- a/file.go
+++ b/file.go
@@ -15,7 +15,6 @@ import (
 	"net/http"
 	"net/url"
 	"os"
-	"path"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -56,10 +55,7 @@ type fileInfo struct {
 
 // 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 `json:"-"`
-	// The full path of the request relatively to a File System.
-	Path string `json:"-"`
+	*fileInfo
 	// The items (files and folders) in the path.
 	Items []fileInfo `json:"items"`
 	// The number of directories in the listing.
@@ -103,7 +99,7 @@ func getInfo(url *url.URL, c *FileManager, u *User) (*fileInfo, error) {
 }
 
 // getListing gets the information about a specific directory and its files.
-func getListing(u *User, filePath string, baseURL string) (*listing, error) {
+func getListing(u *User, filePath string, baseURL string, i *fileInfo) (*listing, error) {
 	// Gets the directory information using the Virtual File System of
 	// the user configuration.
 	file, err := u.fileSystem.OpenFile(context.TODO(), filePath, os.O_RDONLY, 0)
@@ -155,8 +151,7 @@ func getListing(u *User, filePath string, baseURL string) (*listing, error) {
 	}
 
 	return &listing{
-		Name:     path.Base(filePath),
-		Path:     filePath,
+		fileInfo: i,
 		Items:    fileinfos,
 		NumDirs:  dirCount,
 		NumFiles: fileCount,
diff --git a/serve.go b/serve.go
index 0c9515d1..0fe1344c 100644
--- a/serve.go
+++ b/serve.go
@@ -64,7 +64,7 @@ func serveListing(c *requestContext, w http.ResponseWriter, r *http.Request) (in
 
 	c.pg.Kind = "listing"
 
-	listing, err = getListing(c.us, c.fi.VirtualPath, c.fm.RootURL()+r.URL.Path)
+	listing, err = getListing(c.us, c.fi.VirtualPath, c.fm.RootURL()+r.URL.Path, c.fi)
 	if err != nil {
 		return errorToHTTP(err, true), err
 	}