var css_url = document.currentScript.src.replace(/\/[^\/]+$/, '') + '/webdav.css'; const WebDAVNavigator = (url, options) => { // Microdown // https://github.com/commit-intl/micro-down const microdown=function(){function l(n,e,r){return"<"+n+(r?" "+Object.keys(r).map(function(n){return r[n]?n+'="'+(a(r[n])||"")+'"':""}).join(" "):"")+">"+e+""+n+">"}function c(n,e){return e=n.match(/^[+-]/m)?"ul":"ol",n?"<"+e+">"+n.replace(/(?:[+-]|\d+\.) +(.*)\n?(([ \t].*\n?)*)/g,function(n,e,r){return"
/ ${formatBytes(size)}
`, false); await get_url(url); const a = document.createElement('a'); a.style.display = 'none'; a.href = temp_object_url; a.download = name; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(temp_object_url); a.remove(); closeDialog(); window.onbeforeunload = null; }; const download_all = async () => { for (var i = 0; i < items.length; i++) { var item = items[i]; if (item.is_dir) { continue; } await download(item.name, item.size, item.uri) } }; const preview = (type, url) => { if (type.match(/^image\//)) { openDialog(``, false); } else if (type.match(/^audio\//)) { openDialog(``, false); } else if (type.match(/^video\//)) { openDialog(``, false); } else { openDialog(``, false); } $('dialog').className = 'preview'; }; const $ = (a) => document.querySelector(a); const formatBytes = (bytes) => { const unit = _('B'); if (bytes >= 1024*1024*1024) { return Math.round(bytes / (1024*1024*1024)) + ' G' + unit; } else if (bytes >= 1024*1024) { return Math.round(bytes / (1024*1024)) + ' M' + unit; } else if (bytes >= 1024) { return Math.round(bytes / 1024) + ' K' + unit; } else { return bytes + ' ' + unit; } }; const formatDate = (date) => { var now = new Date; var nb_hours = (+(now) - +(date)) / 3600 / 1000; if (date.getFullYear() == now.getFullYear() && date.getMonth() == now.getMonth() && date.getDate() == now.getDate()) { if (nb_hours <= 1) { return _('%d minutes ago').replace(/%d/, Math.round(nb_hours * 60)); } else { return _('%d hours ago').replace(/%d/, Math.round(nb_hours)); } } else if (nb_hours <= 24) { return _('Yesterday, %s').replace(/%s/, date.toLocaleTimeString()); } return date.toLocaleString(); }; const openListing = (uri, push) => { closeDialog(); reqXML('PROPFIND', uri, propfind_tpl, {'Depth': 1}).then((xml) => { buildListing(uri, xml) current_url = uri; changeURL(uri, push); }).catch((e) => { console.error(e); alert(e); }); }; const reloadListing = () => { stopLoading(); openListing(current_url, false); }; const normalizeURL = (url) => { if (!url.match(/^https?:\/\//)) { url = base_url.replace(/^(https?:\/\/[^\/]+\/).*$/, '$1') + url.replace(/^\/+/, ''); } return url; }; const changeURL = (uri, push) => { try { if (push) { history.pushState(1, null, uri); } else { history.replaceState(1, null, uri); } if (popstate_evt) return; popstate_evt = window.addEventListener('popstate', (e) => { var url = location.pathname; openListing(url, false); }); } catch (e) { // If using a HTML page on another origin location.hash = uri; } }; const animateLoading = () => { document.body.classList.add('loading'); }; const stopLoading = () => { document.body.classList.remove('loading'); }; const buildListing = (uri, xml) => { uri = normalizeURL(uri); items = [[], []]; var title = null; var root_permissions = null; xml.querySelectorAll('response').forEach((node) => { var item_uri = normalizeURL(node.querySelector('href').textContent); var props = null; node.querySelectorAll('propstat').forEach((propstat) => { if (propstat.querySelector('status').textContent.match(/200/)) { props = propstat; } }); // This item didn't return any properties, everything is 404? if (!props) { console.error('Cannot find properties for: ' + item_uri); return; } var name = item_uri.replace(/\/$/, '').split('/').pop(); name = decodeURIComponent(name); var permissions = (prop = node.querySelector('permissions')) ? prop.textContent : null; if (item_uri == uri) { title = name; root_permissions = permissions; return; } var is_dir = node.querySelector('resourcetype collection') ? true : false; var index = sort_order == 'name' && is_dir ? 0 : 1; items[index].push({ 'uri': item_uri, 'name': name, 'size': !is_dir && (prop = node.querySelector('getcontentlength')) ? parseInt(prop.textContent, 10) : null, 'mime': !is_dir && (prop = node.querySelector('getcontenttype')) ? prop.textContent : null, 'modified': (prop = node.querySelector('getlastmodified')) ? new Date(prop.textContent) : null, 'is_dir': is_dir, 'permissions': permissions, }); }); 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; } else if (sort_order == 'size') { return b.size - a.size; } else { return a.name.localeCompare(b.name); } }); if (sort_order == 'name') { // Sort with directories first items = items[0].concat(items[1]); } else { items = items[1]; } 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': '↲'}); } else { title = 'My files'; } items.forEach(item => { // Don't include files we cannot read if (item.permissions !== null && item.permissions.indexOf('G') == -1) { console.error('OC permissions deny read access to this file: ' + item.name, 'Permissions: ', item.permissions); return; } 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() : ''); item.modified = item.modified !== null ? formatDate(item.modified) : null; item.name = html(item.name); table += template(row, item); }); 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); reloadListing(); }; if (!items.length) { $('.download_all').disabled = true; } else { $('.download_all').onclick = download_all; } if (!root_permissions || root_permissions.indexOf('CK') != -1) { $('.upload').insertAdjacentHTML('afterbegin', create_buttons); $('.mkdir').onclick = () => { openDialog(mkdir_dialog); document.forms[0].onsubmit = () => { var name = $('input[name=mkdir]').value; if (!name) return false; name = encodeURIComponent(name); req('MKCOL', current_url + name).then(() => openListing(current_url + name + '/')); return false; }; }; $('.mkfile').onclick = () => { openDialog(mkfile_dialog); var t = $('input[name=mkfile]'); t.value = '.md'; t.focus(); t.selectionStart = t.selectionEnd = 0; document.forms[0].onsubmit = () => { var name = t.value; if (!name) return false; name = encodeURIComponent(name); return reqAndReload('PUT', current_url + name, ''); }; }; var fi = $('input[type=file]'); $('.uploadfile').onclick = () => fi.click(); fi.onchange = () => { if (!fi.files.length) return; var body = new Blob(fi.files); var name = fi.files[0].name; name = encodeURIComponent(name); return reqAndReload('PUT', current_url + name, body); }; } Array.from($('table').rows).forEach((tr) => { var $$ = (a) => tr.querySelector(a); var file_url = $$('a').href; var file_name = $$('a').innerText; var dir = $$('[colspan]'); var mime = !dir ? tr.getAttribute('data-mime') : 'dir'; var buttons = $$('td.buttons div'); var permissions = tr.getAttribute('data-permissions'); var size = tr.getAttribute('data-size'); if (permissions == 'null') { permissions = null; } if (dir) { $$('a').onclick = () => { openListing(file_url, true); return false; }; } // For back link if (dir && $$('a').getAttribute('href').length < uri.length) { dir.setAttribute('colspan', 4); tr.querySelector('td:last-child').remove(); tr.querySelector('td:last-child').remove(); return; } // This is to get around CORS when not on the same domain if (user && password && (a = tr.querySelector('a[download]'))) { a.onclick = () => { download(file_name, size, url); return false; }; } // Add rename/delete buttons if (!permissions || permissions.indexOf('NV') != -1) { buttons.insertAdjacentHTML('afterbegin', rename_button); $$('.rename').onclick = () => { openDialog(rename_dialog); let t = $('input[name=rename]'); t.value = file_name; t.focus(); t.selectionStart = 0; t.selectionEnd = file_name.lastIndexOf('.'); document.forms[0].onsubmit = () => { var name = t.value; if (!name) return false; name = encodeURIComponent(name); name = name.replace(/%2F/, '/'); var dest = current_url + name; dest = normalizeURL(dest); return reqAndReload('MOVE', file_url, '', {'Destination': dest}); }; }; } if (!permissions || permissions.indexOf('D') != -1) { buttons.insertAdjacentHTML('afterbegin', delete_button); $$('.delete').onclick = (e) => { openDialog(delete_dialog); document.forms[0].onsubmit = () => { return reqAndReload('DELETE', file_url); }; }; } var view_url, edit_url; // Don't preview PDF in mobile if (mime.match(PREVIEW_TYPES) && !(mime == 'application/pdf' && window.navigator.userAgent.match(/Mobi|Tablet|Android|iPad|iPhone/))) { $$('a').onclick = () => { if (file_url.match(/\.md$/)) { openDialog('', false); $('dialog').className = 'preview'; req('GET', file_url).then(r => r.text()).then(t => { $('.md_preview').innerHTML = microdown.parse(html(t)); }); return false; } if (user && password) { (async () => { preview(mime, await get_url(file_url)); })(); } else { preview(mime, file_url); } return false; }; } else if (view_url = wopi_getViewURL(file_url, mime)) { $$('.icon').classList.add('document'); $$('a').onclick = () => { wopi_open(file_url, view_url); return false; }; } else if (user && password && !dir) { $$('a').onclick = () => { download(file_name, size, file_url); return false; }; } else { $$('a').download = file_name; } if (!permissions || permissions.indexOf('W') != -1) { if (mime.match(/^text\/|application\/x-empty/)) { buttons.insertAdjacentHTML('beforeend', edit_button); $$('.edit').onclick = (e) => { req('GET', file_url).then((r) => r.text().then((t) => { let md = file_url.match(/\.md$/); openDialog(md ? markdown_dialog : edit_dialog); var txt = $('textarea[name=edit]'); txt.value = t; // Markdown editor if (md) { let pre = $('#md'); txt.oninput = () => { pre.innerHTML = microdown.parse(html(txt.value)); }; txt.oninput(); // Sync scroll, not perfect but better than nothing txt.onscroll = (e) => { var p = e.target.scrollTop / (e.target.scrollHeight - e.target.offsetHeight); var target = e.target == pre ? txt : pre; target.scrollTop = p * (target.scrollHeight - target.offsetHeight); e.preventDefault(); return false; }; } document.forms[0].onsubmit = () => { var content = txt.value; return reqAndReload('PUT', file_url, content); }; })); }; } else if (edit_url = wopi_getEditURL(file_url, mime)) { buttons.insertAdjacentHTML('beforeend', edit_button); $$('.icon').classList.add('document'); $$('.edit').onclick = () => { wopi_open(file_url, edit_url); return false; }; } } }); }; var items = [[], []]; var current_xhr = null; var current_url = url; var base_url = url; const user = options.user || null; const password = options.password || null; var auth_header = (user && password) ? 'Basic ' + btoa(user + ':' + password) : null; if (location.pathname.indexOf(base_url) === 0) { current_url = location.pathname; } if (!base_url.match(/^https?:/)) { base_url = location.href.replace(/^(https?:\/\/[^\/]+\/).*$/, '$1') + base_url.replace(/^\/+/, ''); } var evt, paste_upload, popstate_evt, temp_object_url; var sort_order = window.localStorage.getItem('sort_order') || 'name'; var wopi_mimes = {}, wopi_extensions = {}; const wopi_discovery_url = options.wopi_discovery_url || null; document.querySelector('html').innerHTML = html_tpl; if (wopi_discovery_url) { // Wait for WOPI discovery before creating the list wopi_init(); } else { reloadListing(); } window.addEventListener('paste', (e) => { let items = e.clipboardData.items; const IMAGE_MIME_REGEX = /^image\/(p?jpeg|gif|png)$/i; for (var i = 0; i < items.length; i++) { if (items[i].kind === 'file' || IMAGE_MIME_REGEX.test(items[i].type)) { e.preventDefault(); let f = items[i].getAsFile(); let name = f.name == 'image.png' ? f.name.replace(/\./, '-' + (+(new Date)) + '.') : f.name; paste_upload = f; openDialog(paste_upload_dialog); let t = $('input[name=paste_name]'); t.value = name; t.focus(); t.selectionStart = 0; t.selectionEnd = name.lastIndexOf('.'); document.forms[0].onsubmit = () => { name = encodeURIComponent(t.value); return reqAndReload('PUT', current_url + name, paste_upload); }; return; } } }); var dragcounter = 0; window.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); }); window.addEventListener('dragenter', (e) => { e.preventDefault(); e.stopPropagation(); if (!dragcounter) { document.body.classList.add('dragging'); } dragcounter++; }); window.addEventListener('dragleave', (e) => { e.preventDefault(); e.stopPropagation(); dragcounter--; if (!dragcounter) { document.body.classList.remove('dragging'); } }); window.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); document.body.classList.remove('dragging'); dragcounter = 0; const files = [...e.dataTransfer.items].map(item => item.getAsFile()); if (!files.length) return; animateLoading(); (async () => { for (var i = 0; i < files.length; i++) { var f = files[i] await req('PUT', current_url + encodeURIComponent(f.name), f); } window.setTimeout(() => { stopLoading(); reloadListing(); }, 500); })(); }); }; if (url = document.querySelector('html').getAttribute('data-webdav-url')) { WebDAVNavigator(url, { 'wopi_discovery_url': document.querySelector('html').getAttribute('data-wopi-discovery-url'), }); }