Compare commits

...

11 Commits

Author SHA1 Message Date
ed
09557fbe83 v0.4.2 2020-05-15 01:02:18 +02:00
ed
1c0f44fa4e more 206 correctness 2020-05-15 00:52:57 +02:00
ed
fc4d59d2d7 improve autoindent 2020-05-15 00:39:36 +02:00
ed
12345fbacc fix editor cursor (especially in firefox) 2020-05-15 00:03:26 +02:00
ed
2e33c8d222 improve http206 and fuse-client 2020-05-15 00:00:49 +02:00
ed
db5f07f164 v0.4.1 2020-05-14 01:08:42 +02:00
ed
e050e69a43 dodge osx-safari bugs 2020-05-14 00:28:10 +02:00
ed
27cb1d4fc7 fix scroll sync on osx ff/chrome 2020-05-14 00:03:01 +02:00
ed
5d6a740947 fix undo/redo cursor pos 2020-05-13 23:27:27 +02:00
ed
da3f68c363 editor performance 2020-05-13 23:26:11 +02:00
ed
d7d1c3685c sfx notes 2020-05-13 01:12:33 +02:00
13 changed files with 411 additions and 153 deletions

View File

@@ -59,13 +59,16 @@ launch either of them and it'll unpack and run copyparty, assuming you have pyth
pls note that `copyparty-sfx.sh` will fail if you rename `copyparty-sfx.py` to `copyparty.py` and keep it in the same folder because `sys.path` is funky pls note that `copyparty-sfx.sh` will fail if you rename `copyparty-sfx.py` to `copyparty.py` and keep it in the same folder because `sys.path` is funky
if you don't need all the features you can repack the sfx and save a bunch of space, tho currently the only removable feature is the opus/vorbis javascript decoder which is needed by apple devices to play foss audio files if you don't need all the features you can repack the sfx and save a bunch of space; all you need is an sfx and a copy of this repo (nothing else to download or build, except for either msys2 or WSL if you're on windows)
* `724K` original size as of v0.4.0
* `256K` after `./scripts/make-sfx.sh re no-ogv`
* `164K` after `./scripts/make-sfx.sh re no-ogv no-cm`
steps to reduce the sfx size from `720 kB` to `250 kB` roughly: the features you can opt to drop are
* run one of the sfx'es once to unpack it * `ogv`.js, the opus/vorbis decoder which is needed by apple devices to play foss audio files
* `./scripts/make-sfx.sh re no-ogv` creates a new pair of sfx * `cm`/easymde, the "fancy" markdown editor
no internet connection needed, just download an sfx and the repo zip (also if you're on windows use msys2) for the `re`pack to work, first run one of the sfx'es once to unpack it
# install on android # install on android

48
bin/copyparty-fuse.py Executable file → Normal file
View File

@@ -22,7 +22,9 @@ from urllib.parse import quote_from_bytes as quote
try: try:
from fuse import FUSE, FuseOSError, Operations from fuse import FUSE, FuseOSError, Operations
except: except:
print("\n could not import fuse;\n pip install fusepy\n") print(
"\n could not import fuse; these may help:\n python3 -m pip install --user fusepy\n apt install libfuse\n modprobe fuse"
)
raise raise
@@ -34,9 +36,7 @@ usage:
dependencies: dependencies:
sudo apk add fuse-dev sudo apk add fuse-dev
python3 -m venv ~/pe/ve.fusepy python3 -m pip install --user fusepy
. ~/pe/ve.fusepy/bin/activate
pip install fusepy
MB/s MB/s
@@ -60,20 +60,21 @@ def boring_log(msg):
def rice_tid(): def rice_tid():
tid = threading.current_thread().ident tid = threading.current_thread().ident
c = struct.unpack(b"B" * 5, struct.pack(b">Q", tid)[-5:]) c = struct.unpack(b"B" * 5, struct.pack(b">Q", tid)[-5:])
return "".join("\033[1;37;48;5;{}m{:02x}".format(x, x) for x in c) return "".join("\033[1;37;48;5;{}m{:02x}".format(x, x) for x in c) + "\033[0m"
def fancy_log(msg): def fancy_log(msg):
print("{}\033[0m {}\n".format(rice_tid(), msg), end="") print("{} {}\n".format(rice_tid(), msg), end="")
def null_log(msg): def null_log(msg):
pass pass
log = boring_log info = fancy_log
log = fancy_log log = fancy_log
log = threadless_log dbg = fancy_log
log = null_log
dbg = null_log dbg = null_log
@@ -118,7 +119,7 @@ class Gateway(object):
try: try:
return self.conns[tid] return self.conns[tid]
except: except:
log("new conn [{}] [{}]".format(self.web_host, self.web_port)) info("new conn [{}] [{}]".format(self.web_host, self.web_port))
conn = http.client.HTTPConnection(self.web_host, self.web_port, timeout=260) conn = http.client.HTTPConnection(self.web_host, self.web_port, timeout=260)
@@ -152,7 +153,7 @@ class Gateway(object):
if r.status != 200: if r.status != 200:
self.closeconn() self.closeconn()
raise Exception( raise Exception(
"http error {} reading dir {} in {:x}".format( "http error {} reading dir {} in {}".format(
r.status, web_path, rice_tid() r.status, web_path, rice_tid()
) )
) )
@@ -161,14 +162,14 @@ class Gateway(object):
def download_file_range(self, path, ofs1, ofs2): def download_file_range(self, path, ofs1, ofs2):
web_path = "/" + "/".join([self.web_root, path]) web_path = "/" + "/".join([self.web_root, path])
hdr_range = "bytes={}-{}".format(ofs1, ofs2) hdr_range = "bytes={}-{}".format(ofs1, ofs2 - 1)
log("downloading {}".format(hdr_range)) log("downloading {}".format(hdr_range))
r = self.sendreq("GET", self.quotep(web_path), headers={"Range": hdr_range}) r = self.sendreq("GET", self.quotep(web_path), headers={"Range": hdr_range})
if r.status != http.client.PARTIAL_CONTENT: if r.status != http.client.PARTIAL_CONTENT:
self.closeconn() self.closeconn()
raise Exception( raise Exception(
"http error {} reading file {} range {} in {:x}".format( "http error {} reading file {} range {} in {}".format(
r.status, web_path, hdr_range, rice_tid() r.status, web_path, hdr_range, rice_tid()
) )
) )
@@ -246,14 +247,14 @@ class CPPF(Operations):
self.filecache = [] self.filecache = []
self.filecache_mtx = threading.Lock() self.filecache_mtx = threading.Lock()
log("up") info("up")
def clean_dircache(self): def clean_dircache(self):
"""not threadsafe""" """not threadsafe"""
now = time.time() now = time.time()
cutoff = 0 cutoff = 0
for cn in self.dircache: for cn in self.dircache:
if cn.ts - now > 1: if now - cn.ts > 1:
cutoff += 1 cutoff += 1
else: else:
break break
@@ -398,7 +399,7 @@ class CPPF(Operations):
) )
) )
buf = self.gw.download_file_range(path, h_ofs, h_end - 1) buf = self.gw.download_file_range(path, h_ofs, h_end)
ret = buf[-buf_ofs:] + cdr ret = buf[-buf_ofs:] + cdr
elif car: elif car:
@@ -416,7 +417,7 @@ class CPPF(Operations):
) )
) )
buf = self.gw.download_file_range(path, h_ofs, h_end - 1) buf = self.gw.download_file_range(path, h_ofs, h_end)
ret = car + buf[:buf_ofs] ret = car + buf[:buf_ofs]
else: else:
@@ -438,7 +439,7 @@ class CPPF(Operations):
) )
) )
buf = self.gw.download_file_range(path, h_ofs, h_end - 1) buf = self.gw.download_file_range(path, h_ofs, h_end)
ret = buf[buf_ofs:buf_end] ret = buf[buf_ofs:buf_end]
cn = CacheNode([path, h_ofs], buf) cn = CacheNode([path, h_ofs], buf)
@@ -472,13 +473,16 @@ class CPPF(Operations):
log("read {} @ {} len {} end {}".format(path, offset, length, ofs2)) log("read {} @ {} len {} end {}".format(path, offset, length, ofs2))
file_sz = self.getattr(path)["st_size"] file_sz = self.getattr(path)["st_size"]
if ofs2 >= file_sz: if ofs2 > file_sz:
ofs2 = file_sz - 1 ofs2 = file_sz
log("truncate to len {} end {}".format((ofs2 - offset) + 1, ofs2)) log("truncate to len {} end {}".format(ofs2 - offset, ofs2))
if file_sz == 0 or offset >= ofs2:
return b""
# toggle cache here i suppose # toggle cache here i suppose
# return self.get_cached_file(path, offset, ofs2, file_sz) # return self.get_cached_file(path, offset, ofs2, file_sz)
return self.gw.download_file_range(path, offset, ofs2 - 1) return self.gw.download_file_range(path, offset, ofs2)
def getattr(self, path, fh=None): def getattr(self, path, fh=None):
path = path.strip("/") path = path.strip("/")
@@ -495,7 +499,7 @@ class CPPF(Operations):
cn = self.get_cached_dir(dirpath) cn = self.get_cached_dir(dirpath)
if cn: if cn:
# log('cache ok') log("cache ok")
dents = cn.data dents = cn.data
else: else:
log("cache miss") log("cache miss")

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (0, 4, 0) VERSION = (0, 4, 2)
CODENAME = "NIH" CODENAME = "NIH"
BUILD_DT = (2020, 5, 13) BUILD_DT = (2020, 5, 15)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -769,11 +769,18 @@ class HttpCli(object):
else: else:
upper = file_sz upper = file_sz
if lower < 0 or lower >= file_sz or upper < 0 or upper > file_sz: if upper > file_sz:
upper = file_sz
if lower < 0 or lower >= upper:
raise Exception() raise Exception()
except: except:
raise Pebkac(400, "invalid range requested: " + hrange) err = "invalid range ({}), size={}".format(hrange, file_sz)
self.loud_reply(err, status=416, headers={
"Content-Range": "bytes */{}".format(file_sz)
})
return True
status = 206 status = 206
self.out_headers["Content-Range"] = "bytes {}-{}/{}".format( self.out_headers["Content-Range"] = "bytes {}-{}/{}".format(

View File

@@ -80,8 +80,9 @@ class HttpSrv(object):
"%s %s" % addr, "%s %s" % addr,
"shut_rdwr err:\n {}\n {}".format(repr(sck), ex), "shut_rdwr err:\n {}\n {}".format(repr(sck), ex),
) )
if ex.errno not in [10038, 107, 57, 9]: if ex.errno not in [10038, 10054, 107, 57, 9]:
# 10038 No longer considered a socket # 10038 No longer considered a socket
# 10054 Foribly closed by remote
# 107 Transport endpoint not connected # 107 Transport endpoint not connected
# 57 Socket is not connected # 57 Socket is not connected
# 9 Bad file descriptor # 9 Bad file descriptor

View File

@@ -49,6 +49,7 @@ HTTPCODE = {
404: "Not Found", 404: "Not Found",
405: "Method Not Allowed", 405: "Method Not Allowed",
413: "Payload Too Large", 413: "Payload Too Large",
416: "Requested Range Not Satisfiable",
422: "Unprocessable Entity", 422: "Unprocessable Entity",
500: "Internal Server Error", 500: "Internal Server Error",
501: "Not Implemented", 501: "Not Implemented",
@@ -309,18 +310,7 @@ def get_boundary(headers):
def read_header(sr): def read_header(sr):
ret = b"" ret = b""
while True: while True:
if ret.endswith(b"\r\n\r\n"): buf = sr.recv(1024)
break
elif ret.endswith(b"\r\n\r"):
n = 1
elif ret.endswith(b"\r\n"):
n = 2
elif ret.endswith(b"\r"):
n = 3
else:
n = 4
buf = sr.recv(n)
if not buf: if not buf:
if not ret: if not ret:
return None return None
@@ -332,11 +322,15 @@ def read_header(sr):
) )
ret += buf ret += buf
ofs = ret.find(b"\r\n\r\n")
if ofs < 0:
if len(ret) > 1024 * 64:
raise Pebkac(400, "header 2big")
else:
continue
if len(ret) > 1024 * 64: sr.unrecv(ret[ofs + 4 :])
raise Pebkac(400, "header 2big") return ret[:ofs].decode("utf-8", "surrogateescape").split("\r\n")
return ret[:-4].decode("utf-8", "surrogateescape").split("\r\n")
def undot(path): def undot(path):

View File

@@ -83,6 +83,7 @@ h3 {
h1 a, h3 a, h5 a, h1 a, h3 a, h5 a,
h2 a, h4 a, h6 a { h2 a, h4 a, h6 a {
color: inherit; color: inherit;
display: block;
background: none; background: none;
border: none; border: none;
padding: 0; padding: 0;
@@ -239,7 +240,7 @@ blink {
} }
#mn.undocked { #mn.undocked {
position: fixed; position: fixed;
padding: 1.2em 0 1em 1em; padding: 1.7em 0 1.5em 1em;
box-shadow: 0 0 .5em rgba(0, 0, 0, 0.3); box-shadow: 0 0 .5em rgba(0, 0, 0, 0.3);
background: #f7f7f7; background: #f7f7f7;
} }
@@ -424,6 +425,16 @@ blink {
html.dark #mw { html.dark #mw {
scrollbar-color: #b80 #282828; scrollbar-color: #b80 #282828;
} }
html.dark #toc::-webkit-scrollbar-track {
background: #282828;
}
html.dark #toc::-webkit-scrollbar {
background: #282828;
width: .8em;
}
html.dark #toc::-webkit-scrollbar-thumb {
background: #eb0;
}
html.dark #mn.undocked { html.dark #mn.undocked {
box-shadow: 0 0 .5em #555; box-shadow: 0 0 .5em #555;
border: none; border: none;

View File

@@ -6,10 +6,35 @@ var dom_pre = document.getElementById('mp');
var dom_src = document.getElementById('mt'); var dom_src = document.getElementById('mt');
var dom_navtgl = document.getElementById('navtoggle'); var dom_navtgl = document.getElementById('navtoggle');
// chrome 49 needs this
var chromedbg = function () { console.log(arguments); }
// null-logger
var dbg = function () { };
// replace dbg with the real deal here or in the console:
// dbg = chromedbg
// dbg = console.log
function hesc(txt) { function hesc(txt) {
return txt.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); return txt.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
} }
function cls(dom, name, add) {
var re = new RegExp('(^| )' + name + '( |$)');
var lst = (dom.getAttribute('class') + '').replace(re, "$1$2").replace(/ /, "");
dom.setAttribute('class', lst + (add ? ' ' + name : ''));
}
function static(obj) {
return JSON.parse(JSON.stringify(obj));
}
// add navbar // add navbar
(function () { (function () {
var n = document.location + ''; var n = document.location + '';
@@ -28,17 +53,105 @@ function hesc(txt) {
dom_nav.innerHTML = nav.join(''); dom_nav.innerHTML = nav.join('');
})(); })();
// faster than replacing the entire html (chrome 1.8x, firefox 1.6x)
function copydom(src, dst, lv) {
var sc = src.childNodes,
dc = dst.childNodes;
if (sc.length !== dc.length) {
dbg("replace L%d (%d/%d) |%d|",
lv, sc.length, dc.length, src.innerHTML.length);
dst.innerHTML = src.innerHTML;
return;
}
var rpl = [];
for (var a = sc.length - 1; a >= 0; a--) {
var st = sc[a].tagName,
dt = dc[a].tagName;
if (st !== dt) {
dbg("replace L%d (%d/%d) type %s/%s", lv, a, sc.length, st, dt);
rpl.push(a);
continue;
}
var sa = sc[a].attributes || [],
da = dc[a].attributes || [];
if (sa.length !== da.length) {
dbg("replace L%d (%d/%d) attr# %d/%d",
lv, a, sc.length, sa.length, da.length);
rpl.push(a);
continue;
}
var dirty = false;
for (var b = sa.length - 1; b >= 0; b--) {
var name = sa[b].name,
sv = sa[b].value,
dv = dc[a].getAttribute(name);
if (name == "data-ln" && sv !== dv) {
dc[a].setAttribute(name, sv);
continue;
}
if (sv !== dv) {
dbg("replace L%d (%d/%d) attr %s [%s] [%s]",
lv, a, sc.length, name, sv, dv);
dirty = true;
break;
}
}
if (dirty)
rpl.push(a);
}
// TODO pure guessing
if (rpl.length > sc.length / 3) {
dbg("replace L%d fully, %s (%d/%d) |%d|",
lv, rpl.length, sc.length, src.innerHTML.length);
dst.innerHTML = src.innerHTML;
return;
}
// repl is reversed; build top-down
var nbytes = 0;
for (var a = rpl.length - 1; a >= 0; a--) {
var html = sc[rpl[a]].outerHTML;
dc[rpl[a]].outerHTML = html;
nbytes += html.length;
}
if (nbytes > 0)
dbg("replaced %d bytes L%d", nbytes, lv);
for (var a = 0; a < sc.length; a++)
copydom(sc[a], dc[a], lv + 1);
if (src.innerHTML !== dst.innerHTML) {
dbg("setting %d bytes L%d", src.innerHTML.length, lv);
dst.innerHTML = src.innerHTML;
}
}
function convert_markdown(md_text) { function convert_markdown(md_text) {
marked.setOptions({ marked.setOptions({
//headerPrefix: 'h-', //headerPrefix: 'h-',
breaks: true, breaks: true,
gfm: true gfm: true
}); });
var html = marked(md_text); var md_html = marked(md_text);
dom_pre.innerHTML = html; var md_dom = new DOMParser().parseFromString(md_html, "text/html").body;
// todo-lists (should probably be a marked extension) // todo-lists (should probably be a marked extension)
var nodes = dom_pre.getElementsByTagName('input'); var nodes = md_dom.getElementsByTagName('input');
for (var a = nodes.length - 1; a >= 0; a--) { for (var a = nodes.length - 1; a >= 0; a--) {
var dom_box = nodes[a]; var dom_box = nodes[a];
if (dom_box.getAttribute('type') !== 'checkbox') if (dom_box.getAttribute('type') !== 'checkbox')
@@ -58,9 +171,10 @@ function convert_markdown(md_text) {
html.substr(html.indexOf('>') + 1); html.substr(html.indexOf('>') + 1);
} }
var manip_nodes = dom_pre.getElementsByTagName('*'); // separate <code> for each line in <pre>
for (var a = manip_nodes.length - 1; a >= 0; a--) { var nodes = md_dom.getElementsByTagName('pre');
var el = manip_nodes[a]; for (var a = nodes.length - 1; a >= 0; a--) {
var el = nodes[a];
var is_precode = var is_precode =
el.tagName == 'PRE' && el.tagName == 'PRE' &&
@@ -77,18 +191,45 @@ function convert_markdown(md_text) {
el.innerHTML = lines.join(''); el.innerHTML = lines.join('');
} }
// self-link headers
var id_seen = {},
dyn = md_dom.getElementsByTagName('*');
nodes = [];
for (var a = 0, aa = dyn.length; a < aa; a++)
if (/^[Hh]([1-6])/.exec(dyn[a].tagName) !== null)
nodes.push(dyn[a]);
for (var a = 0; a < nodes.length; a++) {
el = nodes[a];
var id = el.getAttribute('id'),
orig_id = id;
if (id_seen[id]) {
for (var n = 1; n < 4096; n++) {
id = orig_id + '-' + n;
if (!id_seen[id])
break;
}
el.setAttribute('id', id);
}
id_seen[id] = 1;
el.innerHTML = '<a href="#' + id + '">' + el.innerHTML + '</a>';
}
copydom(md_dom, dom_pre, 0);
} }
function init_toc() { function init_toc() {
var loader = document.getElementById('ml'); var loader = document.getElementById('ml');
loader.parentNode.removeChild(loader); loader.parentNode.removeChild(loader);
var anchors = []; // list of toc entries, complex objects var anchors = []; // list of toc entries, complex objects
var anchor = null; // current toc node var anchor = null; // current toc node
var id_seen = {}; // taken IDs
var html = []; // generated toc html var html = []; // generated toc html
var lv = 0; // current indentation level in the toc html var lv = 0; // current indentation level in the toc html
var re = new RegExp('^[Hh]([1-3])');
var manip_nodes_dyn = dom_pre.getElementsByTagName('*'); var manip_nodes_dyn = dom_pre.getElementsByTagName('*');
var manip_nodes = []; var manip_nodes = [];
@@ -97,7 +238,7 @@ function init_toc() {
for (var a = 0, aa = manip_nodes.length; a < aa; a++) { for (var a = 0, aa = manip_nodes.length; a < aa; a++) {
var elm = manip_nodes[a]; var elm = manip_nodes[a];
var m = re.exec(elm.tagName); var m = /^[Hh]([1-6])/.exec(elm.tagName);
var is_header = m !== null; var is_header = m !== null;
if (is_header) { if (is_header) {
var nlv = m[1]; var nlv = m[1];
@@ -110,23 +251,7 @@ function init_toc() {
lv--; lv--;
} }
var orig_id = elm.getAttribute('id'); html.push('<li>' + elm.innerHTML + '</li>');
var id = orig_id;
if (id_seen[id]) {
for (var n = 1; n < 4096; n++) {
id = orig_id + '-' + n;
if (!id_seen[id])
break;
}
elm.setAttribute('id', id);
}
id_seen[id] = 1;
var ahref = '<a href="#' + id + '">' +
elm.innerHTML + '</a>';
html.push('<li>' + ahref + '</li>');
elm.innerHTML = ahref;
if (anchor != null) if (anchor != null)
anchors.push(anchor); anchors.push(anchor);

View File

@@ -10,6 +10,9 @@
} }
#mw { #mw {
left: calc(100% - 57em); left: calc(100% - 57em);
overflow-y: auto;
position: fixed;
bottom: 0;
} }
@@ -21,15 +24,17 @@
} }
#mw.preview, #mw.preview,
#mtw.editor { #mtw.editor {
z-index: 3; z-index: 5;
} }
#mtw.single, #mtw.single,
#mw.single { #mw.single {
left: calc((100% - 58em) / 2);
margin: 0; margin: 0;
left: 1em;
left: max(1em, calc((100% - 58em) / 2));
} }
#mtw.single { #mtw.single {
width: 57em; width: 57em;
width: min(57em, calc(100% - 2em));
} }
@@ -42,25 +47,30 @@
color: #444; color: #444;
background: #f7f7f7; background: #f7f7f7;
border: 1px solid #999; border: 1px solid #999;
outline: none;
padding: 0;
margin: 0;
font-family: 'consolas', monospace, monospace; font-family: 'consolas', monospace, monospace;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; /*ie*/ word-wrap: break-word; /*ie*/
overflow-y: scroll; overflow-y: scroll;
line-height: 1.3em; line-height: 1.3em;
font-size: .9em; font-size: .9em;
position: relative; position: relative;
scrollbar-color: #eb0 #f7f7f7;
} }
html.dark #mt { html.dark #mt {
color: #eee; color: #eee;
background: #222; background: #222;
border: 1px solid #777; border: 1px solid #777;
scrollbar-color: #b80 #282828;
} }
#mtr { #mtr {
position: absolute; position: absolute;
top: 1px; top: 0;
left: 1px; left: 0;
} }
#save.force-save { #save.force-save {
color: #400; color: #400;
@@ -95,8 +105,4 @@ html.dark #helpbox {
border-width: 1px 0; border-width: 1px 0;
} }
/* dbg: # mt {opacity: .5;top:1px}
#mt {
opacity: .5;
}
*/

View File

@@ -18,16 +18,12 @@ var dom_ref = (function () {
})(); })();
// replace it with the real deal in the console
var dbg = function () { };
// dbg = console.log
// line->scrollpos maps // line->scrollpos maps
var map_src = []; var map_src = [];
var map_pre = []; var map_pre = [];
function genmap(dom) { function genmap(dom) {
var ret = []; var ret = [];
var last_y = -1;
var parent_y = 0; var parent_y = 0;
var parent_n = null; var parent_n = null;
var nodes = dom.querySelectorAll('*[data-ln]'); var nodes = dom.querySelectorAll('*[data-ln]');
@@ -53,7 +49,14 @@ function genmap(dom) {
while (ln > ret.length) while (ln > ret.length)
ret.push(null); ret.push(null);
ret.push(parent_y + n.offsetTop); var y = parent_y + n.offsetTop;
if (y <= last_y)
//console.log('awawa');
continue;
//console.log('%d %d (%d+%d)', a, y, parent_y, n.offsetTop);
ret.push(y);
last_y = y;
} }
return ret; return ret;
} }
@@ -62,8 +65,10 @@ function genmap(dom) {
// input handler // input handler
var action_stack = null; var action_stack = null;
var nlines = 0; var nlines = 0;
(function () { var draw_md = (function () {
dom_src.oninput = function (e) { var delay = 1;
function draw_md() {
var t0 = new Date().getTime();
var src = dom_src.value; var src = dom_src.value;
convert_markdown(src); convert_markdown(src);
@@ -77,16 +82,22 @@ var nlines = 0;
map_src = genmap(dom_ref); map_src = genmap(dom_ref);
map_pre = genmap(dom_pre); map_pre = genmap(dom_pre);
var sb = document.getElementById('save'); cls(document.getElementById('save'), 'disabled', src == server_md);
var cl = (sb.getAttribute('class') + '').replace(/ disabled/, "");
if (src == server_md)
cl += ' disabled';
sb.setAttribute('class', cl); var t1 = new Date().getTime();
delay = t1 - t0 > 150 ? 25 : 1;
}
var timeout = null;
dom_src.oninput = function (e) {
clearTimeout(timeout);
timeout = setTimeout(draw_md, delay);
if (action_stack) if (action_stack)
action_stack.push(); action_stack.push();
} };
dom_src.oninput();
draw_md();
return draw_md;
})(); })();
@@ -96,7 +107,7 @@ redraw = (function () {
var y = (dom_hbar.offsetTop + dom_hbar.offsetHeight) + 'px'; var y = (dom_hbar.offsetTop + dom_hbar.offsetHeight) + 'px';
dom_wrap.style.top = y; dom_wrap.style.top = y;
dom_swrap.style.top = y; dom_swrap.style.top = y;
dom_ref.style.width = (dom_src.offsetWidth - 4) + 'px'; dom_ref.style.width = getComputedStyle(dom_src).offsetWidth + 'px';
map_src = genmap(dom_ref); map_src = genmap(dom_ref);
map_pre = genmap(dom_pre); map_pre = genmap(dom_pre);
dbg(document.body.clientWidth + 'x' + document.body.clientHeight); dbg(document.body.clientWidth + 'x' + document.body.clientHeight);
@@ -296,7 +307,7 @@ function save_chk() {
last_modified = this.lastmod; last_modified = this.lastmod;
server_md = this.txt; server_md = this.txt;
dom_src.oninput(); draw_md();
var ok = document.createElement('div'); 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.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');
@@ -312,13 +323,26 @@ function save_chk() {
} }
// firefox bug: initial selection offset isn't cleared properly through js
var ff_clearsel = (function () {
if (navigator.userAgent.indexOf(') Gecko/') === -1)
return function () { }
return function () {
var txt = dom_src.value;
var y = dom_src.scrollTop;
dom_src.value = '';
dom_src.value = txt;
dom_src.scrollTop = y;
};
})();
// returns car/cdr (selection bounds) and n1/n2 (grown to full lines) // returns car/cdr (selection bounds) and n1/n2 (grown to full lines)
function linebounds(just_car) { function linebounds(just_car, greedy_growth) {
var car = dom_src.selectionStart, var car = dom_src.selectionStart,
cdr = dom_src.selectionEnd; cdr = dom_src.selectionEnd;
dbg(car, cdr);
if (just_car) if (just_car)
cdr = car; cdr = car;
@@ -326,11 +350,13 @@ function linebounds(just_car) {
n1 = Math.max(car, 0), n1 = Math.max(car, 0),
n2 = Math.min(cdr, md.length - 1); n2 = Math.min(cdr, md.length - 1);
if (n1 < n2 && md[n1] == '\n') if (greedy_growth !== true) {
n1++; if (n1 < n2 && md[n1] == '\n')
n1++;
if (n1 < n2 && md[n2 - 1] == '\n') if (n1 < n2 && md[n2 - 1] == '\n')
n2 -= 2; n2 -= 2;
}
n1 = md.lastIndexOf('\n', n1 - 1) + 1; n1 = md.lastIndexOf('\n', n1 - 1) + 1;
n2 = md.indexOf('\n', n2); n2 = md.indexOf('\n', n2);
@@ -364,11 +390,8 @@ function setsel(s) {
s.cdr = s.pre.length + s.sel.length; s.cdr = s.pre.length + s.sel.length;
} }
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.setSelectionRange(s.car, s.cdr, dom_src.selectionDirection);
try { dom_src.oninput();
dom_src.oninput();
}
catch (ex) { }
} }
@@ -408,17 +431,32 @@ function md_header(dedent) {
// smart-home // smart-home
function md_home(shift) { function md_home(shift) {
var s = linebounds(!shift), var s = linebounds(false, true),
ln = s.md.substring(s.n1, s.n2), ln = s.md.substring(s.n1, s.n2),
m = /^[ \t#>+-]*(\* )?([0-9]+\. +)?/.exec(ln), dir = dom_src.selectionDirection,
home = s.n1 + m[0].length, rev = dir === 'backward',
car = (s.car == home) ? s.n1 : home, p1 = rev ? s.car : s.cdr,
cdr = shift ? s.cdr : car; p2 = rev ? s.cdr : s.car,
home = 0,
lf = ln.lastIndexOf('\n') + 1,
re = /^[ \t#>+-]*(\* )?([0-9]+\. +)?/;
if (car > cdr) if (rev)
car = [cdr, cdr = car][0]; home = s.n1 + re.exec(ln)[0].length;
else
home = s.n1 + lf + re.exec(ln.substring(lf))[0].length;
dom_src.setSelectionRange(car, cdr); p1 = (p1 !== home) ? home : (rev ? s.n1 : s.n1 + lf);
if (!shift)
p2 = p1;
if (rev !== p1 < p2)
dir = rev ? 'forward' : 'backward';
if (!shift)
ff_clearsel();
dom_src.setSelectionRange(Math.min(p1, p2), Math.max(p1, p2), dir);
} }
@@ -426,9 +464,14 @@ function md_home(shift) {
function md_newline() { 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),
m = /^[ \t#>+-]*(\* )?([0-9]+\. +)?/.exec(ln); m1 = /^( *)([0-9]+)(\. +)/.exec(ln),
m2 = /^[ \t>+-]*(\* )?/.exec(ln);
s.pre = s.md.substring(0, s.car) + '\n' + m[0]; var pre = m2[0];
if (m1 !== null)
pre = m1[1] + (parseInt(m1[2]) + 1) + m1[3];
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);
s.car = s.cdr = s.pre.length; s.car = s.cdr = s.pre.length;
@@ -436,6 +479,25 @@ 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);
var v = m[0].replace(/[^ ]/g, " ");
if (v === m[0] || v.length !== ln.length)
return true;
s.pre = s.md.substring(0, s.n1) + v;
s.sel = '';
s.post = s.md.substring(s.car);
s.car = s.cdr = s.pre.length;
setsel(s);
return false;
}
// hotkeys / toolbar // hotkeys / toolbar
(function () { (function () {
function keydown(ev) { function keydown(ev) {
@@ -476,6 +538,9 @@ function md_newline() {
action_stack.redo(); action_stack.redo();
return false; return false;
} }
if (!ctrl && !ev.shiftKey && kc == 8) {
return md_backspace();
}
} }
} }
document.onkeydown = keydown; document.onkeydown = keydown;
@@ -499,14 +564,16 @@ document.getElementById('help').onclick = function (e) {
// blame steen // blame steen
action_stack = (function () { action_stack = (function () {
var undos = []; var hist = {
var redos = []; un: [],
var sched_txt = ''; re: []
};
var sched_cpos = 0;
var sched_timer = null; var sched_timer = null;
var ignore = false; var ignore = false;
var ref = dom_src.value; var ref = dom_src.value;
function diff(from, to) { function diff(from, to, cpos) {
if (from === to) if (from === to)
return null; return null;
@@ -532,36 +599,39 @@ action_stack = (function () {
return { return {
car: car, car: car,
cdr: ++p2, cdr: ++p2,
txt: txt txt: txt,
cpos: cpos
}; };
} }
function undiff(from, change) { function undiff(from, change) {
return { return {
txt: from.substring(0, change.car) + change.txt + from.substring(change.cdr), txt: from.substring(0, change.car) + change.txt + from.substring(change.cdr),
cursor: change.car + change.txt.length cpos: change.cpos
}; };
} }
function apply(src, dst) { function apply(src, dst) {
dbg('undos(%d) redos(%d)', undos.length, redos.length); dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length);
if (src.length === 0) if (src.length === 0)
return false; return false;
var state = undiff(ref, src.pop()), var patch = src.pop(),
change = diff(ref, state.txt); applied = undiff(ref, patch),
cpos = patch.cpos - (patch.cdr - patch.car) + patch.txt.length,
reverse = diff(ref, applied.txt, cpos);
if (change === null) if (reverse === null)
return false; return false;
dst.push(change); dst.push(reverse);
ref = state.txt; ref = applied.txt;
ignore = true; // just some browsers ignore = true; // just some browsers
dom_src.value = ref; dom_src.value = ref;
dom_src.setSelectionRange(state.cursor, state.cursor); dom_src.setSelectionRange(cpos, cpos);
ignore = true; // all browsers ignore = true; // all browsers
dom_src.oninput(); draw_md();
return true; return true;
} }
@@ -570,31 +640,36 @@ action_stack = (function () {
ignore = false; ignore = false;
return; return;
} }
redos = []; hist.re = [];
sched_txt = dom_src.value;
clearTimeout(sched_timer); clearTimeout(sched_timer);
sched_cpos = dom_src.selectionEnd;
sched_timer = setTimeout(push, 500); sched_timer = setTimeout(push, 500);
} }
function undo() { function undo() {
return apply(undos, redos); if (hist.re.length == 0) {
clearTimeout(sched_timer);
push();
}
return apply(hist.un, hist.re);
} }
function redo() { function redo() {
return apply(redos, undos); return apply(hist.re, hist.un);
} }
function push() { function push() {
var change = diff(ref, sched_txt, dom_src.selectionStart); var newtxt = dom_src.value;
var change = diff(ref, newtxt, sched_cpos);
if (change !== null) if (change !== null)
undos.push(change); hist.un.push(change);
ref = sched_txt; ref = newtxt;
dbg('undos(%d) redos(%d)', undos.length, redos.length); dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length);
if (undos.length > 0) if (hist.un.length > 0)
dbg(undos.slice(-1)[0]); dbg(static(hist.un.slice(-1)[0]));
if (redos.length > 0) if (hist.re.length > 0)
dbg(redos.slice(-1)[0]); dbg(static(hist.re.slice(-1)[0]));
} }
return { return {
@@ -602,8 +677,18 @@ action_stack = (function () {
undo: undo, undo: undo,
redo: redo, redo: redo,
push: schedule_push, push: schedule_push,
_undos: undos, _hist: hist,
_redos: redos,
_ref: ref _ref: ref
} }
})(); })();
/*
document.getElementById('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);
for (var a = 0; a < max; a++)
if (c1[a] !== c2[a])
console.log(c1[a] + '\n' + c2[a]);
}
*/

View File

@@ -80,3 +80,22 @@ for d in /usr /var; do find $d -type f -size +30M 2>/dev/null; done | while IFS=
# py2 on osx # py2 on osx
brew install python@2 brew install python@2
pip install virtualenv pip install virtualenv
##
## http 206
# az = abcdefghijklmnopqrstuvwxyz
printf '%s\r\n' 'GET /az HTTP/1.1' 'Host: ocv.me' 'Range: bytes=5-10' '' | ncat ocv.me 80
# Content-Range: bytes 5-10/26
# Content-Length: 6
# fghijk
Range: bytes=0-1 "ab" Content-Range: bytes 0-1/26
Range: bytes=24-24 "y" Content-Range: bytes 24-24/26
Range: bytes=24-25 "yz" Content-Range: bytes 24-25/26
Range: bytes=24- "yz" Content-Range: bytes 24-25/26
Range: bytes=25-29 "z" Content-Range: bytes 25-25/26
Range: bytes=26- Content-Range: bytes */26
HTTP/1.1 416 Requested Range Not Satisfiable

View File

@@ -180,7 +180,7 @@ diff --git a/src/Parser.js b/src/Parser.js
+ // similar to tables, writing contents before the <ul> tag + // similar to tables, writing contents before the <ul> tag
+ // so update the tag attribute as we go + // so update the tag attribute as we go
+ // (assuming all list entries got tagged with a source-line, probably safe w) + // (assuming all list entries got tagged with a source-line, probably safe w)
+ body += this.renderer.tag_ln(item.tokens[0].ln).listitem(itemBody, task, checked); + body += this.renderer.tag_ln((item.tokens[0] || token).ln).listitem(itemBody, task, checked);
} }
- out += this.renderer.list(body, ordered, start); - out += this.renderer.list(body, ordered, start);

View File

@@ -166,3 +166,6 @@ chmod 755 $sfx_out.*
printf "done:\n" printf "done:\n"
printf " %s\n" "$(realpath $sfx_out)."{sh,py} printf " %s\n" "$(realpath $sfx_out)."{sh,py}
# rm -rf * # rm -rf *
# tar -tvf ../sfx/tar | sed -r 's/(.* ....-..-.. ..:.. )(.*)/\2 `` \1/' | sort | sed -r 's/(.*) `` (.*)/\2 \1/'| less
# for n in {1..9}; do tar -tf tar | grep -vE '/$' | sed -r 's/(.*)\.(.*)/\2.\1/' | sort | sed -r 's/([^\.]+)\.(.*)/\2.\1/' | tar -cT- | bzip2 -c$n | wc -c; done