-
-
-
+ const body_tpl = `
+
-
`;
+
`;
- const create_buttons = `
+ const create_buttons = `
-
-
`;
+
+
`;
- const dir_row_tpl = `
- | %icon% |
- %name% |
- %modified% |
+ const dir_row_tpl = `
+ |
+ %thumb% %name% |
+ %modified% |
|
`;
- const file_row_tpl = `
- | %thumb% |
- %name% |
+ const file_row_tpl = `
+ |
+ %thumb% %name% |
%size_bytes% |
- %modified% |
- |
+ %modified% |
+ |
`;
const icon_tpl = `
%icon%`;
@@ -2646,14 +2655,18 @@ const WebDAVNavigator = (url, options) => {
window.onbeforeunload = null;
};
- const download_all = async () => {
+ const download_selected = async () => {
+ var items = document.querySelectorAll('tbody input[type=checkbox]:checked');
for (var i = 0; i < items.length; i++) {
- var item = items[i];
- if (item.is_dir) {
- continue;
+ var input = items[i];
+ var row = input.parentNode.parentNode;
+
+ // Skip directories
+ if (!row.dataset.mime) {
+ return;
}
- await download(item.name, item.size, item.uri)
+ await download(row.dataset.name, row.dataset.size, row.querySelector('th a').href);
}
};
@@ -2713,7 +2726,7 @@ const WebDAVNavigator = (url, options) => {
return _('Yesterday, %s').replace(/%s/, date.toLocaleTimeString());
}
- return date.toLocaleString();
+ return date.toLocaleString([], {year: 'numeric', month: 'numeric', day: 'numeric'});
};
const openListing = (uri, push) => {
@@ -2775,7 +2788,7 @@ const WebDAVNavigator = (url, options) => {
const buildListing = (uri, xml) => {
uri = normalizeURL(uri);
- items = [[], []];
+ items = [];
var title = null;
var root_permissions = null;
@@ -2808,9 +2821,9 @@ const WebDAVNavigator = (url, options) => {
}
var is_dir = node.querySelector('resourcetype collection') ? true : false;
- var index = sort_order == 'name' && is_dir ? 0 : 1;
+ var index = is_dir ? 0 : 1;
- items[index].push({
+ items.push({
'uri': item_uri,
'path': item_uri.substring(base_url.length),
'name': name,
@@ -2822,39 +2835,35 @@ const WebDAVNavigator = (url, options) => {
});
});
- if (sort_order == 'name') {
- items[0].sort((a, b) => a.name.localeCompare(b.name));
- }
-
- items[1].sort((a, b) => {
- if (sort_order == 'date') {
- return b.modified - a.modified;
+ items.sort((a, b) => {
+ if (sort_order === 'date') {
+ return a.modified - b.modified;
}
- else if (sort_order == 'size') {
- return b.size - a.size;
+ else if (sort_order === 'size') {
+ return a.size - b.size;
}
else {
return a.name.localeCompare(b.name);
}
});
- if (sort_order == 'name') {
+ if (sort_order !== 'date') {
// Sort with directories first
- items = items[0].concat(items[1]);
- }
- else {
- items = items[1];
+ items.sort((a, b) => b.is_dir - a.is_dir);
}
+ if (sort_order_desc) {
+ items = items.reverse();
+ }
var table = '';
var parent = uri.replace(/\/+$/, '').split('/').slice(0, -1).join('/') + '/';
if (parent.length >= base_url.length) {
- table += template(dir_row_tpl, {'name': _('Back'), 'uri': parent, 'icon': '↲'});
+ table += template(dir_row_tpl, {'name': _('Back'), 'uri': parent, 'class': 'parent', 'thumb': template(icon_tpl, {})});
}
else {
- title = 'My files';
+ title = _('My files');
}
items.forEach(item => {
@@ -2866,7 +2875,17 @@ const WebDAVNavigator = (url, options) => {
var row = item.is_dir ? dir_row_tpl : file_row_tpl;
item.size_bytes = item.size !== null ? formatBytes(item.size).replace(/ /g, ' ') : null;
- item.icon = item.is_dir ? '📁' : (item.uri.indexOf('.') > 0 ? item.uri.replace(/^.*\.(\w+)$/, '$1').toUpperCase() : '');
+
+ if (!item.is_dir && (pos = item.uri.lastIndexOf('.'))) {
+ var ext = item.uri.substr(pos+1).toUpperCase();
+
+ if (ext.length > 4) {
+ ext = '';
+ }
+ }
+
+ item.icon = ext ?? '';
+ item.class = item.is_dir ? 'dir' : 'file';
item.modified = item.modified !== null ? formatDate(item.modified) : null;
item.name = html(item.name);
@@ -2883,22 +2902,40 @@ const WebDAVNavigator = (url, options) => {
document.title = title;
document.querySelector('main').innerHTML = template(body_tpl, {'title': html(document.title), 'base_url': base_url, 'table': table});
- var select = $('.sortorder');
- select.value = sort_order;
- select.onchange = () => {
- sort_order = select.value;
- window.localStorage.setItem('sort_order', sort_order);
+ var parent_check = document.querySelector('tbody tr.parent .check');
+
+ if (parent_check) {
+ parent_check.innerHTML = '';
+ }
+
+ var column = document.querySelector('thead td[data-sort="' + sort_order + '"]').className += ' selected ' + (sort_order_desc ? 'desc' : 'asc');
+
+ document.querySelectorAll('thead td[data-sort] button').forEach(elm => elm.onclick = (e) => {
+ var new_sort_order = e.target.parentNode.dataset.sort;
+
+ if (sort_order == new_sort_order) {
+ sort_order_desc = !sort_order_desc;
+ }
+
+ sort_order = new_sort_order;
+
+ window.localStorage.setItem('sort_order', new_sort_order);
+ window.localStorage.setItem('sort_order_desc', sort_order_desc ? '1' : '0');
reloadListing();
+ });
+
+ document.querySelector('thead td.check input').onchange = (e) => {
+ document.querySelectorAll('tbody td.check input').forEach(i => i.checked = e.target.checked);
};
if (!items.length) {
- $('.download_all').disabled = true;
+ $('div.buttons .download').disabled = true;
}
else {
- $('.download_all').onclick = download_all;
+ $('div.buttons .download').onclick = download_selected;
}
- $('.deleted_selected').onclick = () => {
+ $('div.buttons .delete').onclick = () => {
var l = document.querySelectorAll('input[name=delete]:checked');
if (!l.length) {
@@ -2924,7 +2961,7 @@ const WebDAVNavigator = (url, options) => {
};
if (!root_permissions || root_permissions.indexOf('C') != -1 || root_permissions.indexOf('K') != -1) {
- $('.upload').insertAdjacentHTML('afterbegin', create_buttons);
+ $('.buttons').insertAdjacentHTML('beforeend', create_buttons);
$('.mkdir').onclick = () => {
openDialog(mkdir_dialog);
@@ -2959,7 +2996,7 @@ const WebDAVNavigator = (url, options) => {
var fi = $('input[type=file]');
- $('.uploadfile').onclick = () => fi.click();
+ $('.upload').onclick = () => fi.click();
fi.onchange = () => {
if (!fi.files.length) return;
@@ -2968,10 +3005,10 @@ const WebDAVNavigator = (url, options) => {
};
}
- Array.from($('table').rows).forEach((tr) => {
+ document.querySelectorAll('table tbody tr').forEach(tr => {
var $$ = (a) => tr.querySelector(a);
var file_url = $$('a').href;
- var file_name = $$('a').innerText;
+ var file_name = tr.dataset.name;
var dir = $$('[colspan]');
var mime = !dir ? tr.getAttribute('data-mime') : 'dir';
var buttons = $$('td.buttons div');
@@ -3045,10 +3082,20 @@ const WebDAVNavigator = (url, options) => {
}
var view_url, edit_url;
+ var allow_preview = false;
+
+ if (mime.match(PREVIEW_TYPES)
+ || file_name.match(PREVIEW_EXTENSIONS)) {
+ allow_preview = true;
+ }
// Don't preview PDF in mobile
- if (mime.match(PREVIEW_TYPES)
- && !(mime == 'application/pdf' && window.navigator.userAgent.match(/Mobi|Tablet|Android|iPad|iPhone/))) {
+ if ((mime == 'application/pdf' || file_name.match(/\.pdf/i))
+ && window.navigator.userAgent.match(/Mobi|Tablet|Android|iPad|iPhone/)) {
+ allow_preview = false;
+ }
+
+ if (allow_preview) {
$$('a').onclick = () => {
if (file_url.match(/\.md$/)) {
openDialog('
', false);
@@ -3076,7 +3123,7 @@ const WebDAVNavigator = (url, options) => {
else if (user && password && !dir) {
$$('a').onclick = () => { download(file_name, size, file_url); return false; };
}
- else {
+ else if (!dir) {
$$('a').download = file_name;
}
@@ -3174,7 +3221,7 @@ const WebDAVNavigator = (url, options) => {
// Markdown editor
if (md) {
- let pre = $('#md');
+ let pre = $('.md_preview');
txt.oninput = () => {
pre.innerHTML = microdown.parse(txt.value);
@@ -3228,6 +3275,7 @@ const WebDAVNavigator = (url, options) => {
var evt, paste_upload, popstate_evt, temp_object_url;
var sort_order = window.localStorage.getItem('sort_order') || 'name';
+ var sort_order_desc = !!parseInt(window.localStorage.getItem('sort_order_desc'), 10);
var wopi_mimes = {}, wopi_extensions = {};
const wopi_discovery_url = options.wopi_discovery_url || null;
@@ -3306,7 +3354,9 @@ const WebDAVNavigator = (url, options) => {
document.body.classList.remove('dragging');
dragcounter = 0;
- const files = [...e.dataTransfer.items].map(item => item.getAsFile());
+ var files = [...e.dataTransfer.items].map(item => item.getAsFile());
+
+ files = files.filter(f => f !== null);
if (!files.length) return;
@@ -3323,24 +3373,29 @@ if (url = document.querySelector('html').getAttribute('data-webdav-url')) {
:root {
--bg-color: #fff;
--fg-color: #000;
- --g1-color: #eee;
- --g2-color: #ccc;
- --g3-color: #999;
- --link-color: blue;
- --visited-color: purple;
- --active-color: darkred;
+ --g1-color: #f5f7f7;
+ --g2-color: #ccd8d6;
+ --g3-color: #7c9696;
+ --link-color: #2e3c3a;
+ --visited-color: #2e3c3a;
+ --active-color: #c47508;
+}
+
+* {
+ margin: 0;
+ padding: 0;
}
body {
text-align: center;
- font-size: 1.1em;
- font-family: Arial, Helvetica, sans-serif;
+ font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
background: var(--bg-color);
color: var(--fg-color);
}
a:link {
color: var(--link-color);
+ text-decoration: none;
}
a:visited {
@@ -3349,70 +3404,126 @@ a:visited {
a:hover {
color: var(--active-color);
+ text-decoration: underline;
}
table {
- margin: 2em auto;
+ margin: 0 auto;
border-collapse: collapse;
- width: 90%;
+ width: 100%;
}
-th, td {
- padding: .5em;
+thead td {
+ height: 2em;
+ vertical-align: bottom;
+}
+
+thead button {
+ width: 100%;
+ border: none;
+ text-align: inherit;
+ font: inherit;
+ background: none;
+ cursor: pointer;
+ font-size: .9em;
+ color: #999;
+ padding: .4em;
+}
+
+thead .selected button::after {
+ content: "🠇";
+ margin-left: .5em;
+}
+
+thead .selected.desc button::after {
+ content: "🠅";
+}
+
+td.name, tbody th, td.date {
text-align: left;
- border: 2px solid var(--g2-color);
}
-th {
+tbody tr:has(.check input:checked) {
+ background: #fdefdc;
+}
+
+tbody th, tbody td {
+ padding: 0 .5em;
+ text-align: left;
+ border-top: 1px solid var(--g2-color);
+ border-bottom: 1px solid var(--g2-color);
+ vertical-align: middle;
+}
+
+tbody th {
word-break: break-all;
+ font-weight: normal;
}
-td.thumb {
- width: 3.6em;
- padding: 0;
- text-align: center;
+tbody th a {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 1em;
+}
+
+tbody th img {
+ width: 2em;
+ height: 2em;
+ border-radius: .2em;
+}
+
+tbody th .icon {
+ width: 2em;
+ height: 2em;
+ border-radius: .2em;
+ overflow: hidden;
+ background: no-repeat center center;
+ background-size: 100%;
position: relative;
}
-td.thumb img {
- width: 3.6em;
- height: 3.6em;
- display: block;
-}
-
-td.thumb .icon {
- margin: .5em;
-}
-
-td.thumb input, td.thumb label span::before {
+th .icon b {
+ font-size: 7pt;
+ font-weight: normal;
+ text-decoration: none;
+ display: inline-block;
+ background: #eee;
+ border-radius: .3em;
+ padding: 0 .2em;
+ color: #2e3c3a;
position: absolute;
- top: 0;
- left: 0;
- right: 0;
bottom: 0;
- margin: 0;
- padding: 0;
- opacity: 0;
- display: block;
+ right: 0;
+ border: 1px solid #2e3c3a;
+}
+
+td.check {
+ width: 1.5em;
+ text-align: center;
+}
+
+td.check input {
cursor: pointer;
- user-select: none;
+ width: 1.5em;
+ height: 1.5em;
+ border: 2px solid #999;
+ border-radius: .2em;
+ background: none;
+ appearance: none;
+ outline: 0;
}
-td.thumb input:checked + span::before {
- content: "✔️";
- font-size: 3em;
- opacity: 1;
- color: #fff;
- text-shadow: 0px 0px 5px #000, 0px 0px 10px #000;
- display: flex;
- align-items: center;
- justify-content: center;
+td.check input:checked {
+ box-shadow: 0px 0px 5px darkorange;
+ background: no-repeat center center;
+ background-image: url('data:image/svg+xml;utf8,
');
+ background-size: contain;
}
-
td.buttons {
text-align: right;
- width: 20em;
+ width: 5em;
}
td.buttons div {
@@ -3420,60 +3531,65 @@ td.buttons div {
flex-direction: row-reverse;
}
-table tr:nth-child(even) {
+tbody tr:nth-child(even) {
background: var(--g1-color);
}
-.icon {
- width: 2.6em;
- height: 2.6em;
- display: inline-block;
- border-radius: .2em;
- background:var(--g3-color);
- overflow: hidden;
- color: var(--bg-color);
- text-align: center;
+tbody th .icon {
+ background-image: url('data:image/svg+xml;utf8,
');
}
-.icon b {
- font-weight: normal;
- display: inline-block;
- transform: rotate(-30deg);
- line-height: 2.6rem;
+th .icon.JPEG, th .icon.PNG, th .icon.JPG, th .icon.GIF, th .icon.SVG, th .icon.WEBP {
+ background-image: url('data:image/svg+xml;utf8,
');
}
-.icon.JPEG, .icon.PNG, .icon.JPG, .icon.GIF, .icon.SVG, .icon.WEBP {
- background: #966;
+th .icon.TXT, th .icon.MD {
+ background-image: url('data:image/svg+xml;utf8,
');
}
-.icon.TXT, .icon.MD {
- background: var(--fg-color);
+th .icon.MP4, th .icon.MKV, th .icon.MP3, th .icon.M4A, th .icon.WAV, th .icon.FLAC, th .icon.OGG, th .icon.OGV, th .icon.AAC, th .icon.WEBM {
+ background-image: url('data:image/svg+xml;utf8,
');
}
-.icon.MP4, .icon.MKV, .icon.MP3, .icon.M4A, .icon.WAV, .icon.FLAC, .icon.OGG, .icon.OGV, .icon.AAC, .icon.WEBM {
- background: #669;
+th .icon.document {
+ background-image: url('data:image/svg+xml;utf8,
');
}
-.icon.document {
- background: #696;
+th .icon.document b {
+ position: relative;
+ background: none;
+ border: none;
+ color: #fff;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
}
-.icon.PDF {
- background: #969;
+.dir th .icon {
+ background-image: url('data:image/svg+xml;utf8,
');
}
-.icon.dir {
- background: var(--g2-color);
- color: var(--fg-color);
+.parent th .icon {
+ background-image: url('data:image/svg+xml;utf8,
');
}
-.icon.dir b {
- font-size: 2em;
- transform: none;
+.dir th .icon b, .parent th .icon b {
+ display: none;
}
-.size {
+td.size {
text-align: right;
+ font-size: .9em;
+ color: #666;
+}
+
+td.date {
+ font-size: .9em;
+ color: #666;
+ min-width: 9em;
}
input[type=button], input[type=submit], .btn, a.btn {
@@ -3511,7 +3627,7 @@ input:focus, textarea:focus {
input[type=button]:hover, input[type=submit]:hover, .btn:hover {
color: var(--active-color);
text-decoration: underline;
- background: var(--bg-color);
+ background-color: var(--bg-color);
box-shadow: 0px 0px 5px var(--fg-color);
}
@@ -3571,8 +3687,31 @@ dialog form div {
text-align: center;
}
-.upload {
- margin: 1em 0;
+div.buttons {
+ display: flex;
+ justify-content: flex-end;
+ margin: .5em;
+ margin-bottom: 0;
+}
+
+div.buttons .selected {
+ margin-right: auto;
+ display: none;
+}
+
+body:has(tbody .check input:checked) div.buttons .selected {
+ display: block;
+}
+
+div.buttons input {
+ overflow: visible;
+ text-indent: 0;
+ width: auto;
+ height: auto;
+ background-position: .2em center;
+ padding: .3em .5em;
+ padding-left: 2em;
+ background-size: 1.3em;
}
#mdp div, #mdp textarea {
@@ -3584,7 +3723,7 @@ dialog form div {
margin: 0;
}
-#md {
+#mdp .md_preview {
overflow: hidden;
overflow-x: auto;
}
@@ -3594,10 +3733,10 @@ dialog form div {
grid-template-columns: 1fr 1fr;
grid-gap: .2em;
background: var(--g1-color);
- height: 82vh;
+ height: 90vh;
}
-#md img, #md video, #md iframe, #md embed, #md object {
+.md_preview img, .md_preview video, .md_preview iframe, .md_preview embed, .md_preview object {
max-width: 100%;
}
@@ -3614,7 +3753,7 @@ dialog.preview {
overflow: hidden;
}
-iframe, .md_preview {
+iframe, dialog.preview .md_preview {
overflow: auto;
position: absolute;
top: 0;
@@ -3655,11 +3794,21 @@ iframe, iframe body, .md_preview {
max-height: 100%;
}
-.md_preview {
+dialog.preview .md_preview {
width: calc(100vw - 2em);
- height: calc(100vh - 2em);
+ height: calc(100vh - 4em);
padding: 1em;
text-align: left;
+ padding-bottom: 2em;
+}
+
+.md_preview ul, .md_preview ol, .md_preview p, .md_preview blockquote, .md_preview pre,
+.md_preview h1, .md_preview h2, .md_preview h3, .md_preview h4, .md_preview h5, .md_preview h6 {
+ margin: 1rem 0;
+}
+
+.md_preview ul, .md_preview ol, .md_preview blockquote {
+ margin-left: 2em;
}
.preview .close {
@@ -3748,6 +3897,19 @@ input[name=rename], input[name=paste_name] {
dialog {
transition: all .3s;
+ margin: auto;
+ padding: 1em;
+}
+
+dialog:has(#mdp) {
+ margin: 0;
+ padding: .5em;
+ border-radius: none;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 100vh;
}
progress {
@@ -3755,6 +3917,45 @@ progress {
width: 90%;
}
+input.icon, .buttons a[download] {
+ background: #e0e6e6 no-repeat center center;
+ background-size: contain;
+ width: 1.8em;
+ height: 1.8em;
+ padding: 0;
+ overflow: hidden;
+ text-indent: 100em;
+ display: inline-block;
+}
+
+input.delete {
+ background-image: url('data:image/svg+xml;utf8,
');
+}
+
+input.rename {
+ background-image: url('data:image/svg+xml;utf8,
');
+}
+
+.buttons a[download], input.download {
+ background-image: url('data:image/svg+xml;utf8,
');
+}
+
+input.edit {
+ background-image: url('data:image/svg+xml;utf8,
');
+}
+
+input.mkdir {
+ background-image: url('data:image/svg+xml;utf8,
');
+}
+
+input.mkfile {
+ background-image: url('data:image/svg+xml;utf8,
');
+}
+
+input.upload {
+ background-image: url('data:image/svg+xml;utf8,
');
+}
+
@keyframes spin { to { transform: rotate(360deg); } }
@media screen and (max-width: 800px) {
@@ -3767,74 +3968,99 @@ progress {
body {
margin: 0;
- font-size: 1em;
+ }
+
+ div.buttons {
+ flex-wrap: wrap;
+ justify-content: center;
+ flex-direction: row;
+ font-size: .9em;
+ }
+
+ div.buttons .selected {
+ margin: 0;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 1em;
+ background: var(--bg-color);
+ box-shadow: 0px 0px 2px 5px var(--g3-color);
}
table {
- margin: 2em 0;
width: 100%;
display: flex;
flex-direction: column;
}
- table tr {
- display: block;
- border-top: 5px solid var(--bg-color);
+ table tbody tr {
+ display: grid;
+ grid-template-columns: 3rem 1fr .3fr;
+ grid-template-areas:
+ "name name check"
+ "size date buttons";
padding: 0;
- padding-left: 2em;
- position: relative;
- text-align: left;
- min-height: 2.5em;
}
- table td, table th {
+ table tbody td, table tbody th {
border: none;
- display: inline-block;
+ display: block;
padding: .2em .5em;
+ width: unset;
}
- table td.buttons {
- display: block;
- width: auto;
- text-align: left;
+ table tbody th {
+ grid-area: name;
}
- td.buttons div {
- display: inline-block;
- }
-
- table td.thumb {
- padding: 0;
- width: 2em;
- position: absolute;
- left: 0;
- top: 0;
- bottom: 0;
+ table tbody td.check {
+ grid-area: check;
+ text-align: right;
+ width: unset;
}
- table th {
- display: block;
+ table tbody td.buttons {
+ grid-area: buttons;
+ width: unset;
}
- .icon {
- font-size: 12px;
- height: 100%;
- border-radius: 0;
+ table tbody td.date {
+ grid-area: date;
+ display: inline-block;
+ min-width: unset;
}
- .icon:not(.dir) b {
- line-height: 3em;
- display: block;
- transform: translateX(-50%) translateY(-50%) rotate(-90deg);
- font-size: 2em;
- height: 3em;
- position: absolute;
- top: 50%;
- left: 50%;
+ table tbody td.size {
+ grid-area: size;
+ font-size: .8em;
+ width: 3rem;
+ overflow: hidden;
+ text-align: center;
}
table th a {
font-size: 1.2em;
+ gap: .5em;
+ }
+
+ table thead tr {
+ display: block;
+ text-align: left;
+ }
+
+ table thead tr td {
+ display: inline-block;
+ vertical-align: top;
+ }
+
+ table thead td.check {
+ float: right;
+ margin-right: .4em;
+ }
+
+ table thead button {
+ font-size: 1rem;
}
input[name=rename], input[name=paste_name] {