diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py
index 43ca2b36..7d6841e7 100644
--- a/copyparty/httpcli.py
+++ b/copyparty/httpcli.py
@@ -24,6 +24,7 @@ class HttpCli(object):
"""
def __init__(self, conn):
+ self.t0 = time.time()
self.conn = conn
self.s = conn.s
self.sr = conn.sr
@@ -86,7 +87,7 @@ class HttpCli(object):
if "cookie" in self.headers:
cookies = self.headers["cookie"].split(";")
for k, v in [x.split("=", 1) for x in cookies]:
- if k != "cppwd":
+ if k.strip() != "cppwd":
continue
v = unescape_cookie(v)
@@ -307,10 +308,19 @@ class HttpCli(object):
with open(path, "wb", 512 * 1024) as f:
post_sz, _, sha_b64 = hashcopy(self.conn, reader, f)
- self.log("wrote {}/{} bytes to {}".format(post_sz, remains, path))
+ spd = self._spd(post_sz)
+ self.log("{} wrote {}/{} bytes to {}".format(spd, post_sz, remains, path))
self.reply("{}\n{}\n".format(post_sz, sha_b64).encode("utf-8"))
return True
+ def _spd(self, nbytes, add=True):
+ if add:
+ self.conn.nbyte += nbytes
+
+ spd1 = get_spd(nbytes, self.t0)
+ spd2 = get_spd(self.conn.nbyte, self.conn.t0)
+ return spd1 + " " + spd2
+
def handle_post_multipart(self):
self.parser = MultipartParser(self.log, self.sr, self.headers)
self.parser.parse()
@@ -450,7 +460,9 @@ class HttpCli(object):
except:
self.log("failed to utime ({}, {})".format(path, times))
- self.loud_reply("thank")
+ spd = self._spd(post_sz)
+ self.log("{} thank".format(spd))
+ self.reply("thank")
return True
def handle_login(self):
@@ -463,7 +475,7 @@ class HttpCli(object):
msg = "naw dude"
pwd = "x" # nosec
- h = {"Set-Cookie": "cppwd={}; Path=/".format(pwd)}
+ h = {"Set-Cookie": "cppwd={}; Path=/; SameSite=Lax".format(pwd)}
html = self.conn.tpl_msg.render(h1=msg, h2='ack', redir="/")
self.reply(html.encode("utf-8"), headers=h)
return True
@@ -575,6 +587,7 @@ class HttpCli(object):
raise Pebkac(400, "empty files in post")
files.append([sz, sha512_hex])
+ self.conn.nbyte += sz
except Pebkac:
if fn != os.devnull:
@@ -602,7 +615,9 @@ class HttpCli(object):
# truncated SHA-512 prevents length extension attacks;
# using SHA-512/224, optionally SHA-512/256 = :64
- self.log(msg)
+ vspd = self._spd(sz_total, False)
+ self.log("{} {}".format(vspd, msg))
+
if not nullwrite:
# TODO this is bad
log_fn = "up.{:.6f}.txt".format(t0)
@@ -885,6 +900,7 @@ class HttpCli(object):
self.log(logmsg)
return True
+ ret = True
with open_func(*open_args) as f:
remains = upper - lower
f.seek(lower)
@@ -897,17 +913,17 @@ class HttpCli(object):
if remains < len(buf):
buf = buf[:remains]
- remains -= len(buf)
-
try:
self.s.sendall(buf)
+ remains -= len(buf)
except:
logmsg += " \033[31m" + str(upper - remains) + "\033[0m"
- self.log(logmsg)
- return False
+ ret = False
+ break
- self.log(logmsg)
- return True
+ spd = self._spd((upper - lower) - remains)
+ self.log("{}, {}".format(logmsg, spd))
+ return ret
def tx_md(self, fs_path):
logmsg = "{:4} {} ".format("", self.req)
diff --git a/copyparty/httpconn.py b/copyparty/httpconn.py
index 8be3c49a..79be834e 100644
--- a/copyparty/httpconn.py
+++ b/copyparty/httpconn.py
@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
import os
import sys
import ssl
+import time
import socket
try:
@@ -41,6 +42,8 @@ class HttpConn(object):
self.auth = hsrv.auth
self.cert_path = hsrv.cert_path
+ self.t0 = time.time()
+ self.nbyte = 0
self.workload = 0
self.log_func = hsrv.log
self.log_src = "{} \033[36m{}".format(addr[0], addr[1]).ljust(26)
diff --git a/copyparty/util.py b/copyparty/util.py
index a3a4ae16..b1cab228 100644
--- a/copyparty/util.py
+++ b/copyparty/util.py
@@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals
import re
import sys
+import time
import base64
import struct
import hashlib
@@ -349,6 +350,16 @@ def humansize(sz, terse=False):
return ret.replace("iB", "").replace(" ", "")
+def get_spd(nbyte, t0, t=None):
+ if t is None:
+ t = time.time()
+
+ bps = nbyte / ((t - t0) + 0.001)
+ s1 = humansize(nbyte).replace(" ", "\033[33m").replace("iB", "")
+ s2 = humansize(bps).replace(" ", "\033[35m").replace("iB", "")
+ return "{} \033[0m{}/s\033[0m".format(s1, s2)
+
+
def undot(path):
ret = []
for node in path.split("/"):
diff --git a/copyparty/web/md.css b/copyparty/web/md.css
index e32b45c3..b6c2837a 100644
--- a/copyparty/web/md.css
+++ b/copyparty/web/md.css
@@ -109,8 +109,12 @@ h2 a, h4 a, h6 a {
#mp ol>li {
margin: .7em 0;
}
+strong {
+ color: #000;
+}
p>em,
-li>em {
+li>em,
+td>em {
color: #c50;
padding: .1em;
border-bottom: .1em solid #bbb;
@@ -289,6 +293,32 @@ blink {
text-decoration: underline;
border: none;
}
+ #mh a:hover {
+ color: #000;
+ background: #ddd;
+ }
+ #toolsbox {
+ overflow: hidden;
+ display: inline-block;
+ background: #eee;
+ height: 1.5em;
+ padding: 0 .2em;
+ margin: 0 .2em;
+ position: absolute;
+ }
+ #toolsbox.open {
+ height: auto;
+ overflow: visible;
+ background: #eee;
+ box-shadow: 0 .2em .2em #ccc;
+ padding-bottom: .2em;
+ }
+ #toolsbox a {
+ display: block;
+ }
+ #toolsbox a+a {
+ text-decoration: none;
+ }
@@ -332,8 +362,12 @@ blink {
html.dark #m>ol {
border-color: #555;
}
+ html.dark strong {
+ color: #fff;
+ }
html.dark p>em,
- html.dark li>em {
+ html.dark li>em,
+ html.dark td>em {
color: #f94;
border-color: #666;
}
@@ -371,6 +405,17 @@ blink {
color: #ccc;
background: none;
}
+ html.dark #mh a:hover {
+ background: #333;
+ color: #fff;
+ }
+ html.dark #toolsbox {
+ background: #222;
+ }
+ html.dark #toolsbox.open {
+ box-shadow: 0 .2em .2em #069;
+ border-radius: 0 0 .4em .4em;
+ }
}
@media screen and (min-width: 66em) {
@@ -541,7 +586,8 @@ blink {
color: #240;
}
html.dark p>em,
- html.dark li>em {
+ html.dark li>em,
+ html.dark td>em {
color: #940;
}
}
diff --git a/copyparty/web/md.html b/copyparty/web/md.html
index 1e93cbc9..b9545cad 100644
--- a/copyparty/web/md.html
+++ b/copyparty/web/md.html
@@ -17,7 +17,14 @@
save
sbs
editor
- help
+
{%- else %}
edit (basic)
edit (fancy)
@@ -46,6 +53,9 @@ write markdown (most html is π too)
## hotkey list
* `Ctrl-S` to save
+* `Ctrl-E` to toggle mode
+* `Ctrl-K` to prettyprint a table
+* `Ctrl-U` to iterate non-ascii chars
* `Ctrl-H` / `Ctrl-Shift-H` to create a header
* `TAB` / `Shift-TAB` to indent/dedent a selection
diff --git a/copyparty/web/md2.js b/copyparty/web/md2.js
index 318ba9fd..6b74c85c 100644
--- a/copyparty/web/md2.js
+++ b/copyparty/web/md2.js
@@ -2,10 +2,16 @@
var server_md = dom_src.value;
+// the non-ascii whitelist
+var esc_uni_whitelist = '\\n\\t\\x20-\\x7eΓΓΓ
æøΓ₯';
+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_ref = (function () {
var d = document.createElement('div');
d.setAttribute('id', 'mtr');
@@ -339,7 +345,7 @@ function savechk_cb() {
server_md = this.txt;
draw_md();
toast('font-size:6em;font-family:serif;color:#cf6;width:4em;',
- 'OKβοΈ' + this.ntry + '');
+ 'OKβοΈ' + this.ntry + '');
}
function toast(style, msg) {
@@ -427,6 +433,9 @@ function setsel(s) {
dom_src.value = [s.pre, s.sel, s.post].join('');
dom_src.setSelectionRange(s.car, s.cdr, dom_src.selectionDirection);
dom_src.oninput();
+ // support chrome:
+ dom_src.blur();
+ dom_src.focus();
}
@@ -500,7 +509,8 @@ function md_newline() {
var s = linebounds(true),
ln = s.md.substring(s.n1, s.n2),
m1 = /^( *)([0-9]+)(\. +)/.exec(ln),
- m2 = /^[ \t>+-]*(\* )?/.exec(ln);
+ m2 = /^[ \t>+-]*(\* )?/.exec(ln),
+ drop = dom_src.selectionEnd - dom_src.selectionStart;
var pre = m2[0];
if (m1 !== null)
@@ -512,7 +522,7 @@ function md_newline() {
s.pre = s.md.substring(0, s.car) + '\n' + pre;
s.sel = '';
- s.post = s.md.substring(s.car);
+ s.post = s.md.substring(s.car + drop);
s.car = s.cdr = s.pre.length;
setsel(s);
return false;
@@ -522,11 +532,17 @@ function md_newline() {
// backspace
function md_backspace() {
var s = linebounds(true),
- ln = s.md.substring(s.n1, s.n2),
- m = /^[ \t>+-]*(\* )?([0-9]+\. +)?/.exec(ln);
+ o0 = dom_src.selectionStart,
+ left = s.md.slice(s.n1, o0),
+ m = /^[ \t>+-]*(\* )?([0-9]+\. +)?/.exec(left);
+ // if car is in whitespace area, do nothing
+ if (/^\s*$/.test(left))
+ return true;
+
+ // same if line is all-whitespace or non-markup
var v = m[0].replace(/[^ ]/g, " ");
- if (v === m[0] || v.length !== ln.length)
+ if (v === m[0] || v.length !== left.length)
return true;
s.pre = s.md.substring(0, s.n1) + v;
@@ -540,8 +556,8 @@ function md_backspace() {
// paragraph jump
function md_p_jump(down) {
- var ofs = dom_src.selectionStart;
- var txt = dom_src.value;
+ var txt = dom_src.value,
+ ofs = dom_src.selectionStart;
if (down) {
while (txt[ofs] == '\n' && --ofs > 0);
@@ -562,6 +578,202 @@ function md_p_jump(down) {
}
+function reLastIndexOf(txt, ptn, end) {
+ var ofs = (typeof end !== 'undefined') ? end : txt.length;
+ end = ofs;
+ while (ofs >= 0) {
+ var sub = txt.slice(ofs, end);
+ if (ptn.test(sub))
+ return ofs;
+
+ ofs--;
+ }
+ return -1;
+}
+
+
+// table formatter
+function fmt_table(e) {
+ if (e) e.preventDefault();
+ //dom_tbox.setAttribute('class', '');
+
+ var txt = dom_src.value,
+ ofs = dom_src.selectionStart,
+ //o0 = txt.lastIndexOf('\n\n', ofs),
+ //o1 = txt.indexOf('\n\n', ofs);
+ o0 = reLastIndexOf(txt, /\n\s*\n/m, ofs),
+ o1 = txt.slice(ofs).search(/\n\s*\n/m);
+ // note \s contains \n but its fine
+
+ if (o0 < 0)
+ o0 = 0;
+ else {
+ // seek past the hit
+ var m = /\n\s*\n/m.exec(txt.slice(o0));
+ o0 += m[0].length;
+ }
+
+ o1 = o1 < 0 ? txt.length : o1 + ofs;
+
+ var err = 'cannot format table due to ',
+ tab = txt.slice(o0, o1).split(/\s*\n/),
+ re_ind = /^\s*/,
+ ind = tab[1].match(re_ind)[0],
+ r0_ind = tab[0].slice(0, ind.length),
+ lpipe = tab[1].indexOf('|') < tab[1].indexOf('-'),
+ rpipe = tab[1].lastIndexOf('|') > tab[1].lastIndexOf('-'),
+ re_lpipe = lpipe ? /^\s*\|\s*/ : /^\s*/,
+ re_rpipe = rpipe ? /\s*\|\s*$/ : /\s*$/;
+
+ for (var a = 0; a < tab.length; a++) {
+ var ind2 = tab[a].match(re_ind)[0];
+ if (ind != ind2 && a > 0) // the table can be a list entry or something, ignore [0]
+ return alert(err + 'indentation mismatch on row 2 and ' + (a + 1) + ',\n' + tab[a]);
+
+ var t = tab[a].slice(ind.length);
+ t = t.replace(re_lpipe, "");
+ t = t.replace(re_rpipe, "");
+ tab[a] = t.split(/\s*\|\s*/g);
+
+ if (a == 0)
+ ncols = tab[a].length;
+
+ if (ncols != tab[a].length)
+ return alert(err + 'num.columns mismatch on row 2 and ' + (a + 1) + '; ' + ncols + ' != ' + tab[a].length);
+ }
+
+ var re_align = /^ *(:?)-+(:?) *$/;
+ var align = [];
+ for (var col = 0; col < tab[1].length; col++) {
+ var m = tab[1][col].match(re_align);
+ if (!m)
+ return alert(err + 'invalid column specification, row 2, col ' + (col + 1) + ', [' + tab[1][col] + ']');
+
+ if (m[2]) {
+ if (m[1])
+ align.push('c');
+ else
+ align.push('r');
+ }
+ else
+ align.push('l');
+ }
+
+ var pad = [];
+ var tmax = 0;
+ for (var col = 0; col < ncols; col++) {
+ var max = 0;
+ for (var row = 0; row < tab.length; row++)
+ max = Math.max(max, tab[row][col].length);
+
+ var s = '';
+ for (var n = 0; n < max; n++)
+ s += ' ';
+
+ pad.push(s);
+ tmax = Math.max(max, tmax);
+ }
+
+ var dashes = '';
+ for (var a = 0; a < tmax; a++)
+ dashes += '-';
+
+ var ret = [];
+ for (var row = 0; row < tab.length; row++) {
+ var ln = [];
+ for (var col = 0; col < tab[row].length; col++) {
+ var p = pad[col];
+ var s = tab[row][col];
+
+ if (align[col] == 'l') {
+ s = (s + p).slice(0, p.length);
+ }
+ else if (align[col] == 'r') {
+ s = (p + s).slice(-p.length);
+ }
+ else {
+ var pt = p.length - s.length;
+ var pl = p.slice(0, Math.floor(pt / 2));
+ var pr = p.slice(0, pt - pl.length);
+ s = pl + s + pr;
+ }
+
+ if (row == 1) {
+ if (align[col] == 'l')
+ s = dashes.slice(0, p.length);
+ else if (align[col] == 'r')
+ s = dashes.slice(0, p.length - 1) + ':';
+ else
+ s = ':' + dashes.slice(0, p.length - 2) + ':';
+ }
+ ln.push(s);
+ }
+ ret.push(ind + '| ' + ln.join(' | ') + ' |');
+ }
+
+ // restore any markup in the row0 gutter
+ ret[0] = r0_ind + ret[0].slice(ind.length);
+
+ ret = {
+ "pre": txt.slice(0, o0),
+ "sel": ret.join('\n'),
+ "post": txt.slice(o1),
+ "car": o0,
+ "cdr": o0
+ };
+ setsel(ret);
+}
+
+
+// show unicode
+function mark_uni(e) {
+ if (e) e.preventDefault();
+ dom_tbox.setAttribute('class', '');
+
+ var txt = dom_src.value,
+ ptn = new RegExp('([^' + js_uni_whitelist + ']+)', 'g');
+
+ mod = txt.replace(/\r/g, "").replace(ptn, "\u2588\u2770$1\u2771");
+
+ if (txt == mod) {
+ alert('no results; no modifications were made');
+ return;
+ }
+ dom_src.value = mod;
+}
+
+
+// iterate unicode
+function iter_uni(e) {
+ if (e) e.preventDefault();
+
+ var txt = dom_src.value,
+ ofs = dom_src.selectionDirection == "forward" ? dom_src.selectionEnd : dom_src.selectionStart,
+ re = new RegExp('([^' + js_uni_whitelist + ']+)'),
+ m = re.exec(txt.slice(ofs));
+
+ if (!m) {
+ alert('no more hits from cursor onwards');
+ return;
+ }
+ ofs += m.index;
+
+ dom_src.setSelectionRange(ofs, ofs + m[0].length, "forward");
+ dom_src.oninput();
+ // support chrome:
+ dom_src.blur();
+ dom_src.focus();
+}
+
+
+// configure whitelist
+function cfg_uni(e) {
+ if (e) e.preventDefault();
+ esc_uni_whitelist = prompt("unicode whitelist", esc_uni_whitelist);
+ js_uni_whitelist = eval('\'' + esc_uni_whitelist + '\'');
+}
+
+
// hotkeys / toolbar
(function () {
function keydown(ev) {
@@ -609,6 +821,19 @@ function md_p_jump(down) {
if (!ctrl && !ev.shiftKey && kc == 8) {
return md_backspace();
}
+ if (ctrl && (ev.code == "KeyK")) {
+ fmt_table();
+ return false;
+ }
+ if (ctrl && (ev.code == "KeyU")) {
+ iter_uni();
+ return false;
+ }
+ if (ctrl && (ev.code == "KeyE")) {
+ dom_nsbs.click();
+ //fmt_table();
+ return false;
+ }
var up = ev.code == "ArrowUp" || kc == 38;
var dn = ev.code == "ArrowDown" || kc == 40;
if (ctrl && (up || dn)) {
@@ -622,8 +847,17 @@ function md_p_jump(down) {
})();
+document.getElementById('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) {
if (e) e.preventDefault();
+ dom_tbox.setAttribute('class', '');
+
var dom = document.getElementById('helpbox');
var dtxt = dom.getElementsByTagName('textarea');
if (dtxt.length > 0) {
@@ -638,6 +872,12 @@ document.getElementById('help').onclick = function (e) {
};
+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;
+
+
// blame steen
action_stack = (function () {
var hist = {
diff --git a/copyparty/web/mde.css b/copyparty/web/mde.css
index 6e73c9cd..ac59593a 100644
--- a/copyparty/web/mde.css
+++ b/copyparty/web/mde.css
@@ -160,8 +160,12 @@ h2 {
.mdo ol>li {
margin: .7em 0;
}
+strong {
+ color: #000;
+}
p>em,
-li>em {
+li>em,
+td>em {
color: #c50;
padding: .1em;
border-bottom: .1em solid #bbb;
@@ -253,8 +257,12 @@ html.dark .mdo>ul,
html.dark .mdo>ol {
border-color: #555;
}
+html.dark strong {
+ color: #fff;
+}
html.dark p>em,
-html.dark li>em {
+html.dark li>em,
+html.dark td>em {
color: #f94;
border-color: #666;
}
diff --git a/copyparty/web/mde.js b/copyparty/web/mde.js
index 2a8d7906..fd16cba0 100644
--- a/copyparty/web/mde.js
+++ b/copyparty/web/mde.js
@@ -121,7 +121,7 @@ function save(mde) {
fd.append("lastmod", (force ? -1 : last_modified));
fd.append("body", txt);
- var url = (document.location + '').split('?')[0] + '?raw';
+ var url = (document.location + '').split('?')[0];
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'text';
diff --git a/scripts/sfx.sh b/scripts/sfx.sh
index c7f7bbde..acaaa0ed 100644
--- a/scripts/sfx.sh
+++ b/scripts/sfx.sh
@@ -32,8 +32,12 @@ dir="$(
# detect available pythons
(IFS=:; for d in $PATH; do
- printf '%s\n' "$d"/python* "$d"/pypy* | tac;
-done) | grep -E '(python|pypy)[0-9\.-]*$' > $dir/pys || true
+ printf '%s\n' "$d"/python* "$d"/pypy*;
+done) |
+(sed -E 's/(.*\/[^/0-9]+)([0-9]?[^/]*)$/\2 \1/' || cat) |
+(sort -nr || cat) |
+(sed -E 's/([^ ]*) (.*)/\2\1/' || cat) |
+grep -E '/(python|pypy)[0-9\.-]*$' >$dir/pys || true
# see if we made a choice before
[ -z "$pybin" ] && pybin="$(cat $dir/py 2>/dev/null || true)"
diff --git a/srv/test.md b/srv/test.md
index 3025cc2d..8572fc48 100644
--- a/srv/test.md
+++ b/srv/test.md
@@ -1,5 +1,16 @@
### hello world
+* qwe
+* asd
+ * zxc
+ * 573
+ * one
+ * two
+
+ * |||
+ |--|--|
+ |listed|table|
+
```
[72....................................................................]
[80............................................................................]
@@ -21,6 +32,8 @@
l[i]=1I;(){}o0O> var foo = "$(`bar`)"; a's'd
```
+ππ½.π.ππ
+
[](#s1)
[s1](#s1)
[#s1](#s1)
@@ -121,6 +134,11 @@ a newline toplevel
| a table | on the right |
| second row | foo bar |
+||
+--|:-:|-:
+a table | big text in this | aaakbfddd
+second row | centred | bbb
+
* list entry
* [x] yes
* [ ] no