Compare commits

..

24 Commits

Author SHA1 Message Date
ed
a7da1dd233 v0.4.3 2020-05-17 16:46:47 +02:00
ed
678ef296b4 fully hide the navbar when asked 2020-05-17 16:44:58 +02:00
ed
9e5627d805 drop opus audio support on old iOS versions 2020-05-17 16:44:17 +02:00
ed
5958ee4439 autoindent oversight 2020-05-17 08:20:54 +02:00
ed
7127e57f0e happens on macs too 2020-05-17 02:58:22 +02:00
ed
ee9c6dc8aa use marked.js v1.1.0 2020-05-17 02:28:03 +02:00
ed
92779b3f48 2x chrome editor perf 2020-05-17 00:49:49 +02:00
ed
2f1baf17d4 numbered headers for paper-prints 2020-05-17 00:33:34 +02:00
ed
583da3d4a9 actually consider paper-printing 2020-05-16 02:24:27 +02:00
ed
bf9ff78bcc autofill blank link descriptions 2020-05-16 02:19:45 +02:00
ed
2cb07792cc add monospace font 2020-05-16 02:13:34 +02:00
ed
47bc8bb466 multiprocessing adds latency; default to off 2020-05-16 02:05:18 +02:00
ed
94ad1f5732 option to list dotfiles 2020-05-16 01:40:29 +02:00
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
26 changed files with 963 additions and 534 deletions

2
.vscode/launch.json vendored
View File

@@ -9,8 +9,6 @@
"console": "integratedTerminal", "console": "integratedTerminal",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"args": [ "args": [
"-j",
"0",
//"-nw", //"-nw",
"-a", "-a",
"ed:wark", "ed:wark",

View File

@@ -19,6 +19,8 @@ turn your phone or raspi into a portable file server with resumable uploads/down
* Android-Chrome: set max "parallel uploads" for 200% upload speed (android bug) * Android-Chrome: set max "parallel uploads" for 200% upload speed (android bug)
* Android-Firefox: takes a while to select files (in order to avoid the above android-chrome issue) * Android-Firefox: takes a while to select files (in order to avoid the above android-chrome issue)
* Desktop-Firefox: may use gigabytes of RAM if your connection is great and your files are massive * Desktop-Firefox: may use gigabytes of RAM if your connection is great and your files are massive
* paper-printing is affected by dark/light-mode! use lightmode for color, darkmode for grayscale
* because no browsers currently implement the media-query to do this properly orz
## status ## status
@@ -59,13 +61,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

52
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)
@@ -146,13 +147,13 @@ class Gateway(object):
return c.getresponse() return c.getresponse()
def listdir(self, path): def listdir(self, path):
web_path = "/" + "/".join([self.web_root, path]) web_path = "/" + "/".join([self.web_root, path]) + "?dots"
r = self.sendreq("GET", self.quotep(web_path)) r = self.sendreq("GET", self.quotep(web_path))
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()
) )
) )
@@ -160,15 +161,15 @@ class Gateway(object):
return self.parse_html(r) return self.parse_html(r)
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]) + "?raw"
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

@@ -129,11 +129,14 @@ def main():
ap.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind") ap.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind")
ap.add_argument("-p", metavar="PORT", type=int, default=1234, help="port to bind") ap.add_argument("-p", metavar="PORT", type=int, default=1234, help="port to bind")
ap.add_argument("-nc", metavar="NUM", type=int, default=16, help="max num clients") ap.add_argument("-nc", metavar="NUM", type=int, default=16, help="max num clients")
ap.add_argument("-j", metavar="CORES", type=int, help="max num cpu cores") ap.add_argument(
"-j", metavar="CORES", type=int, default=1, help="max num cpu cores"
)
ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account") ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account")
ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume") ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume")
ap.add_argument("-q", action="store_true", help="quiet") ap.add_argument("-q", action="store_true", help="quiet")
ap.add_argument("-nw", action="store_true", help="benchmark: disable writing") ap.add_argument("-ed", action="store_true", help="enable ?dots")
ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
al = ap.parse_args() al = ap.parse_args()
SvcHub(al).run() SvcHub(al).run()

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (0, 4, 0) VERSION = (0, 4, 3)
CODENAME = "NIH" CODENAME = "NIH"
BUILD_DT = (2020, 5, 13) BUILD_DT = (2020, 5, 17)
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

@@ -29,7 +29,7 @@ class BrokerMp(object):
self.mutex = threading.Lock() self.mutex = threading.Lock()
cores = self.args.j cores = self.args.j
if cores is None: if not cores:
cores = mp.cpu_count() cores = mp.cpu_count()
self.log("broker", "booting {} subprocesses".format(cores)) self.log("broker", "booting {} subprocesses".format(cores))

View File

@@ -769,11 +769,20 @@ 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(
@@ -941,9 +950,13 @@ class HttpCli(object):
except: except:
pass pass
# show dotfiles if permitted and requested
if not self.args.ed or "dots" not in self.uparam:
vfs_ls = exclude_dotfiles(vfs_ls)
dirs = [] dirs = []
files = [] files = []
for fn in exclude_dotfiles(vfs_ls): for fn in vfs_ls:
base = "" base = ""
href = fn href = fn
if self.absolute_urls and vpath: if self.absolute_urls and vpath:

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

@@ -129,8 +129,8 @@ class SvcHub(object):
return None return None
def check_mp_enable(self): def check_mp_enable(self):
if self.args.j == 0: if self.args.j == 1:
self.log("root", "multiprocessing disabled by argument -j 0;") self.log("root", "multiprocessing disabled by argument -j 1;")
return False return False
if mp.cpu_count() <= 1: if mp.cpu_count() <= 1:

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

@@ -33,7 +33,7 @@
<tr> <tr>
<th></th> <th></th>
<th>File Name</th> <th>File Name</th>
<th>File Size</th> <th sort="int">File Size</th>
<th>Date</th> <th>Date</th>
</tr> </tr>
</thead> </thead>

View File

@@ -106,7 +106,9 @@ function makeSortable(table) {
if (th) i = th.length; if (th) i = th.length;
else return; // if no `<thead>` then do nothing else return; // if no `<thead>` then do nothing
while (--i >= 0) (function (i) { while (--i >= 0) (function (i) {
th[i].addEventListener('click', function () { sortTable(table, i) }); th[i].onclick = function () {
sortTable(table, i);
};
}(i)); }(i));
} }
makeSortable(o('files')); makeSortable(o('files'));
@@ -123,7 +125,6 @@ var mp = (function () {
'cover_url': '' 'cover_url': ''
}; };
var re_audio = new RegExp('\.(opus|ogg|m4a|aac|mp3|wav|flac)$', 'i'); var re_audio = new RegExp('\.(opus|ogg|m4a|aac|mp3|wav|flac)$', 'i');
var re_cover = new RegExp('^(cover|folder|cd|front|back)\.(jpe?g|png|gif)$', 'i');
var trs = document.getElementById('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr'); var trs = document.getElementById('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
for (var a = 0, aa = trs.length; a < aa; a++) { for (var a = 0, aa = trs.length; a < aa; a++) {
@@ -414,15 +415,6 @@ var vbar = (function () {
var x = e.clientX - rect.left; var x = e.clientX - rect.left;
var mul = x * 1.0 / rect.width; var mul = x * 1.0 / rect.width;
/*
dbg(//Math.round(rect.width) + 'x' + Math.round(rect.height) + '+' +
//Math.round(rect.left) + '+' + Math.round(rect.top) + ', ' +
//Math.round(e.clientX) + 'x' + Math.round(e.clientY) + ', ' +
Math.round(mp.au.currentTime * 10) / 10 + ', ' +
Math.round(mp.au.duration * 10) / 10 + '*' +
Math.round(mul * 1000) / 1000);
*/
mp.au.currentTime = mp.au.duration * mul; mp.au.currentTime = mp.au.duration * mul;
if (mp.au === mp.au_native) if (mp.au === mp.au_native)
@@ -483,8 +475,14 @@ function setclass(id, clas) {
} }
var iOS = !!navigator.platform && var need_ogv = true;
/iPad|iPhone|iPod/.test(navigator.platform); try {
need_ogv = new Audio().canPlayType('audio/ogg; codecs=opus') !== 'probably';
if (/ Edge\//.exec(navigator.userAgent + ''))
need_ogv = true;
}
catch (ex) { }
// plays the tid'th audio file on the page // plays the tid'th audio file on the page
@@ -507,7 +505,7 @@ function play(tid, call_depth) {
var hack_attempt_play = true; var hack_attempt_play = true;
var url = mp.tracks[tid]; var url = mp.tracks[tid];
if (iOS && /\.(ogg|opus)$/i.test(url)) { if (need_ogv && /\.(ogg|opus)$/i.test(url)) {
if (mp.au_ogvjs) { if (mp.au_ogvjs) {
mp.au = mp.au_ogvjs; mp.au = mp.au_ogvjs;
} }
@@ -594,7 +592,6 @@ function evau_error(e) {
err += '\n\nFile: «' + decodeURIComponent(eplaya.src.split('/').slice(-1)[0]) + '»'; err += '\n\nFile: «' + decodeURIComponent(eplaya.src.split('/').slice(-1)[0]) + '»';
alert(err); alert(err);
play(eplaya.tid + 1);
} }
@@ -613,7 +610,7 @@ function show_modal(html) {
function unblocked() { function unblocked() {
var dom = o('blocked'); var dom = o('blocked');
if (dom) if (dom)
dom.remove(); dom.parentNode.removeChild(dom);
} }

View File

@@ -1,3 +1,7 @@
@font-face {
font-family: 'scp';
src: local('Source Code Pro Regular'), local('SourceCodePro-Regular'), url(/.cpr/deps/scp.woff2) format('woff2');
}
html, body { html, body {
color: #333; color: #333;
background: #eee; background: #eee;
@@ -9,6 +13,7 @@ html, body {
} }
#mw { #mw {
margin: 0 auto; margin: 0 auto;
padding: 0 1.5em;
} }
pre, code, a { pre, code, a {
color: #480; color: #480;
@@ -22,7 +27,7 @@ code {
font-size: .96em; font-size: .96em;
} }
pre, code { pre, code {
font-family: monospace, monospace; font-family: 'scp', monospace, monospace;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-all;
} }
@@ -42,7 +47,7 @@ pre code {
pre code:last-child { pre code:last-child {
border-bottom: none; border-bottom: none;
} }
pre code:before { pre code::before {
content: counter(precode); content: counter(precode);
-webkit-user-select: none; -webkit-user-select: none;
display: inline-block; display: inline-block;
@@ -83,6 +88,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;
@@ -167,14 +173,12 @@ small {
} }
table { table {
border-collapse: collapse; border-collapse: collapse;
margin: 1em 0;
} }
td { th, td {
padding: .2em .5em; padding: .2em .5em;
border: .12em solid #aaa; border: .12em solid #aaa;
} }
th {
border: .12em solid #aaa;
}
blink { blink {
animation: blinker .7s cubic-bezier(.9, 0, .1, 1) infinite; animation: blinker .7s cubic-bezier(.9, 0, .1, 1) infinite;
} }
@@ -197,13 +201,15 @@ blink {
height: 100%; height: 100%;
} }
#mw { #mw {
padding: 0 1em;
margin: 0 auto; margin: 0 auto;
right: 0; right: 0;
} }
#mp { #mp {
max-width: 54em; max-width: 52em;
margin-bottom: 6em; margin-bottom: 6em;
word-break: break-word;
overflow-wrap: break-word;
word-wrap: break-word; /*ie*/
} }
a { a {
color: #fff; color: #fff;
@@ -237,12 +243,6 @@ blink {
z-index: 10; z-index: 10;
width: calc(100% - 1em); width: calc(100% - 1em);
} }
#mn.undocked {
position: fixed;
padding: 1.2em 0 1em 1em;
box-shadow: 0 0 .5em rgba(0, 0, 0, 0.3);
background: #f7f7f7;
}
#mn a { #mn a {
color: #444; color: #444;
background: none; background: none;
@@ -260,7 +260,7 @@ blink {
#mn a:last-child { #mn a:last-child {
padding-right: .5em; padding-right: .5em;
} }
#mn a:not(:last-child):after { #mn a:not(:last-child)::after {
content: ''; content: '';
width: 1.05em; width: 1.05em;
height: 1.05em; height: 1.05em;
@@ -354,7 +354,7 @@ blink {
background: #282828; background: #282828;
border: .07em dashed #444; border: .07em dashed #444;
} }
html.dark #mn a:not(:last-child):after { html.dark #mn a:not(:last-child)::after {
border-color: rgba(255,255,255,0.3); border-color: rgba(255,255,255,0.3);
} }
html.dark #mn a { html.dark #mn a {
@@ -373,19 +373,19 @@ blink {
} }
} }
@media screen and (min-width: 70em) { @media screen and (min-width: 66em) {
#mw { #mw {
position: fixed; position: fixed;
overflow-y: auto; overflow-y: auto;
left: 14em; left: 14em;
left: calc(100% - 57em); left: calc(100% - 55em);
max-width: none; max-width: none;
bottom: 0; bottom: 0;
scrollbar-color: #eb0 #f7f7f7; scrollbar-color: #eb0 #f7f7f7;
} }
#toc { #toc {
width: 13em; width: 13em;
width: calc(100% - 57.3em); width: calc(100% - 55.3em);
max-width: 30em; max-width: 30em;
background: #eee; background: #eee;
position: fixed; position: fixed;
@@ -424,32 +424,126 @@ blink {
html.dark #mw { html.dark #mw {
scrollbar-color: #b80 #282828; scrollbar-color: #b80 #282828;
} }
html.dark #mn.undocked { html.dark #toc::-webkit-scrollbar-track {
box-shadow: 0 0 .5em #555; background: #282828;
border: none; }
background: #0a0a0a; html.dark #toc::-webkit-scrollbar {
background: #282828;
width: .8em;
}
html.dark #toc::-webkit-scrollbar-thumb {
background: #b80;
} }
} }
@media screen and (min-width: 87.5em) { @media screen and (min-width: 85.5em) {
#toc { width: 30em } #toc { width: 30em }
#mw { left: 30.5em } #mw { left: 30.5em }
} }
@media print { @media print {
@page {
size: A4;
padding: 0;
margin: .5in .6in;
mso-header-margin: .6in;
mso-footer-margin: .6in;
mso-paper-source: 0;
}
a { a {
color: #079; color: #079;
text-decoration: none; text-decoration: none;
border-bottom: .07em solid #4ac; border-bottom: .07em solid #4ac;
padding: 0 .3em; padding: 0 .3em;
} }
#toc {
margin: 0 !important;
}
#toc>ul { #toc>ul {
border-left: .1em solid #84c4dd; border-left: .1em solid #84c4dd;
} }
#mn, #mh { #mn, #mh {
display: none; display: none;
} }
html, body, #toc, #mw {
margin: 0 !important;
word-break: break-word;
width: 52em;
}
#toc {
margin-left: 1em !important;
}
#toc a {
color: #000 !important;
}
#toc a::after {
/* hopefully supported by browsers eventually */
content: leader('.') target-counter(attr(href), page);
}
a[ctr]::before {
content: attr(ctr) '. ';
}
h1 {
margin: 2em 0;
}
h2 {
margin: 2em 0 0 0;
}
h1, h2, h3 {
page-break-inside: avoid;
}
h1::after,
h2::after,
h3::after {
content: 'orz';
color: transparent;
display: block;
line-height: 1em;
padding: 4em 0 0 0;
margin: 0 0 -5em 0;
}
p {
page-break-inside: avoid;
}
table {
page-break-inside: auto;
}
tr {
page-break-inside: avoid;
page-break-after: auto;
}
thead {
display: table-header-group;
}
tfoot {
display: table-footer-group;
}
#mp a.vis::after {
content: ' (' attr(href) ')';
border-bottom: 1px solid #bbb;
color: #444;
}
blockquote {
border-color: #555;
}
code {
border-color: #bbb;
}
pre, pre code {
border-color: #999;
}
pre code::before {
color: #058;
}
html.dark a {
color: #000;
}
html.dark pre,
html.dark code {
color: #240;
}
html.dark p>em,
html.dark li>em {
color: #940;
}
} }
/* /*

View File

@@ -21,6 +21,7 @@
{%- 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>
<a href="?raw">view raw</a>
{%- endif %} {%- endif %}
</div> </div>
<div id="toc"></div> <div id="toc"></div>
@@ -41,14 +42,14 @@
<div id="helpbox"> <div id="helpbox">
<textarea> <textarea>
write markdown (html is permitted) write markdown (most html is 🙆 too)
### hotkey list ## hotkey list
* `Ctrl-S` to save * `Ctrl-S` to save
* `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
### toolbar ## toolbar
1. toggle dark mode 1. toggle dark mode
2. show/hide navigation bar 2. show/hide navigation bar
3. save changes on server 3. save changes on server
@@ -56,8 +57,56 @@ write markdown (html is permitted)
5. toggle editor/preview 5. toggle editor/preview
6. this thing :^) 6. this thing :^)
. ## markdown
|||
|--|--|
|`**bold**`|**bold**|
|`_italic_`|_italic_|
|`~~strike~~`|~~strike~~|
|`` `code` ``|`code`|
|`[](#hotkey-list)`|[](#hotkey-list)|
|`[](/foo/bar.md#header)`|[](/foo/bar.md#header)|
|`<blink>💯</blink>`|<blink>💯</blink>|
## tables
|left-aligned|centered|right-aligned
| ---------- | :----: | ----------:
|one |two |three
|left-aligned|centered|right-aligned
| ---------- | :----: | ----------:
|one |two |three
## lists
* one
* two
1. one
1. two
* one
* two
1. one
1. two
## headers
# level 1
## level 2
### level 3
## quote
> hello
> hello
## codeblock
four spaces (no tab pls)
## code in lists
* foo
bar
six spaces total
* foo
bar
six spaces total
.
</textarea> </textarea>
</div> </div>
{%- endif %} {%- endif %}

View File

@@ -6,10 +6,48 @@ 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));
}
// dodge browser issues
(function () {
var ua = navigator.userAgent;
if (ua.indexOf(') Gecko/') !== -1 && /Linux| Mac /.exec(ua)) {
// necessary on ff-68.7 at least
var s = document.createElement('style');
s.innerHTML = '@page { margin: .5in .6in .8in .6in; }';
console.log(s.innerHTML);
document.head.appendChild(s);
}
})();
// add navbar // add navbar
(function () { (function () {
var n = document.location + ''; var n = document.location + '';
@@ -28,17 +66,116 @@ function hesc(txt) {
dom_nav.innerHTML = nav.join(''); dom_nav.innerHTML = nav.join('');
})(); })();
function convert_markdown(md_text) {
// 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, dest_dom) {
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;
var nodes = md_dom.getElementsByTagName('a');
for (var a = nodes.length - 1; a >= 0; a--) {
var href = nodes[a].getAttribute('href');
var txt = nodes[a].textContent;
if (!txt)
nodes[a].textContent = href;
else if (href !== txt)
nodes[a].setAttribute('class', 'vis');
}
// todo-lists (should probably be a marked extension) // todo-lists (should probably be a marked extension)
var nodes = dom_pre.getElementsByTagName('input'); 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 +195,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 +215,46 @@ 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, dest_dom, 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 ctr = [0, 0, 0, 0, 0, 0];
var manip_nodes_dyn = dom_pre.getElementsByTagName('*'); var manip_nodes_dyn = dom_pre.getElementsByTagName('*');
var manip_nodes = []; var manip_nodes = [];
@@ -97,7 +263,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];
@@ -109,24 +275,13 @@ function init_toc() {
html.push('</ul>'); html.push('</ul>');
lv--; lv--;
} }
ctr[lv - 1]++;
for (var b = lv; b < 6; b++)
ctr[b] = 0;
var orig_id = elm.getAttribute('id'); elm.childNodes[0].setAttribute('ctr', ctr.slice(0, lv).join('.'));
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 + '">' + html.push('<li>' + elm.innerHTML + '</li>');
elm.innerHTML + '</a>';
html.push('<li>' + ahref + '</li>');
elm.innerHTML = ahref;
if (anchor != null) if (anchor != null)
anchors.push(anchor); anchors.push(anchor);
@@ -208,7 +363,7 @@ function init_toc() {
// "main" :p // "main" :p
convert_markdown(dom_src.value); convert_markdown(dom_src.value, dom_pre);
var toc = init_toc(); var toc = init_toc();
@@ -240,40 +395,10 @@ var redraw = (function () {
dom_navtgl.onclick = function () { dom_navtgl.onclick = function () {
var timeout = null;
function show_nav(e) {
if (e && e.target == dom_hbar && e.pageX && e.pageX < dom_hbar.offsetWidth / 2)
return;
clearTimeout(timeout);
dom_nav.style.display = 'block';
}
function hide_nav() {
clearTimeout(timeout);
timeout = setTimeout(function () {
dom_nav.style.display = 'none';
}, 30);
}
var hidden = dom_navtgl.innerHTML == 'hide nav'; var hidden = dom_navtgl.innerHTML == 'hide nav';
dom_navtgl.innerHTML = hidden ? 'show nav' : 'hide nav'; dom_navtgl.innerHTML = hidden ? 'show nav' : 'hide nav';
if (hidden) { dom_nav.style.display = hidden ? 'none' : 'block';
dom_nav.setAttribute('class', 'undocked');
dom_nav.style.display = 'none';
dom_nav.style.top = dom_hbar.offsetHeight + 'px';
dom_nav.onmouseenter = show_nav;
dom_nav.onmouseleave = hide_nav;
dom_hbar.onmouseenter = show_nav;
dom_hbar.onmouseleave = hide_nav;
}
else {
dom_nav.setAttribute('class', '');
dom_nav.style.display = 'block';
dom_nav.style.top = '0';
dom_nav.onmouseenter = null;
dom_nav.onmouseleave = null;
dom_hbar.onmouseenter = null;
dom_hbar.onmouseleave = null;
}
if (window.localStorage) if (window.localStorage)
localStorage.setItem('hidenav', hidden ? 1 : 0); localStorage.setItem('hidenav', hidden ? 1 : 0);

View File

@@ -4,12 +4,15 @@
#mtw { #mtw {
display: block; display: block;
position: fixed; position: fixed;
left: 0; left: .5em;
bottom: 0; bottom: 0;
width: calc(100% - 58em); width: calc(100% - 56em);
} }
#mw { #mw {
left: calc(100% - 57em); left: calc(100% - 55em);
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% - 56em) / 2));
} }
#mtw.single { #mtw.single {
width: 57em; width: 55em;
width: min(55em, calc(100% - 2em));
} }
@@ -38,29 +43,34 @@
} }
#mt, #mtr { #mt, #mtr {
width: 100%; width: 100%;
height: calc(100% - 5px); height: calc(100% - 1px);
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,19 +18,13 @@ 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 = []; function genmapq(dom, query) {
var map_pre = [];
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(query);
for (var a = 0; a < nodes.length; a++) { for (var a = 0; a < nodes.length; a++) {
var n = nodes[a]; var n = nodes[a];
var ln = parseInt(n.getAttribute('data-ln')); var ln = parseInt(n.getAttribute('data-ln'));
@@ -39,7 +33,7 @@ function genmap(dom) {
var y = 0; var y = 0;
var par = n.offsetParent; var par = n.offsetParent;
if (par != parent_n) { if (par && par != parent_n) {
while (par && par != dom) { while (par && par != dom) {
y += par.offsetTop; y += par.offsetTop;
par = par.offsetParent; par = par.offsetParent;
@@ -53,19 +47,47 @@ function genmap(dom) {
while (ln > ret.length) while (ln > ret.length)
ret.push(null); ret.push(null);
ret.push(parent_y + n.offsetTop); 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;
} }
var map_src = [];
var map_pre = [];
function genmap(dom, oldmap) {
var find = nlines;
while (oldmap && find --> 0) {
var tmap = genmapq(dom, '*[data-ln="' + find + '"]');
if (!tmap || !tmap.length)
continue;
var cy = tmap[find];
var oy = parseInt(oldmap[find]);
if (cy + 24 > oy && cy - 24 < oy)
return oldmap;
console.log('map regen', dom.getAttribute('id'), find, oy, cy, oy - cy);
break;
}
return genmapq(dom, '*[data-ln]');
}
// 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, dom_pre);
var lines = hesc(src).replace(/\r/g, "").split('\n'); var lines = hesc(src).replace(/\r/g, "").split('\n');
nlines = lines.length; nlines = lines.length;
@@ -74,19 +96,25 @@ var nlines = 0;
html.push('<span data-ln="' + (a + 1) + '">' + lines[a] + "</span>"); html.push('<span data-ln="' + (a + 1) + '">' + lines[a] + "</span>");
dom_ref.innerHTML = html.join('\n'); dom_ref.innerHTML = html.join('\n');
map_src = genmap(dom_ref); map_src = genmap(dom_ref, map_src);
map_pre = genmap(dom_pre); map_pre = genmap(dom_pre, map_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 > 100 ? 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,9 +124,9 @@ 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_src);
map_pre = genmap(dom_pre); map_pre = genmap(dom_pre, map_pre);
dbg(document.body.clientWidth + 'x' + document.body.clientHeight); dbg(document.body.clientWidth + 'x' + document.body.clientHeight);
} }
function setsbs() { function setsbs() {
@@ -296,7 +324,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 +340,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 +367,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 +407,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 +448,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,13 +481,42 @@ 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];
if (pre.length > s.car - s.n1)
// in gutter, do nothing
return true;
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;
setsel(s); setsel(s);
return false;
}
// 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;
} }
@@ -461,8 +545,7 @@ function md_newline() {
return false; return false;
} }
if (!ctrl && !ev.shiftKey && (ev.code == "Enter" || kc == 13)) { if (!ctrl && !ev.shiftKey && (ev.code == "Enter" || kc == 13)) {
md_newline(); return md_newline();
return false;
} }
if (ctrl && (ev.code == "KeyZ" || kc == 90)) { if (ctrl && (ev.code == "KeyZ" || kc == 90)) {
if (ev.shiftKey) if (ev.shiftKey)
@@ -476,6 +559,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;
@@ -487,8 +573,10 @@ document.getElementById('help').onclick = function (e) {
if (e) e.preventDefault(); if (e) e.preventDefault();
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) {
dom.innerHTML = '<a href="#" id="helpclose">close</a>' + marked(dtxt[0].value); convert_markdown(dtxt[0].value, dom);
dom.innerHTML = '<a href="#" id="helpclose">close</a>' + dom.innerHTML;
}
dom.style.display = 'block'; dom.style.display = 'block';
document.getElementById('helpclose').onclick = function () { document.getElementById('helpclose').onclick = function () {
@@ -499,14 +587,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,34 +622,37 @@ 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(); dom_src.oninput();
return true; return true;
@@ -570,31 +663,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 +700,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

@@ -39,6 +39,6 @@ var lightswitch = (function () {
})(); })();
</script> </script>
<script src="/.cpr/deps/easymde.full.js"></script> <script src="/.cpr/deps/easymde.js"></script>
<script src="/.cpr/mde.js"></script> <script src="/.cpr/mde.js"></script>
</body></html> </body></html>

View File

@@ -80,3 +80,36 @@ 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
##
## md perf
var tsh = [];
function convert_markdown(md_text, dest_dom) {
tsh.push(new Date().getTime());
while (tsh.length > 10)
tsh.shift();
if (tsh.length > 1) {
var end = tsh.slice(-2);
console.log("render", end.pop() - end.pop(), (tsh[tsh.length - 1] - tsh[0]) / (tsh.length - 1));
}

10
docs/unirange.py Normal file
View File

@@ -0,0 +1,10 @@
v = "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
for v in v.split(","):
if "+" in v:
v = v.split("+")[1]
if "-" in v:
lo, hi = v.split("-")
else:
lo = hi = v
for v in range(int(lo, 16), int(hi, 16) + 1):
print("{:4x} [{}]".format(v, chr(v)))

View File

@@ -3,7 +3,7 @@ WORKDIR /z
ENV ver_asmcrypto=2821dd1dedd1196c378f5854037dda5c869313f3 \ ENV ver_asmcrypto=2821dd1dedd1196c378f5854037dda5c869313f3 \
ver_markdownit=10.0.0 \ ver_markdownit=10.0.0 \
ver_showdown=1.9.1 \ ver_showdown=1.9.1 \
ver_marked=1.0.0 \ ver_marked=1.1.0 \
ver_ogvjs=1.6.1 \ ver_ogvjs=1.6.1 \
ver_mde=2.10.1 \ ver_mde=2.10.1 \
ver_codemirror=5.53.2 \ ver_codemirror=5.53.2 \
@@ -11,8 +11,11 @@ ENV ver_asmcrypto=2821dd1dedd1196c378f5854037dda5c869313f3 \
ver_zopfli=1.0.3 ver_zopfli=1.0.3
# download # download;
RUN apk add cmake make g++ git bash npm patch wget tar pigz brotli gzip unzip python3 python3-dev \ # the scp url is latin from https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap
RUN mkdir -p /z/dist/no-pk \
&& wget https://fonts.gstatic.com/s/sourcecodepro/v11/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2 -O scp.woff2 \
&& apk add cmake make g++ git bash npm patch wget tar pigz brotli gzip unzip python3 python3-dev brotli py3-brotli \
&& wget https://github.com/brion/ogv.js/releases/download/$ver_ogvjs/ogvjs-$ver_ogvjs.zip -O ogvjs.zip \ && wget https://github.com/brion/ogv.js/releases/download/$ver_ogvjs/ogvjs-$ver_ogvjs.zip -O ogvjs.zip \
&& wget https://github.com/asmcrypto/asmcrypto.js/archive/$ver_asmcrypto.tar.gz -O asmcrypto.tgz \ && wget https://github.com/asmcrypto/asmcrypto.js/archive/$ver_asmcrypto.tar.gz -O asmcrypto.tgz \
&& wget https://github.com/markedjs/marked/archive/v$ver_marked.tar.gz -O marked.tgz \ && wget https://github.com/markedjs/marked/archive/v$ver_marked.tar.gz -O marked.tgz \
@@ -36,23 +39,7 @@ RUN apk add cmake make g++ git bash npm patch wget tar pigz brotli gzip unzi
&& npm install \ && npm install \
&& npm i gulp-cli -g ) \ && npm i gulp-cli -g ) \
&& unzip fontawesome.zip \ && unzip fontawesome.zip \
&& tar -xf zopfli.tgz \ && tar -xf zopfli.tgz
&& mkdir -p /z/dist/no-pk
# uncomment if you wanna test the abandoned markdown converters
#ENV build_abandoned=1
RUN [ $build_abandoned ] || exit 0; \
git clone --depth 1 --branch $ver_showdown https://github.com/showdownjs/showdown/ \
&& wget https://github.com/markdown-it/markdown-it/archive/$ver_markdownit.tar.gz -O markdownit.tgz \
&& (cd showdown \
&& npm install \
&& npm i grunt -g ) \
&& (tar -xf markdownit.tgz \
&& cd markdown-it-$ver_markdownit \
&& npm install )
# build fonttools (which needs zopfli) # build fonttools (which needs zopfli)
@@ -80,31 +67,27 @@ RUN cd ogvjs-$ver_ogvjs \
&& cp -pv \ && cp -pv \
ogv.js \ ogv.js \
ogv-worker-audio.js \ ogv-worker-audio.js \
ogv-demuxer-ogg.js \
ogv-demuxer-ogg-wasm.js \ ogv-demuxer-ogg-wasm.js \
ogv-demuxer-ogg-wasm.wasm \ ogv-demuxer-ogg-wasm.wasm \
ogv-demuxer-webm.js \
ogv-demuxer-webm-wasm.js \ ogv-demuxer-webm-wasm.js \
ogv-demuxer-webm-wasm.wasm \ ogv-demuxer-webm-wasm.wasm \
ogv-decoder-audio-opus.js \
ogv-decoder-audio-opus-wasm.js \ ogv-decoder-audio-opus-wasm.js \
ogv-decoder-audio-opus-wasm.wasm \ ogv-decoder-audio-opus-wasm.wasm \
ogv-decoder-audio-vorbis.js \
ogv-decoder-audio-vorbis-wasm.js \ ogv-decoder-audio-vorbis-wasm.js \
ogv-decoder-audio-vorbis-wasm.wasm \ ogv-decoder-audio-vorbis-wasm.wasm \
dynamicaudio.swf \
/z/dist /z/dist
# ogv-demuxer-ogg.js \
# ogv-demuxer-webm.js \
# ogv-decoder-audio-opus.js \
# ogv-decoder-audio-vorbis.js \
# dynamicaudio.swf \
# build marked # build marked
RUN wget https://github.com/markedjs/marked/commit/5c166d4164791f643693478e4ac094d63d6e0c9a.patch -O marked-git-1.patch \
&& wget https://patch-diff.githubusercontent.com/raw/markedjs/marked/pull/1652.patch -O marked-git-2.patch
COPY marked.patch /z/ COPY marked.patch /z/
COPY marked-ln.patch /z/ COPY marked-ln.patch /z/
RUN cd marked-$ver_marked \ RUN cd marked-$ver_marked \
&& patch -p1 < /z/marked-git-1.patch \
&& patch -p1 < /z/marked-git-2.patch \
&& patch -p1 < /z/marked-ln.patch \ && patch -p1 < /z/marked-ln.patch \
&& patch -p1 < /z/marked.patch \ && patch -p1 < /z/marked.patch \
&& npm run build \ && npm run build \
@@ -138,57 +121,10 @@ RUN cd easy-markdown-editor-$ver_mde \
&& patch -p1 < /z/easymde-ln.patch \ && patch -p1 < /z/easymde-ln.patch \
&& gulp \ && gulp \
&& cp -pv dist/easymde.min.css /z/dist/easymde.css \ && cp -pv dist/easymde.min.css /z/dist/easymde.css \
&& cp -pv dist/easymde.min.js /z/dist/easymde.js \ && cp -pv dist/easymde.min.js /z/dist/easymde.js
&& sed -ri '/pipe.terser/d; /cleanCSS/d' gulpfile.js \
&& gulp \
&& cp -pv dist/easymde.min.css /z/dist/easymde.full.css \
&& cp -pv dist/easymde.min.js /z/dist/easymde.full.js
# build showdown (abandoned; disabled by default) # build fontawesome and scp
COPY showdown.patch /z/
RUN [ $build_abandoned ] || exit 0; \
cd showdown \
&& rm -rf bin dist \
# # remove ellipsis plugin \
&& rm \
src/subParsers/ellipsis.js \
test/cases/ellipsis* \
# # remove html-to-md converter \
&& rm \
test/node/testsuite.makemd.js \
test/node/showdown.Converter.makeMarkdown.js \
# # remove emojis \
&& rm src/subParsers/emoji.js \
&& awk '/^showdown.helper.emojis/ {o=1} !o; /^\}/ {o=0}' \
>f <src/helpers.js \
&& mv f src/helpers.js \
&& rm -rf test/features/emojis \
# # remove ghmentions \
&& rm test/features/ghMentions.* \
# # remove option descriptions \
&& sed -ri '/descri(ption|be): /d' src/options.js \
&& patch -p1 < /z/showdown.patch
RUN [ $build_abandoned ] || exit 0; \
cd showdown \
&& grunt build \
&& sed -ri '/sourceMappingURL=showdown.min.js.map/d' dist/showdown.min.js \
&& mv dist/showdown.min.js /z/dist/showdown.js \
&& ls -al /z/dist/showdown.js
# build markdownit (abandoned; disabled by default)
COPY markdown-it.patch /z/
RUN [ $build_abandoned ] || exit 0; \
cd markdown-it-$ver_markdownit \
&& patch -p1 < /z/markdown-it.patch \
&& make browserify \
&& cp -pv dist/markdown-it.min.js /z/dist/markdown-it.js \
&& cp -pv dist/markdown-it.js /z/dist/markdown-it-full.js
# build fontawesome
COPY mini-fa.sh /z COPY mini-fa.sh /z
COPY mini-fa.css /z COPY mini-fa.css /z
RUN /bin/ash /z/mini-fa.sh RUN /bin/ash /z/mini-fa.sh
@@ -203,38 +139,6 @@ RUN cd /z/dist \
&& rmdir no-pk && rmdir no-pk
# showdown: abandoned due to code-blocks in lists failing # git diff -U2 --no-index marked-1.1.0-orig/ marked-1.1.0-edit/ -U2 | sed -r '/^index /d;s`^(diff --git a/)[^/]+/(.* b/)[^/]+/`\1\2`; s`^(---|\+\+\+) ([ab]/)[^/]+/`\1 \2`' > ../dev/copyparty/scripts/deps-docker/marked-ln.patch
# 22770 orig # d=/home/ed/dev/copyparty/scripts/deps-docker/; tar -cf ../x . && ssh root@$bip "cd $d && tar -xv >&2 && make >&2 && tar -cC ../../copyparty/web deps" <../x | (cd ../../copyparty/web/; cat > the.tgz; tar -xvf the.tgz; rm the.tgz)
# 12154 no-emojis
# 12134 no-srcmap
# 11189 no-descriptions
# 11152 no-ellipsis
# 10617 no-this.makeMd
# 9569 no-extensions
# 9537 no-extensions
# 9410 no-mentions
# markdown-it: abandoned because no header anchors (and too big)
# 32322 107754 orig (wowee)
# 19619 21392 71540 less entities
# marked:
# 9253 29773 orig
# 9159 29633 no copyright (reverted)
# 9040 29057 no sanitize
# 8870 28631 no email-mangle
# so really not worth it, just drop the patch when that stops working
# easymde:
# 91836 orig
# 88635 no spellcheck
# 88392 no urlRE
# 85651 less bidi
# 82855 less mode meta
# d=/home/ed/dev/copyparty/scripts/deps-docker/; tar -cf ../x . && ssh root@$bip "cd $d && tar -xv >&2 && make >&2 && tar -cC ../../copyparty/web deps" <../x | (cd ../../copyparty/web/; cat > the.tgz; tar -xvf the.tgz)
# gzip -dkf ../dev/copyparty/copyparty/web/deps/deps/marked.full.js.gz && diff -NarU2 ../dev/copyparty/copyparty/web/deps/{,deps/}marked.full.js # gzip -dkf ../dev/copyparty/copyparty/web/deps/deps/marked.full.js.gz && diff -NarU2 ../dev/copyparty/copyparty/web/deps/{,deps/}marked.full.js

View File

@@ -35,7 +35,7 @@ add data-ln="%d" to most tags, %d is the source markdown line
+ // this.ln will be bumped by recursive calls into this func; + // this.ln will be bumped by recursive calls into this func;
+ // reset the count and rely on the outermost token's raw only + // reset the count and rely on the outermost token's raw only
+ ln = this.ln; + ln = this.ln;
+ +
// newline // newline
if (token = this.tokenizer.space(src)) { if (token = this.tokenizer.space(src)) {
src = src.substring(token.raw.length); src = src.substring(token.raw.length);
@@ -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);
@@ -234,7 +234,7 @@ diff --git a/src/Renderer.js b/src/Renderer.js
- return '<pre><code>' - return '<pre><code>'
+ return '<pre' + this.ln + '><code>' + return '<pre' + this.ln + '><code>'
+ (escaped ? code : escape(code, true)) + (escaped ? code : escape(code, true))
+ '</code></pre>'; + '</code></pre>\n';
} }
- return '<pre><code class="' - return '<pre><code class="'

View File

@@ -1,7 +1,141 @@
diff -NarU1 marked-1.0.0-orig/src/defaults.js marked-1.0.0-edit/src/defaults.js diff --git a/src/Lexer.js b/src/Lexer.js
--- marked-1.0.0-orig/src/defaults.js 2020-04-21 01:03:48.000000000 +0000 --- a/src/Lexer.js
+++ marked-1.0.0-edit/src/defaults.js 2020-04-25 19:16:56.124621393 +0000 +++ b/src/Lexer.js
@@ -9,10 +9,6 @@ @@ -5,5 +5,5 @@ const { block, inline } = require('./rules.js');
/**
* smartypants text replacement
- */
+ *
function smartypants(text) {
return text
@@ -26,5 +26,5 @@ function smartypants(text) {
/**
* mangle email addresses
- */
+ *
function mangle(text) {
let out = '',
@@ -439,5 +439,5 @@ module.exports = class Lexer {
// autolink
- if (token = this.tokenizer.autolink(src, mangle)) {
+ if (token = this.tokenizer.autolink(src)) {
src = src.substring(token.raw.length);
tokens.push(token);
@@ -446,5 +446,5 @@ module.exports = class Lexer {
// url (gfm)
- if (!inLink && (token = this.tokenizer.url(src, mangle))) {
+ if (!inLink && (token = this.tokenizer.url(src))) {
src = src.substring(token.raw.length);
tokens.push(token);
@@ -453,5 +453,5 @@ module.exports = class Lexer {
// text
- if (token = this.tokenizer.inlineText(src, inRawBlock, smartypants)) {
+ if (token = this.tokenizer.inlineText(src, inRawBlock)) {
src = src.substring(token.raw.length);
tokens.push(token);
diff --git a/src/Renderer.js b/src/Renderer.js
--- a/src/Renderer.js
+++ b/src/Renderer.js
@@ -140,5 +140,5 @@ module.exports = class Renderer {
link(href, title, text) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
+ href = cleanUrl(this.options.baseUrl, href);
if (href === null) {
return text;
@@ -153,5 +153,5 @@ module.exports = class Renderer {
image(href, title, text) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
+ href = cleanUrl(this.options.baseUrl, href);
if (href === null) {
return text;
diff --git a/src/Tokenizer.js b/src/Tokenizer.js
--- a/src/Tokenizer.js
+++ b/src/Tokenizer.js
@@ -287,11 +287,8 @@ module.exports = class Tokenizer {
if (cap) {
return {
- type: this.options.sanitize
- ? 'paragraph'
- : 'html',
+ type: 'html',
raw: cap[0],
- pre: !this.options.sanitizer
- && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
- text: this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0]
+ pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',
+ text: cap[0]
};
}
@@ -421,15 +418,9 @@ module.exports = class Tokenizer {
return {
- type: this.options.sanitize
- ? 'text'
- : 'html',
+ type: 'html',
raw: cap[0],
inLink,
inRawBlock,
- text: this.options.sanitize
- ? (this.options.sanitizer
- ? this.options.sanitizer(cap[0])
- : escape(cap[0]))
- : cap[0]
+ text: cap[0]
};
}
@@ -550,10 +541,10 @@ module.exports = class Tokenizer {
}
- autolink(src, mangle) {
+ autolink(src) {
const cap = this.rules.inline.autolink.exec(src);
if (cap) {
let text, href;
if (cap[2] === '@') {
- text = escape(this.options.mangle ? mangle(cap[1]) : cap[1]);
+ text = escape(cap[1]);
href = 'mailto:' + text;
} else {
@@ -578,10 +569,10 @@ module.exports = class Tokenizer {
}
- url(src, mangle) {
+ url(src) {
let cap;
if (cap = this.rules.inline.url.exec(src)) {
let text, href;
if (cap[2] === '@') {
- text = escape(this.options.mangle ? mangle(cap[0]) : cap[0]);
+ text = escape(cap[0]);
href = 'mailto:' + text;
} else {
@@ -615,12 +606,12 @@ module.exports = class Tokenizer {
}
- inlineText(src, inRawBlock, smartypants) {
+ inlineText(src, inRawBlock) {
const cap = this.rules.inline.text.exec(src);
if (cap) {
let text;
if (inRawBlock) {
- text = this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0];
+ text = cap[0];
} else {
- text = escape(this.options.smartypants ? smartypants(cap[0]) : cap[0]);
+ text = escape(cap[0]);
}
return {
diff --git a/src/defaults.js b/src/defaults.js
--- a/src/defaults.js
+++ b/src/defaults.js
@@ -8,12 +8,8 @@ function getDefaults() {
highlight: null,
langPrefix: 'language-', langPrefix: 'language-',
- mangle: true, - mangle: true,
pedantic: false, pedantic: false,
@@ -12,10 +146,12 @@ diff -NarU1 marked-1.0.0-orig/src/defaults.js marked-1.0.0-edit/src/defaults.js
smartLists: false, smartLists: false,
- smartypants: false, - smartypants: false,
tokenizer: null, tokenizer: null,
diff -NarU1 marked-1.0.0-orig/src/helpers.js marked-1.0.0-edit/src/helpers.js walkTokens: null,
--- marked-1.0.0-orig/src/helpers.js 2020-04-21 01:03:48.000000000 +0000 diff --git a/src/helpers.js b/src/helpers.js
+++ marked-1.0.0-edit/src/helpers.js 2020-04-25 18:58:43.001320210 +0000 --- a/src/helpers.js
@@ -65,16 +65,3 @@ +++ b/src/helpers.js
@@ -64,18 +64,5 @@ function edit(regex, opt) {
const nonWordAndColonTest = /[^\w:]/g;
const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
-function cleanUrl(sanitize, base, href) { -function cleanUrl(sanitize, base, href) {
- if (sanitize) { - if (sanitize) {
@@ -33,7 +169,9 @@ diff -NarU1 marked-1.0.0-orig/src/helpers.js marked-1.0.0-edit/src/helpers.js
- } - }
+function cleanUrl(base, href) { +function cleanUrl(base, href) {
if (base && !originIndependentUrl.test(href)) { if (base && !originIndependentUrl.test(href)) {
@@ -224,8 +211,2 @@ href = resolveUrl(base, href);
@@ -223,10 +210,4 @@ function findClosingBracket(str, b) {
}
-function checkSanitizeDeprecation(opt) { -function checkSanitizeDeprecation(opt) {
- if (opt && opt.sanitize && !opt.silent) { - if (opt && opt.sanitize && !opt.silent) {
@@ -42,228 +180,161 @@ diff -NarU1 marked-1.0.0-orig/src/helpers.js marked-1.0.0-edit/src/helpers.js
-} -}
- -
module.exports = { module.exports = {
@@ -240,4 +221,3 @@ escape,
@@ -239,5 +220,4 @@ module.exports = {
splitCells,
rtrim, rtrim,
- findClosingBracket, - findClosingBracket,
- checkSanitizeDeprecation - checkSanitizeDeprecation
+ findClosingBracket + findClosingBracket
}; };
diff -NarU1 marked-1.0.0-orig/src/Lexer.js marked-1.0.0-edit/src/Lexer.js diff --git a/src/marked.js b/src/marked.js
--- marked-1.0.0-orig/src/Lexer.js 2020-04-21 01:03:48.000000000 +0000 --- a/src/marked.js
+++ marked-1.0.0-edit/src/Lexer.js 2020-04-25 22:46:54.107584066 +0000 +++ b/src/marked.js
@@ -6,3 +6,3 @@ @@ -7,5 +7,4 @@ const Slugger = require('./Slugger.js');
* smartypants text replacement const {
- */
+ *
function smartypants(text) {
@@ -27,3 +27,3 @@
* mangle email addresses
- */
+ *
function mangle(text) {
@@ -388,3 +388,3 @@
// autolink
- if (token = this.tokenizer.autolink(src, mangle)) {
+ if (token = this.tokenizer.autolink(src)) {
src = src.substring(token.raw.length);
@@ -395,3 +395,3 @@
// url (gfm)
- if (!inLink && (token = this.tokenizer.url(src, mangle))) {
+ if (!inLink && (token = this.tokenizer.url(src))) {
src = src.substring(token.raw.length);
@@ -402,3 +402,3 @@
// text
- if (token = this.tokenizer.inlineText(src, inRawBlock, smartypants)) {
+ if (token = this.tokenizer.inlineText(src, inRawBlock)) {
src = src.substring(token.raw.length);
diff -NarU1 marked-1.0.0-orig/src/marked.js marked-1.0.0-edit/src/marked.js
--- marked-1.0.0-orig/src/marked.js 2020-04-21 01:03:48.000000000 +0000
+++ marked-1.0.0-edit/src/marked.js 2020-04-25 22:42:55.140924439 +0000
@@ -8,3 +8,2 @@
merge, merge,
- checkSanitizeDeprecation, - checkSanitizeDeprecation,
escape escape
@@ -37,3 +36,2 @@ } = require('./helpers.js');
opt = merge({}, marked.defaults, opt || {}); @@ -35,5 +34,4 @@ function marked(src, opt, callback) {
- checkSanitizeDeprecation(opt);
const highlight = opt.highlight; opt = merge({}, marked.defaults, opt || {});
@@ -101,6 +99,5 @@ - checkSanitizeDeprecation(opt);
opt = merge({}, marked.defaults, opt || {});
- checkSanitizeDeprecation(opt); if (callback) {
return Parser.parse(Lexer.lex(src, opt), opt); @@ -108,5 +106,5 @@ function marked(src, opt, callback) {
return Parser.parse(tokens, opt);
} catch (e) { } catch (e) {
- e.message += '\nPlease report this to https://github.com/markedjs/marked.'; - e.message += '\nPlease report this to https://github.com/markedjs/marked.';
+ e.message += '\nmake issue @ https://github.com/9001/copyparty'; + e.message += '\nmake issue @ https://github.com/9001/copyparty';
if ((opt || marked.defaults).silent) { if (opt.silent) {
diff -NarU1 marked-1.0.0-orig/src/Renderer.js marked-1.0.0-edit/src/Renderer.js return '<p>An error occurred:</p><pre>'
--- marked-1.0.0-orig/src/Renderer.js 2020-04-21 01:03:48.000000000 +0000 diff --git a/test/bench.js b/test/bench.js
+++ marked-1.0.0-edit/src/Renderer.js 2020-04-25 18:59:15.091319265 +0000 --- a/test/bench.js
@@ -134,3 +134,3 @@ +++ b/test/bench.js
link(href, title, text) { @@ -33,5 +33,4 @@ async function runBench(options) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); breaks: false,
+ href = cleanUrl(this.options.baseUrl, href);
if (href === null) {
@@ -147,3 +147,3 @@
image(href, title, text) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
+ href = cleanUrl(this.options.baseUrl, href);
if (href === null) {
diff -NarU1 marked-1.0.0-orig/src/Tokenizer.js marked-1.0.0-edit/src/Tokenizer.js
--- marked-1.0.0-orig/src/Tokenizer.js 2020-04-21 01:03:48.000000000 +0000
+++ marked-1.0.0-edit/src/Tokenizer.js 2020-04-25 22:47:07.610917004 +0000
@@ -256,9 +256,6 @@
return {
- type: this.options.sanitize
- ? 'paragraph'
- : 'html',
- raw: cap[0],
- pre: !this.options.sanitizer
- && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
- text: this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0]
+ type: 'html',
+ raw: cap[0],
+ pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',
+ text: cap[0]
};
@@ -382,5 +379,3 @@
return {
- type: this.options.sanitize
- ? 'text'
- : 'html',
+ type: 'html',
raw: cap[0],
@@ -388,7 +383,3 @@
inRawBlock,
- text: this.options.sanitize
- ? (this.options.sanitizer
- ? this.options.sanitizer(cap[0])
- : escape(cap[0]))
- : cap[0]
+ text: cap[0]
};
@@ -504,3 +495,3 @@
- autolink(src, mangle) {
+ autolink(src) {
const cap = this.rules.inline.autolink.exec(src);
@@ -509,3 +500,3 @@
if (cap[2] === '@') {
- text = escape(this.options.mangle ? mangle(cap[1]) : cap[1]);
+ text = escape(cap[1]);
href = 'mailto:' + text;
@@ -532,3 +523,3 @@
- url(src, mangle) {
+ url(src) {
let cap;
@@ -537,3 +528,3 @@
if (cap[2] === '@') {
- text = escape(this.options.mangle ? mangle(cap[0]) : cap[0]);
+ text = escape(cap[0]);
href = 'mailto:' + text;
@@ -569,3 +560,3 @@
- inlineText(src, inRawBlock, smartypants) {
+ inlineText(src, inRawBlock) {
const cap = this.rules.inline.text.exec(src);
@@ -574,5 +565,5 @@
if (inRawBlock) {
- text = this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0];
+ text = cap[0];
} else {
- text = escape(this.options.smartypants ? smartypants(cap[0]) : cap[0]);
+ text = escape(cap[0]);
}
diff -NarU1 marked-1.0.0-orig/test/bench.js marked-1.0.0-edit/test/bench.js
--- marked-1.0.0-orig/test/bench.js 2020-04-21 01:03:48.000000000 +0000
+++ marked-1.0.0-edit/test/bench.js 2020-04-25 19:02:27.227980287 +0000
@@ -34,3 +34,2 @@
pedantic: false, pedantic: false,
- sanitize: false, - sanitize: false,
smartLists: false smartLists: false
@@ -46,3 +45,2 @@ });
@@ -45,5 +44,4 @@ async function runBench(options) {
breaks: false,
pedantic: false, pedantic: false,
- sanitize: false, - sanitize: false,
smartLists: false smartLists: false
@@ -59,3 +57,2 @@ });
@@ -58,5 +56,4 @@ async function runBench(options) {
breaks: false,
pedantic: false, pedantic: false,
- sanitize: false, - sanitize: false,
smartLists: false smartLists: false
@@ -71,3 +68,2 @@ });
@@ -70,5 +67,4 @@ async function runBench(options) {
breaks: false,
pedantic: false, pedantic: false,
- sanitize: false, - sanitize: false,
smartLists: false smartLists: false
@@ -84,3 +80,2 @@ });
@@ -83,5 +79,4 @@ async function runBench(options) {
breaks: false,
pedantic: true, pedantic: true,
- sanitize: false, - sanitize: false,
smartLists: false smartLists: false
@@ -96,3 +91,2 @@ });
@@ -95,5 +90,4 @@ async function runBench(options) {
breaks: false,
pedantic: true, pedantic: true,
- sanitize: false, - sanitize: false,
smartLists: false smartLists: false
diff -NarU1 marked-1.0.0-orig/test/specs/run-spec.js marked-1.0.0-edit/test/specs/run-spec.js });
--- marked-1.0.0-orig/test/specs/run-spec.js 2020-04-21 01:03:48.000000000 +0000 diff --git a/test/specs/run-spec.js b/test/specs/run-spec.js
+++ marked-1.0.0-edit/test/specs/run-spec.js 2020-04-25 19:05:24.321308408 +0000 --- a/test/specs/run-spec.js
@@ -21,6 +21,2 @@ +++ b/test/specs/run-spec.js
@@ -22,8 +22,4 @@ function runSpecs(title, dir, showCompletionTable, options) {
} }
- if (spec.options.sanitizer) { - if (spec.options.sanitizer) {
- // eslint-disable-next-line no-eval - // eslint-disable-next-line no-eval
- spec.options.sanitizer = eval(spec.options.sanitizer); - spec.options.sanitizer = eval(spec.options.sanitizer);
- } - }
(spec.only ? fit : (spec.skip ? xit : it))('should ' + passFail + example, async() => { (spec.only ? fit : (spec.skip ? xit : it))('should ' + passFail + example, async() => {
@@ -49,2 +45 @@ @@ -53,3 +49,2 @@ runSpecs('Original', './original', false, { gfm: false, pedantic: true });
runSpecs('New', './new');
runSpecs('ReDOS', './redos'); runSpecs('ReDOS', './redos');
-runSpecs('Security', './security', false, { silent: true }); // silent - do not show deprecation warning -runSpecs('Security', './security', false, { silent: true }); // silent - do not show deprecation warning
diff -NarU1 marked-1.0.0-orig/test/unit/Lexer-spec.js marked-1.0.0-edit/test/unit/Lexer-spec.js diff --git a/test/unit/Lexer-spec.js b/test/unit/Lexer-spec.js
--- marked-1.0.0-orig/test/unit/Lexer-spec.js 2020-04-21 01:03:48.000000000 +0000 --- a/test/unit/Lexer-spec.js
+++ marked-1.0.0-edit/test/unit/Lexer-spec.js 2020-04-25 22:47:27.170916427 +0000 +++ b/test/unit/Lexer-spec.js
@@ -464,3 +464,3 @@ @@ -465,5 +465,5 @@ a | b
});
- it('sanitize', () => { - it('sanitize', () => {
+ /*it('sanitize', () => { + /*it('sanitize', () => {
expectTokens({ expectTokens({
@@ -482,3 +482,3 @@ md: '<div>html</div>',
@@ -483,5 +483,5 @@ a | b
]
}); });
- }); - });
+ });*/ + });*/
}); });
@@ -586,3 +586,3 @@
@@ -587,5 +587,5 @@ a | b
});
- it('html sanitize', () => { - it('html sanitize', () => {
+ /*it('html sanitize', () => { + /*it('html sanitize', () => {
expectInlineTokens({ expectInlineTokens({
@@ -596,3 +596,3 @@ md: '<div>html</div>',
@@ -597,5 +597,5 @@ a | b
]
}); });
- }); - });
+ });*/ + });*/
@@ -825,3 +825,3 @@ it('link', () => {
@@ -909,5 +909,5 @@ a | b
});
- it('autolink mangle email', () => { - it('autolink mangle email', () => {
+ /*it('autolink mangle email', () => { + /*it('autolink mangle email', () => {
expectInlineTokens({ expectInlineTokens({
@@ -845,3 +845,3 @@ md: '<test@example.com>',
@@ -929,5 +929,5 @@ a | b
]
}); });
- }); - });
+ });*/ + });*/
@@ -882,3 +882,3 @@ it('url', () => {
@@ -966,5 +966,5 @@ a | b
});
- it('url mangle email', () => { - it('url mangle email', () => {
+ /*it('url mangle email', () => { + /*it('url mangle email', () => {
expectInlineTokens({ expectInlineTokens({
@@ -902,3 +902,3 @@ md: 'test@example.com',
@@ -986,5 +986,5 @@ a | b
]
}); });
- }); - });
+ });*/ + });*/
}); });
@@ -918,3 +918,3 @@
@@ -1002,5 +1002,5 @@ a | b
});
- describe('smartypants', () => { - describe('smartypants', () => {
+ /*describe('smartypants', () => { + /*describe('smartypants', () => {
it('single quotes', () => { it('single quotes', () => {
@@ -988,3 +988,3 @@ expectInlineTokens({
@@ -1072,5 +1072,5 @@ a | b
});
}); });
- }); - });
+ });*/ + });*/
}); });
});

View File

@@ -26,3 +26,6 @@ awk '/:before .content:"\\/ {sub(/[^"]+"./,""); sub(/".*/,""); print}' </z/dist/
# and finally create a woff with just our icons # and finally create a woff with just our icons
pyftsubset "$orig_woff" --unicodes-file=/z/icon.list --no-ignore-missing-unicodes --flavor=woff --with-zopfli --output-file=/z/dist/no-pk/mini-fa.woff --verbose pyftsubset "$orig_woff" --unicodes-file=/z/icon.list --no-ignore-missing-unicodes --flavor=woff --with-zopfli --output-file=/z/dist/no-pk/mini-fa.woff --verbose
# scp is easier, just want basic latin
pyftsubset /z/scp.woff2 --unicodes="20-7e,ab,b7,bb,2022" --no-ignore-missing-unicodes --flavor=woff2 --output-file=/z/dist/no-pk/scp.woff2 --verbose

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

View File

@@ -17,6 +17,14 @@
[80............................................................................] [80............................................................................]
``` ```
```
l[i]=1I;(){}o0O</> var foo = "$(`bar`)"; a's'd
```
[](#s1)
[s1](#s1)
[#s1](#s1)
a123456789b123456789c123456789d123456789e123456789f123456789g123456789h123456789i123456789j123456789k123456789l123456789m123456789n123456789o123456789p123456789q123456789r123456789s123456789t123456789u123456789v123456789w123456789x123456789y123456789z123456789 a123456789b123456789c123456789d123456789e123456789f123456789g123456789h123456789i123456789j123456789k123456789l123456789m123456789n123456789o123456789p123456789q123456789r123456789s123456789t123456789u123456789v123456789w123456789x123456789y123456789z123456789
<foo> &nbsp; bar &amp; <span>baz</span> <foo> &nbsp; bar &amp; <span>baz</span>