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 }