From 2bc6a20d711499c22b8bf91ce45316b78c78cb4f Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 30 Nov 2020 03:00:44 +0100 Subject: [PATCH] md: poll server for changes --- copyparty/__main__.py | 9 +- copyparty/httpcli.py | 1 + copyparty/web/browser.js | 6 +- copyparty/web/md.html | 3 +- copyparty/web/md.js | 18 ++-- copyparty/web/md2.css | 30 +++++-- copyparty/web/md2.js | 177 ++++++++++++++++++++++++++++++++------- copyparty/web/mde.html | 3 +- copyparty/web/mde.js | 12 +-- copyparty/web/up2k.js | 4 +- 10 files changed, 200 insertions(+), 63 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index a0d7251d..e346ac18 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -123,20 +123,17 @@ def main(): """ ), ) - ap.add_argument( - "-c", metavar="PATH", type=str, action="append", help="add config file" - ) + ap.add_argument("-c", metavar="PATH", type=str, action="append", help="add config file") ap.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind") ap.add_argument("-p", metavar="PORT", type=int, default=3923, help="port to bind") ap.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients") - ap.add_argument( - "-j", metavar="CORES", type=int, default=1, help="max num cpu cores" - ) + ap.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores") ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account") ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume") ap.add_argument("-q", action="store_true", help="quiet") ap.add_argument("-ed", action="store_true", help="enable ?dots") ap.add_argument("-emp", action="store_true", help="enable markdown plugins") + ap.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate") ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)") ap.add_argument("-nih", action="store_true", help="no info hostname") ap.add_argument("-nid", action="store_true", help="no info disk-usage") diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 46fd96c8..6b51b2e4 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -964,6 +964,7 @@ class HttpCli(object): "title": html_escape(self.vpath), "lastmod": int(ts_md * 1000), "md_plug": "true" if self.args.emp else "false", + "md_chk_rate": self.args.mcr, "md": "", } sz_html = len(template.render(**targs).encode("utf-8")) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index cdecea51..236f1408 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -27,7 +27,7 @@ var mp = (function () { }; var re_audio = new RegExp('\.(opus|ogg|m4a|aac|mp3|wav|flac)$', 'i'); - var trs = document.getElementById('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr'); + var trs = ebi('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr'); for (var a = 0, aa = trs.length; a < aa; a++) { var tds = trs[a].getElementsByTagName('td'); var link = tds[1].getElementsByTagName('a')[0]; @@ -70,8 +70,8 @@ var mp = (function () { // toggle player widget var widget = (function () { var ret = {}; - var widget = document.getElementById('widget'); - var wtoggle = document.getElementById('wtoggle'); + var widget = ebi('widget'); + var wtoggle = ebi('wtoggle'); var touchmode = false; var side_open = false; var was_paused = true; diff --git a/copyparty/web/md.html b/copyparty/web/md.html index 29498133..6bc43ba1 100644 --- a/copyparty/web/md.html +++ b/copyparty/web/md.html @@ -126,7 +126,8 @@ write markdown (most html is 🙆 too) var last_modified = {{ lastmod }}; var md_opt = { link_md_as_html: false, - allow_plugins: {{ md_plug }} + allow_plugins: {{ md_plug }}, + modpoll_freq: {{ md_chk_rate }} }; (function () { diff --git a/copyparty/web/md.js b/copyparty/web/md.js index d81c8aef..74d4b57e 100644 --- a/copyparty/web/md.js +++ b/copyparty/web/md.js @@ -1,12 +1,12 @@ "use strict"; -var dom_toc = document.getElementById('toc'); -var dom_wrap = document.getElementById('mw'); -var dom_hbar = document.getElementById('mh'); -var dom_nav = document.getElementById('mn'); -var dom_pre = document.getElementById('mp'); -var dom_src = document.getElementById('mt'); -var dom_navtgl = document.getElementById('navtoggle'); +var dom_toc = ebi('toc'); +var dom_wrap = ebi('mw'); +var dom_hbar = ebi('mh'); +var dom_nav = ebi('mn'); +var dom_pre = ebi('mp'); +var dom_src = ebi('mt'); +var dom_navtgl = ebi('navtoggle'); // chrome 49 needs this @@ -161,7 +161,7 @@ function copydom(src, dst, lv) { function md_plug_err(ex, js) { - var errbox = document.getElementById('md_errbox'); + var errbox = ebi('md_errbox'); if (errbox) errbox.parentNode.removeChild(errbox); @@ -367,7 +367,7 @@ function convert_markdown(md_text, dest_dom) { function init_toc() { - var loader = document.getElementById('ml'); + var loader = ebi('ml'); loader.parentNode.removeChild(loader); var anchors = []; // list of toc entries, complex objects diff --git a/copyparty/web/md2.css b/copyparty/web/md2.css index 1de6257d..655cf040 100644 --- a/copyparty/web/md2.css +++ b/copyparty/web/md2.css @@ -77,32 +77,52 @@ html.dark #mt { background: #f97; border-radius: .15em; } +html.dark #save.force-save { + color: #fca; + background: #720; +} #save.disabled { opacity: .4; } +#helpbox, +#toast { + background: #f7f7f7; + border-radius: .4em; + z-index: 9001; +} #helpbox { display: none; position: fixed; - background: #f7f7f7; - box-shadow: 0 .5em 2em #777; - border-radius: .4em; padding: 2em; top: 4em; overflow-y: auto; + box-shadow: 0 .5em 2em #777; height: calc(100% - 12em); left: calc(50% - 15em); right: 0; width: 30em; - z-index: 9001; } #helpclose { display: block; } html.dark #helpbox { - background: #222; box-shadow: 0 .5em 2em #444; +} +html.dark #helpbox, +html.dark #toast { + background: #222; border: 1px solid #079; border-width: 1px 0; } +#toast { + font-weight: bold; + text-align: center; + padding: .6em 0; + position: fixed; + z-index: 9001; + top: 30%; + transition: opacity 0.2s ease-in-out; + opacity: 1; +} # mt {opacity: .5;top:1px} diff --git a/copyparty/web/md2.js b/copyparty/web/md2.js index 0db410db..74dca04a 100644 --- a/copyparty/web/md2.js +++ b/copyparty/web/md2.js @@ -11,15 +11,15 @@ var js_uni_whitelist = eval('\'' + esc_uni_whitelist + '\''); // dom nodes -var dom_swrap = document.getElementById('mtw'); -var dom_sbs = document.getElementById('sbs'); -var dom_nsbs = document.getElementById('nsbs'); -var dom_tbox = document.getElementById('toolsbox'); +var dom_swrap = ebi('mtw'); +var dom_sbs = ebi('sbs'); +var dom_nsbs = ebi('nsbs'); +var dom_tbox = ebi('toolsbox'); var dom_ref = (function () { var d = document.createElement('div'); d.setAttribute('id', 'mtr'); dom_swrap.appendChild(d); - d = document.getElementById('mtr'); + d = ebi('mtr'); // hide behind the textarea (offsetTop is not computed if display:none) dom_src.style.zIndex = '4'; d.style.zIndex = '3'; @@ -108,7 +108,7 @@ var draw_md = (function () { map_src = genmap(dom_ref, map_src); map_pre = genmap(dom_pre, map_pre); - cls(document.getElementById('save'), 'disabled', src == server_md); + cls(ebi('save'), 'disabled', src == server_md); var t1 = new Date().getTime(); delay = t1 - t0 > 100 ? 25 : 1; @@ -223,14 +223,108 @@ redraw = (function () { })(); +// modification checker +function Modpoll() { + this.skip_one = true; + this.disabled = false; + + this.periodic = function () { + var that = this; + setTimeout(function () { + that.periodic(); + }, 1000 * md_opt.modpoll_freq); + + var skip = null; + + if (ebi('toast')) + skip = 'toast'; + + else if (this.skip_one) + skip = 'saved'; + + else if (this.disabled) + skip = 'disabled'; + + if (skip) { + console.log('modpoll skip, ' + skip); + this.skip_one = false; + return; + } + + console.log('modpoll...'); + var url = (document.location + '').split('?')[0] + '?raw&_=' + new Date().getTime(); + var xhr = new XMLHttpRequest(); + xhr.modpoll = this; + xhr.open('GET', url, true); + xhr.responseType = 'text'; + xhr.onreadystatechange = this.cb; + xhr.send(); + } + + this.cb = function () { + if (this.modpoll.disabled || this.modpoll.skip_one) { + console.log('modpoll abort'); + return; + } + + if (this.readyState != XMLHttpRequest.DONE) + return; + + if (this.status !== 200) { + console.log('modpoll err ' + this.status + ": " + this.responseText); + return; + } + + if (!this.responseText) + return; + + var server_ref = server_md.replace(/\r/g, ''); + var server_now = this.responseText.replace(/\r/g, ''); + + if (server_ref != server_now) { + console.log("modpoll diff |" + server_ref.length + "|, |" + server_now.length + "|"); + this.modpoll.disabled = true; + var msg = [ + "The document has changed on the server.
" + + "The changes will NOT be loaded into your editor automatically.", + + "Press F5 or CTRL-R to refresh the page,
" + + "replacing your document with the server copy.", + + "You can click this message to ignore and contnue." + ]; + return toast(false, "box-shadow:0 1em 2em rgba(64,64,64,0.8);font-weight:normal", + 36, "

" + msg.join('

\n

') + '

'); + } + + console.log('modpoll eq'); + } + + if (md_opt.modpoll_freq > 0) + this.periodic(); + + return this; +}; +var modpoll = new Modpoll(); + + +window.onbeforeunload = function (e) { + if ((ebi("save").getAttribute('class') + '').indexOf('disabled') >= 0) + return; //nice (todo) + + e.preventDefault(); //ff + e.returnValue = ''; //chrome +}; + + // save handler function save(e) { if (e) e.preventDefault(); - var save_btn = document.getElementById("save"), + var save_btn = ebi("save"), save_cls = save_btn.getAttribute('class') + ''; if (save_cls.indexOf('disabled') >= 0) { - toast('font-size:2em;color:#fc6;width:9em;', 'no changes'); + toast(true, ";font-size:2em;color:#c90", 9, "no changes"); return; } @@ -254,6 +348,8 @@ function save(e) { xhr.onreadystatechange = save_cb; xhr.btn = save_btn; xhr.txt = txt; + + modpoll.skip_one = true; // skip one iteration while we save xhr.send(fd); } @@ -347,23 +443,44 @@ function savechk_cb() { last_modified = this.lastmod; server_md = this.txt; draw_md(); - toast('font-size:6em;font-family:serif;color:#cf6;width:4em;', + toast(true, ";font-size:6em;font-family:serif;color:#9b4", 4, 'OK✔️' + this.ntry + ''); + + modpoll.disabled = false; } -function toast(style, msg) { - var ok = document.createElement('div'); - style += 'font-weight:bold;background:#444;border-radius:.3em;padding:.6em 0;position:fixed;top:30%;left:calc(50% - 2em);text-align:center;z-index:9001;transition:opacity 0.2s ease-in-out;opacity:1'; +function toast(autoclose, style, width, msg) { + var ok = ebi("toast"); + if (ok) + ok.parentNode.removeChild(ok); + + style = "width:" + width + "em;left:calc(50% - " + (width / 2) + "em);" + style; + ok = document.createElement('div'); + ok.setAttribute('id', 'toast'); ok.setAttribute('style', style); ok.innerHTML = msg; - var parent = document.getElementById('m'); + var parent = ebi('m'); document.documentElement.appendChild(ok); - setTimeout(function () { - ok.style.opacity = 0; - }, 500); - setTimeout(function () { - ok.parentNode.removeChild(ok); - }, 750); + + var hide = function (delay) { + delay = delay || 0; + + setTimeout(function () { + ok.style.opacity = 0; + }, delay); + + setTimeout(function () { + if (ok.parentNode) + ok.parentNode.removeChild(ok); + }, delay + 250); + } + + ok.onclick = function () { + hide(0); + }; + + if (autoclose) + hide(500); } @@ -806,7 +923,7 @@ function cfg_uni(e) { return false; } if (ev.code == "Escape" || kc == 27) { - var d = document.getElementById('helpclose'); + var d = ebi('helpclose'); if (d) d.click(); } @@ -863,22 +980,22 @@ function cfg_uni(e) { } } document.onkeydown = keydown; - document.getElementById('save').onclick = save; + ebi('save').onclick = save; })(); -document.getElementById('tools').onclick = function (e) { +ebi('tools').onclick = function (e) { if (e) e.preventDefault(); var is_open = dom_tbox.getAttribute('class') != 'open'; dom_tbox.setAttribute('class', is_open ? 'open' : ''); }; -document.getElementById('help').onclick = function (e) { +ebi('help').onclick = function (e) { if (e) e.preventDefault(); dom_tbox.setAttribute('class', ''); - var dom = document.getElementById('helpbox'); + var dom = ebi('helpbox'); var dtxt = dom.getElementsByTagName('textarea'); if (dtxt.length > 0) { convert_markdown(dtxt[0].value, dom); @@ -886,16 +1003,16 @@ document.getElementById('help').onclick = function (e) { } dom.style.display = 'block'; - document.getElementById('helpclose').onclick = function () { + ebi('helpclose').onclick = function () { dom.style.display = 'none'; }; }; -document.getElementById('fmt_table').onclick = fmt_table; -document.getElementById('mark_uni').onclick = mark_uni; -document.getElementById('iter_uni').onclick = iter_uni; -document.getElementById('cfg_uni').onclick = cfg_uni; +ebi('fmt_table').onclick = fmt_table; +ebi('mark_uni').onclick = mark_uni; +ebi('iter_uni').onclick = iter_uni; +ebi('cfg_uni').onclick = cfg_uni; // blame steen @@ -1019,7 +1136,7 @@ action_stack = (function () { })(); /* -document.getElementById('help').onclick = function () { +ebi('help').onclick = function () { var c1 = getComputedStyle(dom_src).cssText.split(';'); var c2 = getComputedStyle(dom_ref).cssText.split(';'); var max = Math.min(c1.length, c2.length); diff --git a/copyparty/web/mde.html b/copyparty/web/mde.html index 7a6847fa..e88c297f 100644 --- a/copyparty/web/mde.html +++ b/copyparty/web/mde.html @@ -25,7 +25,8 @@ var last_modified = {{ lastmod }}; var md_opt = { link_md_as_html: false, - allow_plugins: {{ md_plug }} + allow_plugins: {{ md_plug }}, + modpoll_freq: {{ md_chk_rate }} }; var lightswitch = (function () { diff --git a/copyparty/web/mde.js b/copyparty/web/mde.js index 4630bd39..13a385ba 100644 --- a/copyparty/web/mde.js +++ b/copyparty/web/mde.js @@ -1,9 +1,9 @@ "use strict"; -var dom_wrap = document.getElementById('mw'); -var dom_nav = document.getElementById('mn'); -var dom_doc = document.getElementById('m'); -var dom_md = document.getElementById('mt'); +var dom_wrap = ebi('mw'); +var dom_nav = ebi('mn'); +var dom_doc = ebi('m'); +var dom_md = ebi('mt'); (function () { var n = document.location + ''; @@ -65,7 +65,7 @@ var mde = (function () { mde.codemirror.on("change", function () { md_changed(mde); }); - var loader = document.getElementById('ml'); + var loader = ebi('ml'); loader.parentNode.removeChild(loader); return mde; })(); @@ -215,7 +215,7 @@ function save_chk() { var ok = document.createElement('div'); ok.setAttribute('style', 'font-size:6em;font-family:serif;font-weight:bold;color:#cf6;background:#444;border-radius:.3em;padding:.6em 0;position:fixed;top:30%;left:calc(50% - 2em);width:4em;text-align:center;z-index:9001;transition:opacity 0.2s ease-in-out;opacity:1'); ok.innerHTML = 'OK✔️'; - var parent = document.getElementById('m'); + var parent = ebi('m'); document.documentElement.appendChild(ok); setTimeout(function () { ok.style.opacity = 0; diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index d827c563..1e4cd9a3 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -38,7 +38,7 @@ function goto(dest) { obj[a].classList.remove('act'); if (dest) { - document.getElementById('op_' + dest).classList.add('act'); + ebi('op_' + dest).classList.add('act'); document.querySelector('#ops>a[data-dest=' + dest + ']').classList.add('act'); var fn = window['goto_' + dest]; @@ -66,7 +66,7 @@ function goto_up2k() { if (op !== null && op !== '.') goto(op); } - document.getElementById('ops').style.display = 'block'; + ebi('ops').style.display = 'block'; })();