Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09557fbe83 | ||
|
|
1c0f44fa4e | ||
|
|
fc4d59d2d7 | ||
|
|
12345fbacc | ||
|
|
2e33c8d222 | ||
|
|
db5f07f164 | ||
|
|
e050e69a43 | ||
|
|
27cb1d4fc7 | ||
|
|
5d6a740947 | ||
|
|
da3f68c363 | ||
|
|
d7d1c3685c |
13
README.md
13
README.md
@@ -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
48
bin/copyparty-fuse.py
Executable file → Normal 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")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
return txt.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user