lots of stuff:

* show per-connection and per-transfer speeds
* support multiple cookies in parser
* set SameSite=Lax
* restore macos support in sfx.sh
* md-editor: add mojibake/unicode hunter
* md-editor: add table formatter
* md-editor: make bold bolder
* md-editor: more hotkeys
* md-editor: fix saving in fancy
* md-editor: fix eof-scrolling in chrome
* md-editor: fix text erasure with newline
* md-editor: fix backspace behavior in gutter
This commit is contained in:
ed 2020-11-13 02:58:38 +01:00
parent a90c49b8fb
commit 69d3359e47
10 changed files with 384 additions and 28 deletions

View file

@ -24,6 +24,7 @@ class HttpCli(object):
""" """
def __init__(self, conn): def __init__(self, conn):
self.t0 = time.time()
self.conn = conn self.conn = conn
self.s = conn.s self.s = conn.s
self.sr = conn.sr self.sr = conn.sr
@ -86,7 +87,7 @@ class HttpCli(object):
if "cookie" in self.headers: if "cookie" in self.headers:
cookies = self.headers["cookie"].split(";") cookies = self.headers["cookie"].split(";")
for k, v in [x.split("=", 1) for x in cookies]: for k, v in [x.split("=", 1) for x in cookies]:
if k != "cppwd": if k.strip() != "cppwd":
continue continue
v = unescape_cookie(v) v = unescape_cookie(v)
@ -307,10 +308,19 @@ class HttpCli(object):
with open(path, "wb", 512 * 1024) as f: with open(path, "wb", 512 * 1024) as f:
post_sz, _, sha_b64 = hashcopy(self.conn, reader, 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")) self.reply("{}\n{}\n".format(post_sz, sha_b64).encode("utf-8"))
return True 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): def handle_post_multipart(self):
self.parser = MultipartParser(self.log, self.sr, self.headers) self.parser = MultipartParser(self.log, self.sr, self.headers)
self.parser.parse() self.parser.parse()
@ -450,7 +460,9 @@ class HttpCli(object):
except: except:
self.log("failed to utime ({}, {})".format(path, times)) 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 return True
def handle_login(self): def handle_login(self):
@ -463,7 +475,7 @@ class HttpCli(object):
msg = "naw dude" msg = "naw dude"
pwd = "x" # nosec 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='<a href="/">ack</a>', redir="/") html = self.conn.tpl_msg.render(h1=msg, h2='<a href="/">ack</a>', redir="/")
self.reply(html.encode("utf-8"), headers=h) self.reply(html.encode("utf-8"), headers=h)
return True return True
@ -575,6 +587,7 @@ class HttpCli(object):
raise Pebkac(400, "empty files in post") raise Pebkac(400, "empty files in post")
files.append([sz, sha512_hex]) files.append([sz, sha512_hex])
self.conn.nbyte += sz
except Pebkac: except Pebkac:
if fn != os.devnull: if fn != os.devnull:
@ -602,7 +615,9 @@ class HttpCli(object):
# truncated SHA-512 prevents length extension attacks; # truncated SHA-512 prevents length extension attacks;
# using SHA-512/224, optionally SHA-512/256 = :64 # 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: if not nullwrite:
# TODO this is bad # TODO this is bad
log_fn = "up.{:.6f}.txt".format(t0) log_fn = "up.{:.6f}.txt".format(t0)
@ -885,6 +900,7 @@ class HttpCli(object):
self.log(logmsg) self.log(logmsg)
return True return True
ret = True
with open_func(*open_args) as f: with open_func(*open_args) as f:
remains = upper - lower remains = upper - lower
f.seek(lower) f.seek(lower)
@ -897,17 +913,17 @@ class HttpCli(object):
if remains < len(buf): if remains < len(buf):
buf = buf[:remains] buf = buf[:remains]
remains -= len(buf)
try: try:
self.s.sendall(buf) self.s.sendall(buf)
remains -= len(buf)
except: except:
logmsg += " \033[31m" + str(upper - remains) + "\033[0m" logmsg += " \033[31m" + str(upper - remains) + "\033[0m"
self.log(logmsg) ret = False
return False break
self.log(logmsg) spd = self._spd((upper - lower) - remains)
return True self.log("{}, {}".format(logmsg, spd))
return ret
def tx_md(self, fs_path): def tx_md(self, fs_path):
logmsg = "{:4} {} ".format("", self.req) logmsg = "{:4} {} ".format("", self.req)

View file

@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
import os import os
import sys import sys
import ssl import ssl
import time
import socket import socket
try: try:
@ -41,6 +42,8 @@ class HttpConn(object):
self.auth = hsrv.auth self.auth = hsrv.auth
self.cert_path = hsrv.cert_path self.cert_path = hsrv.cert_path
self.t0 = time.time()
self.nbyte = 0
self.workload = 0 self.workload = 0
self.log_func = hsrv.log self.log_func = hsrv.log
self.log_src = "{} \033[36m{}".format(addr[0], addr[1]).ljust(26) self.log_src = "{} \033[36m{}".format(addr[0], addr[1]).ljust(26)

View file

@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals
import re import re
import sys import sys
import time
import base64 import base64
import struct import struct
import hashlib import hashlib
@ -349,6 +350,16 @@ def humansize(sz, terse=False):
return ret.replace("iB", "").replace(" ", "") 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): def undot(path):
ret = [] ret = []
for node in path.split("/"): for node in path.split("/"):

View file

@ -109,8 +109,12 @@ h2 a, h4 a, h6 a {
#mp ol>li { #mp ol>li {
margin: .7em 0; margin: .7em 0;
} }
strong {
color: #000;
}
p>em, p>em,
li>em { li>em,
td>em {
color: #c50; color: #c50;
padding: .1em; padding: .1em;
border-bottom: .1em solid #bbb; border-bottom: .1em solid #bbb;
@ -289,6 +293,32 @@ blink {
text-decoration: underline; text-decoration: underline;
border: none; 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 { html.dark #m>ol {
border-color: #555; border-color: #555;
} }
html.dark strong {
color: #fff;
}
html.dark p>em, html.dark p>em,
html.dark li>em { html.dark li>em,
html.dark td>em {
color: #f94; color: #f94;
border-color: #666; border-color: #666;
} }
@ -371,6 +405,17 @@ blink {
color: #ccc; color: #ccc;
background: none; 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) { @media screen and (min-width: 66em) {
@ -541,7 +586,8 @@ blink {
color: #240; color: #240;
} }
html.dark p>em, html.dark p>em,
html.dark li>em { html.dark li>em,
html.dark td>em {
color: #940; color: #940;
} }
} }

View file

@ -17,7 +17,14 @@
<a id="save" href="?edit">save</a> <a id="save" href="?edit">save</a>
<a id="sbs" href="#">sbs</a> <a id="sbs" href="#">sbs</a>
<a id="nsbs" href="#">editor</a> <a id="nsbs" href="#">editor</a>
<a id="help" href="#">help</a> <div id="toolsbox">
<a id="tools" href="#">tools</a>
<a id="fmt_table" href="#">prettify table (ctrl-k)</a>
<a id="iter_uni" href="#">non-ascii: iterate (ctrl-u)</a>
<a id="mark_uni" href="#">non-ascii: markup</a>
<a id="cfg_uni" href="#">non-ascii: whitelist</a>
<a id="help" href="#">help</a>
</div>
{%- else %} {%- else %}
<a href="?edit">edit (basic)</a> <a href="?edit">edit (basic)</a>
<a href="?edit2">edit (fancy)</a> <a href="?edit2">edit (fancy)</a>
@ -46,6 +53,9 @@ write markdown (most html is 🙆 too)
## hotkey list ## hotkey list
* `Ctrl-S` to save * `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 * `Ctrl-H` / `Ctrl-Shift-H` to create a header
* `TAB` / `Shift-TAB` to indent/dedent a selection * `TAB` / `Shift-TAB` to indent/dedent a selection

View file

@ -2,10 +2,16 @@
var server_md = dom_src.value; 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 // dom nodes
var dom_swrap = document.getElementById('mtw'); var dom_swrap = document.getElementById('mtw');
var dom_sbs = document.getElementById('sbs'); var dom_sbs = document.getElementById('sbs');
var dom_nsbs = document.getElementById('nsbs'); var dom_nsbs = document.getElementById('nsbs');
var dom_tbox = document.getElementById('toolsbox');
var dom_ref = (function () { var dom_ref = (function () {
var d = document.createElement('div'); var d = document.createElement('div');
d.setAttribute('id', 'mtr'); d.setAttribute('id', 'mtr');
@ -339,7 +345,7 @@ function savechk_cb() {
server_md = this.txt; server_md = this.txt;
draw_md(); draw_md();
toast('font-size:6em;font-family:serif;color:#cf6;width:4em;', toast('font-size:6em;font-family:serif;color:#cf6;width:4em;',
'OK✔<span style="font-size:.2em;color:#999">' + this.ntry + '</span>'); 'OK✔<span style="font-size:.2em;color:#999;position:absolute">' + this.ntry + '</span>');
} }
function toast(style, msg) { function toast(style, msg) {
@ -427,6 +433,9 @@ function setsel(s) {
dom_src.value = [s.pre, s.sel, s.post].join(''); dom_src.value = [s.pre, s.sel, s.post].join('');
dom_src.setSelectionRange(s.car, s.cdr, dom_src.selectionDirection); dom_src.setSelectionRange(s.car, s.cdr, dom_src.selectionDirection);
dom_src.oninput(); dom_src.oninput();
// support chrome:
dom_src.blur();
dom_src.focus();
} }
@ -500,7 +509,8 @@ function md_newline() {
var s = linebounds(true), var s = linebounds(true),
ln = s.md.substring(s.n1, s.n2), ln = s.md.substring(s.n1, s.n2),
m1 = /^( *)([0-9]+)(\. +)/.exec(ln), m1 = /^( *)([0-9]+)(\. +)/.exec(ln),
m2 = /^[ \t>+-]*(\* )?/.exec(ln); m2 = /^[ \t>+-]*(\* )?/.exec(ln),
drop = dom_src.selectionEnd - dom_src.selectionStart;
var pre = m2[0]; var pre = m2[0];
if (m1 !== null) if (m1 !== null)
@ -512,7 +522,7 @@ function md_newline() {
s.pre = s.md.substring(0, s.car) + '\n' + pre; s.pre = s.md.substring(0, s.car) + '\n' + pre;
s.sel = ''; s.sel = '';
s.post = s.md.substring(s.car); s.post = s.md.substring(s.car + drop);
s.car = s.cdr = s.pre.length; s.car = s.cdr = s.pre.length;
setsel(s); setsel(s);
return false; return false;
@ -522,11 +532,17 @@ function md_newline() {
// backspace // backspace
function md_backspace() { function md_backspace() {
var s = linebounds(true), var s = linebounds(true),
ln = s.md.substring(s.n1, s.n2), o0 = dom_src.selectionStart,
m = /^[ \t>+-]*(\* )?([0-9]+\. +)?/.exec(ln); 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, " "); var v = m[0].replace(/[^ ]/g, " ");
if (v === m[0] || v.length !== ln.length) if (v === m[0] || v.length !== left.length)
return true; return true;
s.pre = s.md.substring(0, s.n1) + v; s.pre = s.md.substring(0, s.n1) + v;
@ -540,8 +556,8 @@ function md_backspace() {
// paragraph jump // paragraph jump
function md_p_jump(down) { 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) { if (down) {
while (txt[ofs] == '\n' && --ofs > 0); 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 // hotkeys / toolbar
(function () { (function () {
function keydown(ev) { function keydown(ev) {
@ -609,6 +821,19 @@ function md_p_jump(down) {
if (!ctrl && !ev.shiftKey && kc == 8) { if (!ctrl && !ev.shiftKey && kc == 8) {
return md_backspace(); 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 up = ev.code == "ArrowUp" || kc == 38;
var dn = ev.code == "ArrowDown" || kc == 40; var dn = ev.code == "ArrowDown" || kc == 40;
if (ctrl && (up || dn)) { 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) { document.getElementById('help').onclick = function (e) {
if (e) e.preventDefault(); if (e) e.preventDefault();
dom_tbox.setAttribute('class', '');
var dom = document.getElementById('helpbox'); var dom = document.getElementById('helpbox');
var dtxt = dom.getElementsByTagName('textarea'); var dtxt = dom.getElementsByTagName('textarea');
if (dtxt.length > 0) { 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 // blame steen
action_stack = (function () { action_stack = (function () {
var hist = { var hist = {

View file

@ -160,8 +160,12 @@ h2 {
.mdo ol>li { .mdo ol>li {
margin: .7em 0; margin: .7em 0;
} }
strong {
color: #000;
}
p>em, p>em,
li>em { li>em,
td>em {
color: #c50; color: #c50;
padding: .1em; padding: .1em;
border-bottom: .1em solid #bbb; border-bottom: .1em solid #bbb;
@ -253,8 +257,12 @@ html.dark .mdo>ul,
html.dark .mdo>ol { html.dark .mdo>ol {
border-color: #555; border-color: #555;
} }
html.dark strong {
color: #fff;
}
html.dark p>em, html.dark p>em,
html.dark li>em { html.dark li>em,
html.dark td>em {
color: #f94; color: #f94;
border-color: #666; border-color: #666;
} }

View file

@ -121,7 +121,7 @@ function save(mde) {
fd.append("lastmod", (force ? -1 : last_modified)); fd.append("lastmod", (force ? -1 : last_modified));
fd.append("body", txt); fd.append("body", txt);
var url = (document.location + '').split('?')[0] + '?raw'; var url = (document.location + '').split('?')[0];
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('POST', url, true); xhr.open('POST', url, true);
xhr.responseType = 'text'; xhr.responseType = 'text';

View file

@ -32,8 +32,12 @@ dir="$(
# detect available pythons # detect available pythons
(IFS=:; for d in $PATH; do (IFS=:; for d in $PATH; do
printf '%s\n' "$d"/python* "$d"/pypy* | tac; printf '%s\n' "$d"/python* "$d"/pypy*;
done) | grep -E '(python|pypy)[0-9\.-]*$' > $dir/pys || true 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 # see if we made a choice before
[ -z "$pybin" ] && pybin="$(cat $dir/py 2>/dev/null || true)" [ -z "$pybin" ] && pybin="$(cat $dir/py 2>/dev/null || true)"

View file

@ -1,5 +1,16 @@
### hello world ### hello world
* qwe
* asd
* zxc
* 573
* one
* two
* |||
|--|--|
|listed|table|
``` ```
[72....................................................................] [72....................................................................]
[80............................................................................] [80............................................................................]
@ -21,6 +32,8 @@
l[i]=1I;(){}o0O</> var foo = "$(`bar`)"; a's'd l[i]=1I;(){}o0O</> var foo = "$(`bar`)"; a's'd
``` ```
🔍🌽.📕.🍙🔎
[](#s1) [](#s1)
[s1](#s1) [s1](#s1)
[#s1](#s1) [#s1](#s1)
@ -121,6 +134,11 @@ a newline toplevel
| a table | on the right | | a table | on the right |
| second row | foo bar | | second row | foo bar |
||
--|:-:|-:
a table | big text in this | aaakbfddd
second row | centred | bbb
* list entry * list entry
* [x] yes * [x] yes
* [ ] no * [ ] no