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) }