diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3ec7987
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.gitignore
+data/
+www/webdav.css
+www/webdav.js
+config.local.php
+error.log
+debug.log
+lib/KD2/
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 7ba82c4..34a9342 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,8 @@
-deps:
+js-deps:
+ wget -O www/webdav.js https://raw.githubusercontent.com/kd2org/webdav-manager.js/main/webdav.js
+ wget -O www/webdav.css https://raw.githubusercontent.com/kd2org/webdav-manager.js/main/webdav.css
+
+php-deps:
wget -O lib/KD2/WebDAV.php 'https://fossil.kd2.org/kd2fw/doc/tip/src/lib/KD2/WebDAV.php'
wget -O lib/KD2/WebDAV_NextCloud.php 'https://fossil.kd2.org/kd2fw/doc/tip/src/lib/KD2/WebDAV_NextCloud.php'
diff --git a/README.md b/README.md
index 3b48ac4..c3366cf 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,8 @@ Although this is a demo, this can be used as a simple but powerful file sharing
This server features:
-* User-friendly directory listings for file browsing with a web browser:
- * Upload directly from browser
+* User-friendly directory listings for file browsing with a web browser, using [WebDAV Manager.js](https://github.com/kd2org/webdav-manager.js)
+ * Upload directly from browser, using paste, or drag and drop
* Rename
* Delete
* Create and edit text file
diff --git a/lib/KaraDAV/Server.php b/lib/KaraDAV/Server.php
index e72a0b3..0d20b04 100644
--- a/lib/KaraDAV/Server.php
+++ b/lib/KaraDAV/Server.php
@@ -17,6 +17,15 @@ class Server
public function route(?string $uri = null): bool
{
+ header('Access-Control-Allow-Origin: *', true);
+ $method = $_SERVER['REQUEST_METHOD'] ?? null;
+
+ // Always say YES to OPTIONS
+ if ($method == 'OPTIONS') {
+ $this->dav->http_options();
+ return true;
+ }
+
$nc = new NextCloud($this->dav, $this->users);
if ($r = $nc->route($uri)) {
diff --git a/lib/KaraDAV/WebDAV.php b/lib/KaraDAV/WebDAV.php
index 637fea7..6de2d45 100644
--- a/lib/KaraDAV/WebDAV.php
+++ b/lib/KaraDAV/WebDAV.php
@@ -6,15 +6,24 @@ use KD2\WebDAV\Server as WebDAV_Server;
class WebDAV extends WebDAV_Server
{
- protected function html_directory(string $uri, iterable $list, array $strings = self::LANGUAGE_STRINGS): ?string
+ protected function html_directory(string $uri, iterable $list): ?string
{
- $out = parent::html_directory($uri, $list, $strings);
+ $out = parent::html_directory($uri, $list);
if (null !== $out) {
- $out = str_replace('', sprintf('', WWW_URL), $out);
- $out = str_replace('
', sprintf('', WWW_URL), $out);
}
return $out;
}
+
+ public function http_options(): void
+ {
+ parent::http_options();
+
+ header('Access-Control-Allow-Origin: *');
+ header('Access-Control-Allow-Credentials: true');
+ header('Access-Control-Allow-Headers: Authorization, *');
+ header('Access-Control-Allow-Methods: GET,HEAD,PUT,DELETE,COPY,MOVE,PROPFIND,MKCOL,LOCK,UNLOCK');
+ }
}
diff --git a/www/files.css b/www/files.css
deleted file mode 100644
index fe7e730..0000000
--- a/www/files.css
+++ /dev/null
@@ -1,173 +0,0 @@
-body {
- text-align: center;
-}
-
-table {
- margin: 2em auto;
-}
-
-table tr:nth-child(even) {
- background: #eee;
-}
-
-input[type=button], input[type=submit] {
- font-size: 1.2em;
- padding: .3em .5em;
- margin: 0 .3em;
- border: none;
- background: #ccc;
- border-radius: .2em;
- cursor: pointer;
-}
-
-td input[type=button], td input[type=submit] {
- font-size: 1em;
-}
-
-input[type=text], textarea {
- font-size: 1.2em;
- padding: .3em .5em;
- border: none;
- background: #fff;
- border-radius: .2em;
- width: calc(100% - 1em);
-}
-
-input:focus, textarea:focus {
- box-shadow: 0px 0px 5px blue;
- outline: 1px solid #999;
-}
-
-input[type=button]:hover, input[type=submit]:hover {
- color: darkred;
- text-decoration: underline;
- background: #fff;
- box-shadow: 0px 0px 5px #000;
-}
-
-.close {
- text-align: right;
- margin: 0;
-}
-
-.close input {
- font-size: .8em;
-}
-
-input[type=submit] {
- float: right;
-}
-
-dialog {
- position: absolute;
- top: 1em;
- right: 1em;
- bottom: 1em;
- left: 1em;
- box-shadow: 0px 0px 5px #000;
- background: #eee;
- border: none;
- border-radius: .5em;
-}
-
-dialog form div {
- clear: both;
- margin: 2em 0;
- text-align: center;
-}
-
-.a {
- margin: 1em 0;
-}
-
-#mdp div, #mdp textarea {
- width: calc(100% - 1em);
- padding: .5em;
- font-size: 1em;
- height: calc(100% - 1em);
- text-align: left;
- margin: 0;
-}
-
-#md {
- overflow: hidden;
- overflow-x: auto;
-}
-
-#mdp {
- display: grid;
- grid-template-columns: 1fr 1fr;
- grid-gap: .2em;
- background: #ddd;
- height: 82vh;
-}
-
-dialog.preview {
- height: calc(100%);
- width: calc(100%);
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- padding: 0;
- border-radius: 0;
- background: #ddd;
- overflow: hidden;
-}
-
-iframe, .md_preview {
- overflow: auto;
- position: absolute;
- top: 2em;
- left: 0;
- width: calc(100vw);
- height: calc(100% - 2em);
- padding: 0;
- margin: 0;
- border: none;
-}
-
-.preview form {
- height: calc(100% - 2em);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.preview form > div {
- height: 100%;
- margin: 0;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.md_preview {
- width: calc(100vw - 2em);
- height: calc(100vh - 2em);
- padding: 1em;
- text-align: left;
-}
-
-.preview .close {
- height: 2em;
- text-align: center;
- font-size: 1em;
- display: block;
- width: 100%;
- margin: 0;
- padding: 0;
- border-radius: 0;
- background: #fff;
- color: #000;
- box-shadow: 0px 0px 5px #000;
-}
-
-.preview img {
- max-width: 95%;
- max-height: 95%;
-}
-
-input[name=rename] {
- width: 30em;
-}
\ No newline at end of file
diff --git a/www/files.js b/www/files.js
deleted file mode 100644
index 229b7ab..0000000
--- a/www/files.js
+++ /dev/null
@@ -1,218 +0,0 @@
-// 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""+g(e+"\n"+(t=r||"").replace(new RegExp("^"+(t.match(/^\s+/)||"")[0],"gm"),"").replace(o,c))+"";var t})+""+e+">":""}function e(r,t,u,c){return function(n,e){return n=n.replace(t,u),l(r,c?c(n):n)}}function t(n,u){return f(n,[//g,"\x3c!--$1--\x3e",/^("""|```)(.*)\n((.*\n)*?)\1/gm,function(n,e,r,t){return'"""'===e?l("div",p(t,u),{class:r}):u&&u.preCode?l("pre",l("code",a(t),{class:r})):l("pre",a(t),{class:r})},/(^>.*\n?)+/gm,e("blockquote",/^> ?(.*)$/gm,"$1",r),/((^|\n)\|.+)+/g,e("table",/^.*(\n\|---.*?)?$/gm,function(n,t){return e("tr",/\|(-?)([^|]*)\1(\|$)?/gm,function(n,e,r){return l(e||t?"th":"td",g(r))})(n.slice(0,n.length-(t||"").length))}),o,c,/#\[([^\]]+?)]/g,'',/^(#+) +(.*)(?:$)/gm,function(n,e,r){return l("h"+e.length,g(r))},/^(===+|---+)(?=\s*$)/gm,"
"],p,u)}var i=this,a=function(n){return n?n.replace(/"/g,""").replace(//g,">"):""},o=/(?:(^|\n)([+-]|\d+\.) +(.*(\n[ \t]+.*)*))+/g,g=function c(n,i){var o=[];return n=(n||"").trim().replace(/`([^`]*)`/g,function(n,e){return"\\"+o.push(l("code",a(e)))}).replace(/[!&]?\[([!&]?\[.*?\)|[^\]]*?)]\((.*?)( .*?)?\)|(\w+:\/\/[$\-.+!*'()/,\w]+)/g,function(n,e,r,t,u){return u?i?n:"\\"+o.push(l("a",u,{href:u})):"&"==n[0]?(e=e.match(/^(.+),(.+),([^ \]]+)( ?.+?)?$/),"\\"+o.push(l("iframe","",{width:e[1],height:e[2],frameborder:e[3],class:e[4],src:r,title:t}))):"\\"+o.push("!"==n[0]?l("img","",{src:r,alt:e,title:t}):l("a",c(e,1),{href:r,title:t}))}),n=function r(n){return n.replace(/\\(\d+)/g,function(n,e){return r(o[Number.parseInt(e)-1])})}(i?n:r(n))},r=function t(n){return f(n,[/([*_]{1,3})((.|\n)+?)\1/g,function(n,e,r){return e=e.length,r=t(r),1"],t)},f=function(n,e,r,t){for(var u,c=0;c/g, ">")
- .replace(/"/g, """)
- .replace(/'/g, "'");
-}
-
-const url = location.pathname;
-
-const PREVIEW_TYPES = /^image\/(png|webp|svg|jpeg|jpg|gif|png)|^application\/pdf|^text\/|^audio\/|^video\//;
-
-const upload_form = `
-
-
-
-
-
`;
-
-const file_buttons = `
- | `;
-
-const edit_button = ``;
-
-const mkdir_dialog = ``;
-const mkfile_dialog = ``;
-const rename_dialog = ``;
-const edit_dialog = ``;
-const markdown_dialog = ``;
-const delete_dialog = `Confirm delete ?
`;
-
-const dialog_tpl = ``;
-
-const req = (method, url, body, headers) => {
- fetch(url, {method, body, headers}).then((r) => location.reload());
- return false;
-};
-
-var evt, evt2;
-
-const dialog = (hash, html, ok_btn = true) => {
- location.hash = '#' + hash;
- var tpl = dialog_tpl.replace(/%b/, ok_btn ? '' : '');
- $('body').insertAdjacentHTML('beforeend', tpl.replace(/%s/, html));
- $('.close input').onclick = close;
- evt = window.addEventListener('keyup', (e) => {
- if (e.key != 'Escape') return;
- e.preventDefault();
- close();
- return false;
- });
- evt2 = window.addEventListener('hashchange', (e) => {
- if (location.hash == '') {
- close();
- }
- });
- if (a = $('dialog form input, dialog form textarea')) a.focus();
-};
-
-const close = () => {
- if (!$('dialog')) return;
- $('dialog').remove();
- window.removeEventListener('keyup', evt);
- evt = null;
- window.removeEventListener('hashchange', evt2);
- evt2 = null;
- location.hash = '';
-};
-
-const $ = (a) => document.querySelector(a);
-
-$('h1').insertAdjacentHTML('afterend', upload_form);
-
-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 type = !dir ? $$('td:nth-child(3)').innerText : 'dir';
-
- // For back link
- if (dir && $$('a').getAttribute('href').indexOf('..') != -1) {
- dir.setAttribute('colspan', 4);
- return;
- }
-
- // Add rename/delete buttons
- tr.insertAdjacentHTML('beforeend', file_buttons);
-
- if (type.match(PREVIEW_TYPES)) {
- $$('a').onclick = () => {
- if (file_url.match(/\.md$/)) {
- dialog(file_name, '', false);
- fetch(file_url).then((r) => r.text().then((t) => {
- $('.md_preview').innerHTML = microdown.parse(html(t));
- }));
- }
- else if (type.match(/^image\//)) {
- dialog(file_name, ``, false);
- }
- else if (type.match(/^audio\//)) {
- dialog(file_name, ``, false);
- }
- else if (type.match(/^video\//)) {
- dialog(file_name, ``, false);
- }
- else {
- dialog(file_name, ``, false);
- }
- $('dialog').className = 'preview';
- return false;
- };
- }
-
- if (type.match(/^text\/|application\/x-empty/)) {
- $$('td:nth-child(5)').insertAdjacentHTML('beforeend', edit_button);
-
- $$('.edit').onclick = (e) => {
- fetch(file_url).then((r) => r.text().then((t) => {
- let md = file_url.match(/\.md$/);
- dialog(file_name, 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 req('PUT', file_url, content);
- };
- }));
- };
- }
-
- $$('.delete').onclick = (e) => {
- dialog('delete', delete_dialog);
- document.forms[0].onsubmit = () => {
- return req('DELETE', file_url, '');
- };
- };
-
- $$('.rename').onclick = () => {
- dialog('rename', 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;
-
- return req('MOVE', file_url, '', {'Destination': location.pathname + name});
- };
- };
-
-});
-
-$('.mkdir').onclick = () => {
- dialog('mkdir', mkdir_dialog);
- document.forms[0].onsubmit = () => {
- var name = $('input[name=mkdir]').value;
-
- if (!name) return false;
-
- return req('MKCOL', url + name);
- };
-};
-
-$('.mkfile').onclick = () => {
- dialog('mkfile', mkfile_dialog);
- document.forms[0].onsubmit = () => {
- var name = $('input[name=mkfile]').value;
-
- if (!name) return false;
-
- return req('PUT', url + name, '');
- };
-};
-
-var fi = $('input[type=file]');
-
-$('.upload').onclick = () => fi.click();
-
-fi.onchange = () => {
- if (!fi.files.length) return;
-
- var body = new Blob(fi.files);
- var name = fi.files[0].name;
-
- return req('PUT', url + name, body);
-};