mirror of
https://github.com/9001/copyparty.git
synced 2025-11-05 14:23:17 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09557fbe83 | ||
|
|
1c0f44fa4e | ||
|
|
fc4d59d2d7 | ||
|
|
12345fbacc | ||
|
|
2e33c8d222 | ||
|
|
db5f07f164 | ||
|
|
e050e69a43 | ||
|
|
27cb1d4fc7 | ||
|
|
5d6a740947 | ||
|
|
da3f68c363 | ||
|
|
d7d1c3685c | ||
|
|
dab3407beb | ||
|
|
592987a54a | ||
|
|
8dca8326f7 | ||
|
|
633481fae3 | ||
|
|
e7b99e6fb7 | ||
|
|
2a6a3aedd0 | ||
|
|
866c74c841 | ||
|
|
dad92bde26 | ||
|
|
a994e034f7 | ||
|
|
2801c04f2e | ||
|
|
316e3abfab |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -11,14 +11,12 @@ dist/
|
|||||||
sfx/
|
sfx/
|
||||||
.venv/
|
.venv/
|
||||||
|
|
||||||
# sublime
|
# ide
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
|
|
||||||
# winmerge
|
# winmerge
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
# other licenses
|
# derived
|
||||||
contrib/
|
copyparty/web/deps/
|
||||||
|
srv/
|
||||||
# deps
|
|
||||||
copyparty/web/deps
|
|
||||||
|
|||||||
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")
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ if platform.system() == "Windows":
|
|||||||
VT100 = not WINDOWS or WINDOWS >= [10, 0, 14393]
|
VT100 = not WINDOWS or WINDOWS >= [10, 0, 14393]
|
||||||
# introduced in anniversary update
|
# introduced in anniversary update
|
||||||
|
|
||||||
|
MACOS = platform.system() == "Darwin"
|
||||||
|
|
||||||
|
|
||||||
class EnvParams(object):
|
class EnvParams(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (0, 3, 1)
|
VERSION = (0, 4, 2)
|
||||||
CODENAME = "docuparty"
|
CODENAME = "NIH"
|
||||||
BUILD_DT = (2020, 5, 7)
|
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(
|
||||||
@@ -834,7 +841,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
def tx_md(self, fs_path):
|
def tx_md(self, fs_path):
|
||||||
logmsg = "{:4} {} ".format("", self.req)
|
logmsg = "{:4} {} ".format("", self.req)
|
||||||
if "edit" in self.uparam:
|
if "edit2" in self.uparam:
|
||||||
html_path = "web/mde.html"
|
html_path = "web/mde.html"
|
||||||
template = self.conn.tpl_mde
|
template = self.conn.tpl_mde
|
||||||
else:
|
else:
|
||||||
@@ -844,18 +851,26 @@ class HttpCli(object):
|
|||||||
html_path = os.path.join(E.mod, html_path)
|
html_path = os.path.join(E.mod, html_path)
|
||||||
|
|
||||||
st = os.stat(fsenc(fs_path))
|
st = os.stat(fsenc(fs_path))
|
||||||
sz_md = st.st_size
|
# sz_md = st.st_size
|
||||||
ts_md = st.st_mtime
|
ts_md = st.st_mtime
|
||||||
|
|
||||||
st = os.stat(fsenc(html_path))
|
st = os.stat(fsenc(html_path))
|
||||||
ts_html = st.st_mtime
|
ts_html = st.st_mtime
|
||||||
|
|
||||||
|
# TODO dont load into memory ;_;
|
||||||
|
# (trivial fix, count the &'s)
|
||||||
|
with open(fsenc(fs_path), "rb") as f:
|
||||||
|
md = f.read().replace(b"&", b"&")
|
||||||
|
sz_md = len(md)
|
||||||
|
|
||||||
file_ts = max(ts_md, ts_html)
|
file_ts = max(ts_md, ts_html)
|
||||||
file_lastmod, do_send = self._chk_lastmod(file_ts)
|
file_lastmod, do_send = self._chk_lastmod(file_ts)
|
||||||
self.out_headers["Last-Modified"] = file_lastmod
|
self.out_headers["Last-Modified"] = file_lastmod
|
||||||
|
self.out_headers["Cache-Control"] = "no-cache"
|
||||||
status = 200 if do_send else 304
|
status = 200 if do_send else 304
|
||||||
|
|
||||||
targs = {
|
targs = {
|
||||||
|
"edit": "edit" in self.uparam,
|
||||||
"title": html_escape(self.vpath, quote=False),
|
"title": html_escape(self.vpath, quote=False),
|
||||||
"lastmod": int(ts_md * 1000),
|
"lastmod": int(ts_md * 1000),
|
||||||
"md": "",
|
"md": "",
|
||||||
@@ -868,9 +883,7 @@ class HttpCli(object):
|
|||||||
self.log(logmsg)
|
self.log(logmsg)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
with open(fsenc(fs_path), "rb") as f:
|
# TODO jinja2 can stream this right?
|
||||||
md = f.read()
|
|
||||||
|
|
||||||
targs["md"] = md.decode("utf-8", "replace")
|
targs["md"] = md.decode("utf-8", "replace")
|
||||||
html = template.render(**targs).encode("utf-8")
|
html = template.render(**targs).encode("utf-8")
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import time
|
|||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .__init__ import E
|
from .__init__ import E, MACOS
|
||||||
from .httpconn import HttpConn
|
from .httpconn import HttpConn
|
||||||
from .authsrv import AuthSrv
|
from .authsrv import AuthSrv
|
||||||
|
|
||||||
@@ -75,11 +75,14 @@ class HttpSrv(object):
|
|||||||
sck.shutdown(socket.SHUT_RDWR)
|
sck.shutdown(socket.SHUT_RDWR)
|
||||||
sck.close()
|
sck.close()
|
||||||
except (OSError, socket.error) as ex:
|
except (OSError, socket.error) as ex:
|
||||||
|
if not MACOS:
|
||||||
self.log(
|
self.log(
|
||||||
"%s %s" % addr, "shut_rdwr err:\n {}\n {}".format(repr(sck), ex),
|
"%s %s" % addr,
|
||||||
|
"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
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import threading
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
from .__init__ import PY2, WINDOWS, VT100
|
from .__init__ import PY2, WINDOWS, MACOS, VT100
|
||||||
from .tcpsrv import TcpSrv
|
from .tcpsrv import TcpSrv
|
||||||
from .up2k import Up2k
|
from .up2k import Up2k
|
||||||
from .util import mp
|
from .util import mp
|
||||||
@@ -111,6 +111,8 @@ class SvcHub(object):
|
|||||||
return msg
|
return msg
|
||||||
elif vmin < 3:
|
elif vmin < 3:
|
||||||
return msg
|
return msg
|
||||||
|
elif MACOS:
|
||||||
|
return "multiprocessing is wonky on mac osx;"
|
||||||
else:
|
else:
|
||||||
msg = "need python 2.7 or 3.3+ for multiprocessing;"
|
msg = "need python 2.7 or 3.3+ for multiprocessing;"
|
||||||
if not PY2 and vmin < 3:
|
if not PY2 and vmin < 3:
|
||||||
|
|||||||
@@ -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:
|
if len(ret) > 1024 * 64:
|
||||||
raise Pebkac(400, "header 2big")
|
raise Pebkac(400, "header 2big")
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
return ret[:-4].decode("utf-8", "surrogateescape").split("\r\n")
|
sr.unrecv(ret[ofs + 4 :])
|
||||||
|
return ret[:ofs].decode("utf-8", "surrogateescape").split("\r\n")
|
||||||
|
|
||||||
|
|
||||||
def undot(path):
|
def undot(path):
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ html, body {
|
|||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
#mtw {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#mw {
|
#mw {
|
||||||
width: 48.5em;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
margin-bottom: 6em;
|
|
||||||
}
|
}
|
||||||
pre, code, a {
|
pre, code, a {
|
||||||
color: #480;
|
color: #480;
|
||||||
@@ -76,27 +77,31 @@ h2 {
|
|||||||
padding-left: .4em;
|
padding-left: .4em;
|
||||||
margin-top: 3em;
|
margin-top: 3em;
|
||||||
}
|
}
|
||||||
|
h3 {
|
||||||
|
border-bottom: .1em solid #999;
|
||||||
|
}
|
||||||
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;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
#m ul,
|
#mp ul,
|
||||||
#m ol {
|
#mp ol {
|
||||||
border-left: .3em solid #ddd;
|
border-left: .3em solid #ddd;
|
||||||
}
|
}
|
||||||
#m>ul,
|
#m>ul,
|
||||||
#m>ol {
|
#m>ol {
|
||||||
border-color: #bbb;
|
border-color: #bbb;
|
||||||
}
|
}
|
||||||
#m ul>li {
|
#mp ul>li {
|
||||||
list-style-type: disc;
|
list-style-type: disc;
|
||||||
}
|
}
|
||||||
#m ul>li,
|
#mp ul>li,
|
||||||
#m ol>li {
|
#mp ol>li {
|
||||||
margin: .7em 0;
|
margin: .7em 0;
|
||||||
}
|
}
|
||||||
p>em,
|
p>em,
|
||||||
@@ -116,8 +121,9 @@ small {
|
|||||||
opacity: .8;
|
opacity: .8;
|
||||||
}
|
}
|
||||||
#toc {
|
#toc {
|
||||||
width: 48.5em;
|
margin: 0 1em;
|
||||||
margin: 0 auto;
|
-ms-scroll-chaining: none;
|
||||||
|
overscroll-behavior-y: none;
|
||||||
}
|
}
|
||||||
#toc ul {
|
#toc ul {
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
@@ -181,10 +187,24 @@ blink {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen {
|
@media screen {
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
outline: 0;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#mw {
|
||||||
|
padding: 0 1em;
|
||||||
|
margin: 0 auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
#mp {
|
||||||
|
max-width: 54em;
|
||||||
|
margin-bottom: 6em;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -212,15 +232,23 @@ blink {
|
|||||||
padding: .5em 0;
|
padding: .5em 0;
|
||||||
}
|
}
|
||||||
#mn {
|
#mn {
|
||||||
font-weight: normal;
|
|
||||||
padding: 1.3em 0 .7em 1em;
|
padding: 1.3em 0 .7em 1em;
|
||||||
font-size: 1.4em;
|
border-bottom: 1px solid #ccc;
|
||||||
|
background: #eee;
|
||||||
|
z-index: 10;
|
||||||
|
width: calc(100% - 1em);
|
||||||
|
}
|
||||||
|
#mn.undocked {
|
||||||
|
position: fixed;
|
||||||
|
padding: 1.7em 0 1.5em 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;
|
||||||
margin: 0 0 0 -.2em;
|
margin: 0 0 0 -.2em;
|
||||||
padding: 0 0 0 .4em;
|
padding: .3em 0 .3em .4em;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border: none;
|
border: none;
|
||||||
/* ie: */
|
/* ie: */
|
||||||
@@ -248,7 +276,19 @@ blink {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
#mh {
|
#mh {
|
||||||
margin: 0 0 1.5em 0;
|
padding: .4em 1em;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
width: calc(100% - 3em);
|
||||||
|
background: #eee;
|
||||||
|
z-index: 9;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
#mh a {
|
||||||
|
color: #444;
|
||||||
|
background: none;
|
||||||
|
text-decoration: underline;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -270,13 +310,12 @@ blink {
|
|||||||
html.dark #toc li {
|
html.dark #toc li {
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
}
|
}
|
||||||
html.dark #m a,
|
html.dark #mp a {
|
||||||
html.dark #mh a {
|
|
||||||
background: #057;
|
background: #057;
|
||||||
}
|
}
|
||||||
html.dark #m h1 a, html.dark #m h4 a,
|
html.dark #mp h1 a, html.dark #mp h4 a,
|
||||||
html.dark #m h2 a, html.dark #m h5 a,
|
html.dark #mp h2 a, html.dark #mp h5 a,
|
||||||
html.dark #m h3 a, html.dark #m h6 a {
|
html.dark #mp h3 a, html.dark #mp h6 a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
@@ -286,8 +325,8 @@ blink {
|
|||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
border: .07em solid #333;
|
border: .07em solid #333;
|
||||||
}
|
}
|
||||||
html.dark #m ul,
|
html.dark #mp ul,
|
||||||
html.dark #m ol {
|
html.dark #mp ol {
|
||||||
border-color: #444;
|
border-color: #444;
|
||||||
}
|
}
|
||||||
html.dark #m>ul,
|
html.dark #m>ul,
|
||||||
@@ -322,26 +361,44 @@ blink {
|
|||||||
html.dark #mn a {
|
html.dark #mn a {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
html.dark #mn {
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
}
|
}
|
||||||
@media screen and (min-width: 64em) {
|
html.dark #mn,
|
||||||
|
html.dark #mh {
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
html.dark #mh a {
|
||||||
|
color: #ccc;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 70em) {
|
||||||
#mw {
|
#mw {
|
||||||
margin-left: 14em;
|
position: fixed;
|
||||||
margin-left: calc(100% - 50em);
|
overflow-y: auto;
|
||||||
|
left: 14em;
|
||||||
|
left: calc(100% - 57em);
|
||||||
|
max-width: none;
|
||||||
|
bottom: 0;
|
||||||
|
scrollbar-color: #eb0 #f7f7f7;
|
||||||
}
|
}
|
||||||
#toc {
|
#toc {
|
||||||
width: 13em;
|
width: 13em;
|
||||||
width: calc(100% - 52.3em);
|
width: calc(100% - 57.3em);
|
||||||
|
max-width: 30em;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
overflow-y: auto;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 100%;
|
bottom: 0;
|
||||||
overflow-y: auto;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-shadow: 0 0 1em #ccc;
|
|
||||||
scrollbar-color: #eb0 #f7f7f7;
|
scrollbar-color: #eb0 #f7f7f7;
|
||||||
xscrollbar-width: thin;
|
box-shadow: 0 0 1em rgba(0,0,0,0.1);
|
||||||
|
border-top: 1px solid #d7d7d7;
|
||||||
}
|
}
|
||||||
#toc li {
|
#toc li {
|
||||||
border-left: .3em solid #ccc;
|
border-left: .3em solid #ccc;
|
||||||
@@ -361,13 +418,32 @@ blink {
|
|||||||
|
|
||||||
html.dark #toc {
|
html.dark #toc {
|
||||||
background: #282828;
|
background: #282828;
|
||||||
|
border-top: 1px solid #2c2c2c;
|
||||||
box-shadow: 0 0 1em #181818;
|
box-shadow: 0 0 1em #181818;
|
||||||
|
}
|
||||||
|
html.dark #toc,
|
||||||
|
html.dark #mw {
|
||||||
scrollbar-color: #b80 #282828;
|
scrollbar-color: #b80 #282828;
|
||||||
}
|
}
|
||||||
|
html.dark #toc::-webkit-scrollbar-track {
|
||||||
|
background: #282828;
|
||||||
}
|
}
|
||||||
@media screen and (min-width: 84em) {
|
html.dark #toc::-webkit-scrollbar {
|
||||||
|
background: #282828;
|
||||||
|
width: .8em;
|
||||||
|
}
|
||||||
|
html.dark #toc::-webkit-scrollbar-thumb {
|
||||||
|
background: #eb0;
|
||||||
|
}
|
||||||
|
html.dark #mn.undocked {
|
||||||
|
box-shadow: 0 0 .5em #555;
|
||||||
|
border: none;
|
||||||
|
background: #0a0a0a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 87.5em) {
|
||||||
#toc { width: 30em }
|
#toc { width: 30em }
|
||||||
#mw { margin-left: 32em }
|
#mw { left: 30.5em }
|
||||||
}
|
}
|
||||||
@media print {
|
@media print {
|
||||||
a {
|
a {
|
||||||
|
|||||||
@@ -4,32 +4,73 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
<link href="/.cpr/md.css" rel="stylesheet">
|
<link href="/.cpr/md.css" rel="stylesheet">
|
||||||
|
{%- if edit %}
|
||||||
|
<link href="/.cpr/md2.css" rel="stylesheet">
|
||||||
|
{%- endif %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mn"></div>
|
<div id="mn">navbar</div>
|
||||||
<div id="toc"></div>
|
|
||||||
<div id="mw">
|
|
||||||
<div id="mh">
|
<div id="mh">
|
||||||
<a id="lightswitch" href="#">go dark</a> //
|
<a id="lightswitch" href="#">go dark</a>
|
||||||
<a id="edit" href="?edit">edit this</a>
|
<a id="navtoggle" href="#">hide nav</a>
|
||||||
|
{%- if edit %}
|
||||||
|
<a id="save" href="?edit">save</a>
|
||||||
|
<a id="sbs" href="#">sbs</a>
|
||||||
|
<a id="nsbs" href="#">editor</a>
|
||||||
|
<a id="help" href="#">help</a>
|
||||||
|
{%- else %}
|
||||||
|
<a href="?edit">edit (basic)</a>
|
||||||
|
<a href="?edit2">edit (fancy)</a>
|
||||||
|
{%- endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<div id="toc"></div>
|
||||||
|
<div id="mtw">
|
||||||
|
<textarea id="mt">{{ md }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div id="mw">
|
||||||
<div id="ml">
|
<div id="ml">
|
||||||
<div style="text-align:center;margin:5em 0">
|
<div style="text-align:center;margin:5em 0">
|
||||||
<div style="font-size:2em;margin:1em 0">Loading</div>
|
<div style="font-size:2em;margin:1em 0">Loading</div>
|
||||||
if you're still reading this, check that javascript is allowed
|
if you're still reading this, check that javascript is allowed
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="m">
|
<div id="mp"></div>
|
||||||
<textarea id="mt" style="display:none">{{ md }}</textarea>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{%- if edit %}
|
||||||
|
<div id="helpbox">
|
||||||
|
<textarea>
|
||||||
|
|
||||||
|
write markdown (html is permitted)
|
||||||
|
|
||||||
|
### hotkey list
|
||||||
|
* `Ctrl-S` to save
|
||||||
|
* `Ctrl-H` / `Ctrl-Shift-H` to create a header
|
||||||
|
* `TAB` / `Shift-TAB` to indent/dedent a selection
|
||||||
|
|
||||||
|
### toolbar
|
||||||
|
1. toggle dark mode
|
||||||
|
2. show/hide navigation bar
|
||||||
|
3. save changes on server
|
||||||
|
4. side-by-side editing
|
||||||
|
5. toggle editor/preview
|
||||||
|
6. this thing :^)
|
||||||
|
|
||||||
|
.
|
||||||
|
|
||||||
|
</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var link_md_as_html = false; // TODO (does nothing)
|
var link_md_as_html = false; // TODO (does nothing)
|
||||||
|
var last_modified = {{ lastmod }};
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
var btn = document.getElementById("lightswitch");
|
var btn = document.getElementById("lightswitch");
|
||||||
var toggle = function () {
|
var toggle = function (e) {
|
||||||
|
if (e) e.preventDefault();
|
||||||
var dark = !document.documentElement.getAttribute("class");
|
var dark = !document.documentElement.getAttribute("class");
|
||||||
document.documentElement.setAttribute("class", dark ? "dark" : "");
|
document.documentElement.setAttribute("class", dark ? "dark" : "");
|
||||||
btn.innerHTML = "go " + (dark ? "light" : "dark");
|
btn.innerHTML = "go " + (dark ? "light" : "dark");
|
||||||
@@ -41,7 +82,17 @@ var link_md_as_html = false; // TODO (does nothing)
|
|||||||
toggle();
|
toggle();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
if (!String.startsWith) {
|
||||||
|
String.prototype.startsWith = function(s, i) {
|
||||||
|
i = i>0 ? i|0 : 0;
|
||||||
|
return this.substring(i, i + s.length) === s;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/deps/marked.full.js"></script>
|
<script src="/.cpr/deps/marked.full.js"></script>
|
||||||
<script src="/.cpr/md.js"></script>
|
<script src="/.cpr/md.js"></script>
|
||||||
|
{%- if edit %}
|
||||||
|
<script src="/.cpr/md2.js"></script>
|
||||||
|
{%- endif %}
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|||||||
@@ -1,17 +1,41 @@
|
|||||||
/*var conv = new showdown.Converter();
|
|
||||||
conv.setFlavor('github');
|
|
||||||
conv.setOption('tasklists', 0);
|
|
||||||
var mhtml = conv.makeHtml(dom_md.value);
|
|
||||||
*/
|
|
||||||
|
|
||||||
var dom_toc = document.getElementById('toc');
|
var dom_toc = document.getElementById('toc');
|
||||||
var dom_wrap = document.getElementById('mw');
|
var dom_wrap = document.getElementById('mw');
|
||||||
var dom_head = document.getElementById('mh');
|
var dom_hbar = document.getElementById('mh');
|
||||||
var dom_nav = document.getElementById('mn');
|
var dom_nav = document.getElementById('mn');
|
||||||
var dom_doc = document.getElementById('m');
|
var dom_pre = document.getElementById('mp');
|
||||||
var dom_md = document.getElementById('mt');
|
var dom_src = document.getElementById('mt');
|
||||||
|
var dom_navtgl = document.getElementById('navtoggle');
|
||||||
|
|
||||||
// add toolbar buttons
|
|
||||||
|
// 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) {
|
||||||
|
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
|
||||||
(function () {
|
(function () {
|
||||||
var n = document.location + '';
|
var n = document.location + '';
|
||||||
n = n.substr(n.indexOf('//') + 2).split('?')[0].split('/');
|
n = n.substr(n.indexOf('//') + 2).split('?')[0].split('/');
|
||||||
@@ -22,27 +46,112 @@ var dom_md = document.getElementById('mt');
|
|||||||
if (a > 0)
|
if (a > 0)
|
||||||
loc.push(n[a]);
|
loc.push(n[a]);
|
||||||
|
|
||||||
var dec = decodeURIComponent(n[a]).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
var dec = hesc(decodeURIComponent(n[a]));
|
||||||
|
|
||||||
nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>');
|
nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>');
|
||||||
}
|
}
|
||||||
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_doc.innerHTML = html;
|
var md_dom = new DOMParser().parseFromString(md_html, "text/html").body;
|
||||||
|
|
||||||
var loader = document.getElementById('ml');
|
|
||||||
loader.parentNode.removeChild(loader);
|
|
||||||
|
|
||||||
// todo-lists (should probably be a marked extension)
|
// todo-lists (should probably be a marked extension)
|
||||||
var nodes = dom_doc.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')
|
||||||
@@ -61,34 +170,76 @@ function convert_markdown(md_text) {
|
|||||||
'<span class="todo_' + clas + '">' + char + '</span>' +
|
'<span class="todo_' + clas + '">' + char + '</span>' +
|
||||||
html.substr(html.indexOf('>') + 1);
|
html.substr(html.indexOf('>') + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// separate <code> for each line in <pre>
|
||||||
|
var nodes = md_dom.getElementsByTagName('pre');
|
||||||
|
for (var a = nodes.length - 1; a >= 0; a--) {
|
||||||
|
var el = nodes[a];
|
||||||
|
|
||||||
|
var is_precode =
|
||||||
|
el.tagName == 'PRE' &&
|
||||||
|
el.childNodes.length === 1 &&
|
||||||
|
el.childNodes[0].tagName == 'CODE';
|
||||||
|
|
||||||
|
if (!is_precode)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var nline = parseInt(el.getAttribute('data-ln')) + 1;
|
||||||
|
var lines = el.innerHTML.replace(/\r?\n<\/code>$/i, '</code>').split(/\r?\n/g);
|
||||||
|
for (var b = 0; b < lines.length - 1; b++)
|
||||||
|
lines[b] += '</code>\n<code data-ln="' + (nline + b) + '">';
|
||||||
|
|
||||||
|
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');
|
||||||
|
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_doc.getElementsByTagName('*');
|
var manip_nodes_dyn = dom_pre.getElementsByTagName('*');
|
||||||
var manip_nodes = [];
|
var manip_nodes = [];
|
||||||
for (var a = 0, aa = manip_nodes_dyn.length; a < aa; a++)
|
for (var a = 0, aa = manip_nodes_dyn.length; a < aa; a++)
|
||||||
manip_nodes.push(manip_nodes_dyn[a]);
|
manip_nodes.push(manip_nodes_dyn[a]);
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
var is_precode =
|
|
||||||
!is_header &&
|
|
||||||
elm.tagName == 'PRE' &&
|
|
||||||
elm.childNodes.length === 1 &&
|
|
||||||
elm.childNodes[0].tagName == 'CODE';
|
|
||||||
|
|
||||||
if (is_header) {
|
if (is_header) {
|
||||||
var nlv = m[1];
|
var nlv = m[1];
|
||||||
while (lv < nlv) {
|
while (lv < nlv) {
|
||||||
@@ -100,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);
|
||||||
@@ -127,17 +262,6 @@ function init_toc() {
|
|||||||
y: null
|
y: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (is_precode) {
|
|
||||||
// not actually toc-related (sorry),
|
|
||||||
// split <pre><code /></pre> into one <code> per line
|
|
||||||
var nline = parseInt(elm.getAttribute('data-ln')) + 1;
|
|
||||||
var lines = elm.innerHTML.replace(/\r?\n<\/code>$/i, '</code>').split(/\r?\n/g);
|
|
||||||
for (var b = 0; b < lines.length - 1; b++)
|
|
||||||
lines[b] += '</code>\n<code data-ln="' + (nline + b) + '">';
|
|
||||||
|
|
||||||
elm.innerHTML = lines.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_header && anchor)
|
if (!is_header && anchor)
|
||||||
anchor.kids.push(elm);
|
anchor.kids.push(elm);
|
||||||
}
|
}
|
||||||
@@ -209,41 +333,77 @@ function init_toc() {
|
|||||||
|
|
||||||
|
|
||||||
// "main" :p
|
// "main" :p
|
||||||
convert_markdown(dom_md.value);
|
convert_markdown(dom_src.value);
|
||||||
var toc = init_toc();
|
var toc = init_toc();
|
||||||
|
|
||||||
|
|
||||||
// scroll handler
|
// scroll handler
|
||||||
(function () {
|
var redraw = (function () {
|
||||||
var timer_active = false;
|
var sbs = false;
|
||||||
var final = null;
|
function onresize() {
|
||||||
|
sbs = window.matchMedia('(min-width: 64em)').matches;
|
||||||
function onscroll() {
|
var y = (dom_hbar.offsetTop + dom_hbar.offsetHeight) + 'px';
|
||||||
clearTimeout(final);
|
if (sbs) {
|
||||||
timer_active = false;
|
dom_toc.style.top = y;
|
||||||
toc.refresh();
|
dom_wrap.style.top = y;
|
||||||
|
dom_toc.style.marginTop = '0';
|
||||||
var y = 0;
|
|
||||||
if (window.matchMedia('(min-width: 64em)').matches)
|
|
||||||
y = parseInt(dom_nav.offsetHeight) - window.scrollY;
|
|
||||||
|
|
||||||
dom_toc.style.marginTop = y < 0 ? 0 : y + "px";
|
|
||||||
}
|
}
|
||||||
onscroll();
|
onscroll();
|
||||||
|
}
|
||||||
|
|
||||||
function ev_onscroll() {
|
function onscroll() {
|
||||||
// long timeout: scroll ended
|
toc.refresh();
|
||||||
clearTimeout(final);
|
}
|
||||||
final = setTimeout(onscroll, 100);
|
|
||||||
|
|
||||||
// short timeout: continuous updates
|
window.onresize = onresize;
|
||||||
if (timer_active)
|
window.onscroll = onscroll;
|
||||||
|
dom_wrap.onscroll = onscroll;
|
||||||
|
|
||||||
|
onresize();
|
||||||
|
return onresize;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
return;
|
||||||
|
|
||||||
timer_active = true;
|
clearTimeout(timeout);
|
||||||
setTimeout(onscroll, 10);
|
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';
|
||||||
|
dom_navtgl.innerHTML = hidden ? 'show nav' : 'hide nav';
|
||||||
|
if (hidden) {
|
||||||
|
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)
|
||||||
|
localStorage.setItem('hidenav', hidden ? 1 : 0);
|
||||||
|
|
||||||
|
redraw();
|
||||||
};
|
};
|
||||||
|
|
||||||
window.onscroll = ev_onscroll;
|
if (window.localStorage && localStorage.getItem('hidenav') == 1)
|
||||||
window.onresize = ev_onscroll;
|
dom_navtgl.onclick();
|
||||||
})();
|
|
||||||
|
|||||||
108
copyparty/web/md2.css
Normal file
108
copyparty/web/md2.css
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#toc {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#mtw {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: calc(100% - 58em);
|
||||||
|
}
|
||||||
|
#mw {
|
||||||
|
left: calc(100% - 57em);
|
||||||
|
overflow-y: auto;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* single-screen */
|
||||||
|
#mtw.preview,
|
||||||
|
#mw.editor {
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#mw.preview,
|
||||||
|
#mtw.editor {
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
#mtw.single,
|
||||||
|
#mw.single {
|
||||||
|
margin: 0;
|
||||||
|
left: 1em;
|
||||||
|
left: max(1em, calc((100% - 58em) / 2));
|
||||||
|
}
|
||||||
|
#mtw.single {
|
||||||
|
width: 57em;
|
||||||
|
width: min(57em, calc(100% - 2em));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#mp {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#mt, #mtr {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 5px);
|
||||||
|
color: #444;
|
||||||
|
background: #f7f7f7;
|
||||||
|
border: 1px solid #999;
|
||||||
|
outline: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'consolas', monospace, monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word; /*ie*/
|
||||||
|
overflow-y: scroll;
|
||||||
|
line-height: 1.3em;
|
||||||
|
font-size: .9em;
|
||||||
|
position: relative;
|
||||||
|
scrollbar-color: #eb0 #f7f7f7;
|
||||||
|
}
|
||||||
|
html.dark #mt {
|
||||||
|
color: #eee;
|
||||||
|
background: #222;
|
||||||
|
border: 1px solid #777;
|
||||||
|
scrollbar-color: #b80 #282828;
|
||||||
|
}
|
||||||
|
#mtr {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
#save.force-save {
|
||||||
|
color: #400;
|
||||||
|
background: #f97;
|
||||||
|
border-radius: .15em;
|
||||||
|
}
|
||||||
|
#save.disabled {
|
||||||
|
opacity: .4;
|
||||||
|
}
|
||||||
|
#helpbox {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
background: #f7f7f7;
|
||||||
|
box-shadow: 0 .5em 2em #777;
|
||||||
|
border-radius: .4em;
|
||||||
|
padding: 2em;
|
||||||
|
top: 4em;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: calc(100% - 12em);
|
||||||
|
left: calc(50% - 15em);
|
||||||
|
right: 0;
|
||||||
|
width: 30em;
|
||||||
|
z-index: 9001;
|
||||||
|
}
|
||||||
|
#helpclose {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
html.dark #helpbox {
|
||||||
|
background: #222;
|
||||||
|
box-shadow: 0 .5em 2em #444;
|
||||||
|
border: 1px solid #079;
|
||||||
|
border-width: 1px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# mt {opacity: .5;top:1px}
|
||||||
694
copyparty/web/md2.js
Normal file
694
copyparty/web/md2.js
Normal file
@@ -0,0 +1,694 @@
|
|||||||
|
// server state
|
||||||
|
var server_md = dom_src.value;
|
||||||
|
|
||||||
|
|
||||||
|
// dom nodes
|
||||||
|
var dom_swrap = document.getElementById('mtw');
|
||||||
|
var dom_sbs = document.getElementById('sbs');
|
||||||
|
var dom_nsbs = document.getElementById('nsbs');
|
||||||
|
var dom_ref = (function () {
|
||||||
|
var d = document.createElement('div');
|
||||||
|
d.setAttribute('id', 'mtr');
|
||||||
|
dom_swrap.appendChild(d);
|
||||||
|
d = document.getElementById('mtr');
|
||||||
|
// hide behind the textarea (offsetTop is not computed if display:none)
|
||||||
|
dom_src.style.zIndex = '4';
|
||||||
|
d.style.zIndex = '3';
|
||||||
|
return d;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// line->scrollpos maps
|
||||||
|
var map_src = [];
|
||||||
|
var map_pre = [];
|
||||||
|
function genmap(dom) {
|
||||||
|
var ret = [];
|
||||||
|
var last_y = -1;
|
||||||
|
var parent_y = 0;
|
||||||
|
var parent_n = null;
|
||||||
|
var nodes = dom.querySelectorAll('*[data-ln]');
|
||||||
|
for (var a = 0; a < nodes.length; a++) {
|
||||||
|
var n = nodes[a];
|
||||||
|
var ln = parseInt(n.getAttribute('data-ln'));
|
||||||
|
if (ln in ret)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var y = 0;
|
||||||
|
var par = n.offsetParent;
|
||||||
|
if (par != parent_n) {
|
||||||
|
while (par && par != dom) {
|
||||||
|
y += par.offsetTop;
|
||||||
|
par = par.offsetParent;
|
||||||
|
}
|
||||||
|
if (par != dom)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
parent_y = y;
|
||||||
|
parent_n = n.offsetParent;
|
||||||
|
}
|
||||||
|
while (ln > ret.length)
|
||||||
|
ret.push(null);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// input handler
|
||||||
|
var action_stack = null;
|
||||||
|
var nlines = 0;
|
||||||
|
var draw_md = (function () {
|
||||||
|
var delay = 1;
|
||||||
|
function draw_md() {
|
||||||
|
var t0 = new Date().getTime();
|
||||||
|
var src = dom_src.value;
|
||||||
|
convert_markdown(src);
|
||||||
|
|
||||||
|
var lines = hesc(src).replace(/\r/g, "").split('\n');
|
||||||
|
nlines = lines.length;
|
||||||
|
var html = [];
|
||||||
|
for (var a = 0; a < lines.length; a++)
|
||||||
|
html.push('<span data-ln="' + (a + 1) + '">' + lines[a] + "</span>");
|
||||||
|
|
||||||
|
dom_ref.innerHTML = html.join('\n');
|
||||||
|
map_src = genmap(dom_ref);
|
||||||
|
map_pre = genmap(dom_pre);
|
||||||
|
|
||||||
|
cls(document.getElementById('save'), 'disabled', src == server_md);
|
||||||
|
|
||||||
|
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)
|
||||||
|
action_stack.push();
|
||||||
|
};
|
||||||
|
|
||||||
|
draw_md();
|
||||||
|
return draw_md;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// resize handler
|
||||||
|
redraw = (function () {
|
||||||
|
function onresize() {
|
||||||
|
var y = (dom_hbar.offsetTop + dom_hbar.offsetHeight) + 'px';
|
||||||
|
dom_wrap.style.top = y;
|
||||||
|
dom_swrap.style.top = y;
|
||||||
|
dom_ref.style.width = getComputedStyle(dom_src).offsetWidth + 'px';
|
||||||
|
map_src = genmap(dom_ref);
|
||||||
|
map_pre = genmap(dom_pre);
|
||||||
|
dbg(document.body.clientWidth + 'x' + document.body.clientHeight);
|
||||||
|
}
|
||||||
|
function setsbs() {
|
||||||
|
dom_wrap.setAttribute('class', '');
|
||||||
|
dom_swrap.setAttribute('class', '');
|
||||||
|
onresize();
|
||||||
|
}
|
||||||
|
function modetoggle() {
|
||||||
|
mode = dom_nsbs.innerHTML;
|
||||||
|
dom_nsbs.innerHTML = mode == 'editor' ? 'preview' : 'editor';
|
||||||
|
mode += ' single';
|
||||||
|
dom_wrap.setAttribute('class', mode);
|
||||||
|
dom_swrap.setAttribute('class', mode);
|
||||||
|
onresize();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onresize = onresize;
|
||||||
|
window.onscroll = null;
|
||||||
|
dom_wrap.onscroll = null;
|
||||||
|
dom_sbs.onclick = setsbs;
|
||||||
|
dom_nsbs.onclick = modetoggle;
|
||||||
|
|
||||||
|
onresize();
|
||||||
|
return onresize;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// scroll handlers
|
||||||
|
(function () {
|
||||||
|
var skip_src = false, skip_pre = false;
|
||||||
|
|
||||||
|
function scroll(src, srcmap, dst, dstmap) {
|
||||||
|
var y = src.scrollTop;
|
||||||
|
if (y < 8) {
|
||||||
|
dst.scrollTop = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (y + 8 + src.clientHeight > src.scrollHeight) {
|
||||||
|
dst.scrollTop = dst.scrollHeight - dst.clientHeight;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
y += src.clientHeight / 2;
|
||||||
|
var sy1 = -1, sy2 = -1, dy1 = -1, dy2 = -1;
|
||||||
|
for (var a = 1; a < nlines + 1; a++) {
|
||||||
|
if (srcmap[a] === null || dstmap[a] === null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (srcmap[a] > y) {
|
||||||
|
sy2 = srcmap[a];
|
||||||
|
dy2 = dstmap[a];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sy1 = srcmap[a];
|
||||||
|
dy1 = dstmap[a];
|
||||||
|
}
|
||||||
|
if (sy1 == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dy = dy1;
|
||||||
|
if (sy2 != -1 && dy2 != -1) {
|
||||||
|
var mul = (y - sy1) / (sy2 - sy1);
|
||||||
|
dy = dy1 + (dy2 - dy1) * mul;
|
||||||
|
}
|
||||||
|
dst.scrollTop = dy - dst.clientHeight / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
dom_src.onscroll = function () {
|
||||||
|
//dbg: dom_ref.scrollTop = dom_src.scrollTop;
|
||||||
|
if (skip_src) {
|
||||||
|
skip_src = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
skip_pre = true;
|
||||||
|
scroll(dom_src, map_src, dom_wrap, map_pre);
|
||||||
|
};
|
||||||
|
|
||||||
|
dom_wrap.onscroll = function () {
|
||||||
|
if (skip_pre) {
|
||||||
|
skip_pre = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
skip_src = true;
|
||||||
|
scroll(dom_wrap, map_pre, dom_src, map_src);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// save handler
|
||||||
|
function save(e) {
|
||||||
|
if (e) e.preventDefault();
|
||||||
|
var save_btn = document.getElementById("save"),
|
||||||
|
save_cls = save_btn.getAttribute('class') + '';
|
||||||
|
|
||||||
|
if (save_cls.indexOf('disabled') >= 0) {
|
||||||
|
alert('there is nothing to save');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var force = (save_cls.indexOf('force-save') >= 0);
|
||||||
|
if (force && !confirm('confirm that you wish to lose the changes made on the server since you opened this document')) {
|
||||||
|
alert('ok, aborted');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var txt = dom_src.value;
|
||||||
|
|
||||||
|
var fd = new FormData();
|
||||||
|
fd.append("act", "tput");
|
||||||
|
fd.append("lastmod", (force ? -1 : last_modified));
|
||||||
|
fd.append("body", txt);
|
||||||
|
|
||||||
|
var url = (document.location + '').split('?')[0] + '?raw';
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.responseType = 'text';
|
||||||
|
xhr.onreadystatechange = save_cb;
|
||||||
|
xhr.btn = save_btn;
|
||||||
|
xhr.txt = txt;
|
||||||
|
xhr.send(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_cb() {
|
||||||
|
if (this.readyState != XMLHttpRequest.DONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.status !== 200) {
|
||||||
|
alert('Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var r;
|
||||||
|
try {
|
||||||
|
r = JSON.parse(this.responseText);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
alert('Failed to parse reply from server:\n\n' + this.responseText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r.ok) {
|
||||||
|
if (!this.btn.classList.contains('force-save')) {
|
||||||
|
this.btn.classList.add('force-save');
|
||||||
|
var msg = [
|
||||||
|
'This file has been modified since you started editing it!\n',
|
||||||
|
'if you really want to overwrite, press save again.\n',
|
||||||
|
'modified ' + ((r.now - r.lastmod) / 1000) + ' seconds ago,',
|
||||||
|
((r.lastmod - last_modified) / 1000) + ' sec after you opened it\n',
|
||||||
|
last_modified + ' lastmod when you opened it,',
|
||||||
|
r.lastmod + ' lastmod on the server now,',
|
||||||
|
r.now + ' server time now,\n',
|
||||||
|
];
|
||||||
|
alert(msg.join('\n'));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('Error! Save failed. Maybe this JSON explains why:\n\n' + this.responseText);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.btn.classList.remove('force-save');
|
||||||
|
//alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512);
|
||||||
|
|
||||||
|
// download the saved doc from the server and compare
|
||||||
|
var url = (document.location + '').split('?')[0] + '?raw';
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
xhr.responseType = 'text';
|
||||||
|
xhr.onreadystatechange = save_chk;
|
||||||
|
xhr.btn = this.save_btn;
|
||||||
|
xhr.txt = this.txt;
|
||||||
|
xhr.lastmod = r.lastmod;
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_chk() {
|
||||||
|
if (this.readyState != XMLHttpRequest.DONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.status !== 200) {
|
||||||
|
alert('Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var doc1 = this.txt.replace(/\r\n/g, "\n");
|
||||||
|
var doc2 = this.responseText.replace(/\r\n/g, "\n");
|
||||||
|
if (doc1 != doc2) {
|
||||||
|
alert(
|
||||||
|
'Error! The document on the server does not appear to have saved correctly (your editor contents and the server copy is not identical). Place the document on your clipboard for now and check the server logs for hints\n\n' +
|
||||||
|
'Length: yours=' + doc1.length + ', server=' + doc2.length
|
||||||
|
);
|
||||||
|
alert('yours, ' + doc1.length + ' byte:\n[' + doc1 + ']');
|
||||||
|
alert('server, ' + doc2.length + ' byte:\n[' + doc2 + ']');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_modified = this.lastmod;
|
||||||
|
server_md = this.txt;
|
||||||
|
draw_md();
|
||||||
|
|
||||||
|
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.innerHTML = 'OK✔️';
|
||||||
|
var parent = document.getElementById('m');
|
||||||
|
document.documentElement.appendChild(ok);
|
||||||
|
setTimeout(function () {
|
||||||
|
ok.style.opacity = 0;
|
||||||
|
}, 500);
|
||||||
|
setTimeout(function () {
|
||||||
|
ok.parentNode.removeChild(ok);
|
||||||
|
}, 750);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
function linebounds(just_car, greedy_growth) {
|
||||||
|
var car = dom_src.selectionStart,
|
||||||
|
cdr = dom_src.selectionEnd;
|
||||||
|
|
||||||
|
if (just_car)
|
||||||
|
cdr = car;
|
||||||
|
|
||||||
|
var md = dom_src.value,
|
||||||
|
n1 = Math.max(car, 0),
|
||||||
|
n2 = Math.min(cdr, md.length - 1);
|
||||||
|
|
||||||
|
if (greedy_growth !== true) {
|
||||||
|
if (n1 < n2 && md[n1] == '\n')
|
||||||
|
n1++;
|
||||||
|
|
||||||
|
if (n1 < n2 && md[n2 - 1] == '\n')
|
||||||
|
n2 -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
n1 = md.lastIndexOf('\n', n1 - 1) + 1;
|
||||||
|
n2 = md.indexOf('\n', n2);
|
||||||
|
if (n2 < n1)
|
||||||
|
n2 = md.length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
"car": car,
|
||||||
|
"cdr": cdr,
|
||||||
|
"n1": n1,
|
||||||
|
"n2": n2,
|
||||||
|
"md": md
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// linebounds + the three textranges
|
||||||
|
function getsel() {
|
||||||
|
var s = linebounds(false);
|
||||||
|
s.pre = s.md.substring(0, s.n1);
|
||||||
|
s.sel = s.md.substring(s.n1, s.n2);
|
||||||
|
s.post = s.md.substring(s.n2);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// place modified getsel into markdown
|
||||||
|
function setsel(s) {
|
||||||
|
if (s.car != s.cdr) {
|
||||||
|
s.car = s.pre.length;
|
||||||
|
s.cdr = s.pre.length + s.sel.length;
|
||||||
|
}
|
||||||
|
dom_src.value = [s.pre, s.sel, s.post].join('');
|
||||||
|
dom_src.setSelectionRange(s.car, s.cdr, dom_src.selectionDirection);
|
||||||
|
dom_src.oninput();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// indent/dedent
|
||||||
|
function md_indent(dedent) {
|
||||||
|
var s = getsel(),
|
||||||
|
sel0 = s.sel;
|
||||||
|
|
||||||
|
if (dedent)
|
||||||
|
s.sel = s.sel.replace(/^ /, "").replace(/\n /g, "\n");
|
||||||
|
else
|
||||||
|
s.sel = ' ' + s.sel.replace(/\n/g, '\n ');
|
||||||
|
|
||||||
|
if (s.car == s.cdr)
|
||||||
|
s.car = s.cdr += s.sel.length - sel0.length;
|
||||||
|
|
||||||
|
setsel(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// header
|
||||||
|
function md_header(dedent) {
|
||||||
|
var s = getsel(),
|
||||||
|
sel0 = s.sel;
|
||||||
|
|
||||||
|
if (dedent)
|
||||||
|
s.sel = s.sel.replace(/^#/, "").replace(/^ +/, "");
|
||||||
|
else
|
||||||
|
s.sel = s.sel.replace(/^(#*) ?/, "#$1 ");
|
||||||
|
|
||||||
|
if (s.car == s.cdr)
|
||||||
|
s.car = s.cdr += s.sel.length - sel0.length;
|
||||||
|
|
||||||
|
setsel(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// smart-home
|
||||||
|
function md_home(shift) {
|
||||||
|
var s = linebounds(false, true),
|
||||||
|
ln = s.md.substring(s.n1, s.n2),
|
||||||
|
dir = dom_src.selectionDirection,
|
||||||
|
rev = dir === 'backward',
|
||||||
|
p1 = rev ? s.car : s.cdr,
|
||||||
|
p2 = rev ? s.cdr : s.car,
|
||||||
|
home = 0,
|
||||||
|
lf = ln.lastIndexOf('\n') + 1,
|
||||||
|
re = /^[ \t#>+-]*(\* )?([0-9]+\. +)?/;
|
||||||
|
|
||||||
|
if (rev)
|
||||||
|
home = s.n1 + re.exec(ln)[0].length;
|
||||||
|
else
|
||||||
|
home = s.n1 + lf + re.exec(ln.substring(lf))[0].length;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// autoindent
|
||||||
|
function md_newline() {
|
||||||
|
var s = linebounds(true),
|
||||||
|
ln = s.md.substring(s.n1, s.n2),
|
||||||
|
m1 = /^( *)([0-9]+)(\. +)/.exec(ln),
|
||||||
|
m2 = /^[ \t>+-]*(\* )?/.exec(ln);
|
||||||
|
|
||||||
|
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.post = s.md.substring(s.car);
|
||||||
|
s.car = s.cdr = s.pre.length;
|
||||||
|
setsel(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
||||||
|
(function () {
|
||||||
|
function keydown(ev) {
|
||||||
|
ev = ev || window.event;
|
||||||
|
var kc = ev.keyCode || ev.which;
|
||||||
|
var ctrl = ev.ctrlKey || ev.metaKey;
|
||||||
|
//console.log(ev.code, kc);
|
||||||
|
if (ctrl && (ev.code == "KeyS" || kc == 83)) {
|
||||||
|
save();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (document.activeElement == dom_src) {
|
||||||
|
if (ev.code == "Tab" || kc == 9) {
|
||||||
|
md_indent(ev.shiftKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ctrl && (ev.code == "KeyH" || kc == 72)) {
|
||||||
|
md_header(ev.shiftKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ctrl && (ev.code == "Home" || kc == 36)) {
|
||||||
|
md_home(ev.shiftKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ctrl && !ev.shiftKey && (ev.code == "Enter" || kc == 13)) {
|
||||||
|
md_newline();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ctrl && (ev.code == "KeyZ" || kc == 90)) {
|
||||||
|
if (ev.shiftKey)
|
||||||
|
action_stack.redo();
|
||||||
|
else
|
||||||
|
action_stack.undo();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ctrl && (ev.code == "KeyY" || kc == 89)) {
|
||||||
|
action_stack.redo();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ctrl && !ev.shiftKey && kc == 8) {
|
||||||
|
return md_backspace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.onkeydown = keydown;
|
||||||
|
document.getElementById('save').onclick = save;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById('help').onclick = function (e) {
|
||||||
|
if (e) e.preventDefault();
|
||||||
|
var dom = document.getElementById('helpbox');
|
||||||
|
var dtxt = dom.getElementsByTagName('textarea');
|
||||||
|
if (dtxt.length > 0)
|
||||||
|
dom.innerHTML = '<a href="#" id="helpclose">close</a>' + marked(dtxt[0].value);
|
||||||
|
|
||||||
|
dom.style.display = 'block';
|
||||||
|
document.getElementById('helpclose').onclick = function () {
|
||||||
|
dom.style.display = 'none';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// blame steen
|
||||||
|
action_stack = (function () {
|
||||||
|
var hist = {
|
||||||
|
un: [],
|
||||||
|
re: []
|
||||||
|
};
|
||||||
|
var sched_cpos = 0;
|
||||||
|
var sched_timer = null;
|
||||||
|
var ignore = false;
|
||||||
|
var ref = dom_src.value;
|
||||||
|
|
||||||
|
function diff(from, to, cpos) {
|
||||||
|
if (from === to)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var car = 0,
|
||||||
|
max = Math.max(from.length, to.length);
|
||||||
|
|
||||||
|
for (; car < max; car++)
|
||||||
|
if (from[car] != to[car])
|
||||||
|
break;
|
||||||
|
|
||||||
|
var p1 = from.length,
|
||||||
|
p2 = to.length;
|
||||||
|
|
||||||
|
while (p1 --> 0 && p2 --> 0)
|
||||||
|
if (from[p1] != to[p2])
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (car > ++p1) {
|
||||||
|
car = p1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var txt = from.substring(car, p1)
|
||||||
|
return {
|
||||||
|
car: car,
|
||||||
|
cdr: ++p2,
|
||||||
|
txt: txt,
|
||||||
|
cpos: cpos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function undiff(from, change) {
|
||||||
|
return {
|
||||||
|
txt: from.substring(0, change.car) + change.txt + from.substring(change.cdr),
|
||||||
|
cpos: change.cpos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function apply(src, dst) {
|
||||||
|
dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length);
|
||||||
|
|
||||||
|
if (src.length === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var patch = src.pop(),
|
||||||
|
applied = undiff(ref, patch),
|
||||||
|
cpos = patch.cpos - (patch.cdr - patch.car) + patch.txt.length,
|
||||||
|
reverse = diff(ref, applied.txt, cpos);
|
||||||
|
|
||||||
|
if (reverse === null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
dst.push(reverse);
|
||||||
|
ref = applied.txt;
|
||||||
|
ignore = true; // just some browsers
|
||||||
|
dom_src.value = ref;
|
||||||
|
dom_src.setSelectionRange(cpos, cpos);
|
||||||
|
ignore = true; // all browsers
|
||||||
|
draw_md();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedule_push() {
|
||||||
|
if (ignore) {
|
||||||
|
ignore = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hist.re = [];
|
||||||
|
clearTimeout(sched_timer);
|
||||||
|
sched_cpos = dom_src.selectionEnd;
|
||||||
|
sched_timer = setTimeout(push, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function undo() {
|
||||||
|
if (hist.re.length == 0) {
|
||||||
|
clearTimeout(sched_timer);
|
||||||
|
push();
|
||||||
|
}
|
||||||
|
return apply(hist.un, hist.re);
|
||||||
|
}
|
||||||
|
|
||||||
|
function redo() {
|
||||||
|
return apply(hist.re, hist.un);
|
||||||
|
}
|
||||||
|
|
||||||
|
function push() {
|
||||||
|
var newtxt = dom_src.value;
|
||||||
|
var change = diff(ref, newtxt, sched_cpos);
|
||||||
|
if (change !== null)
|
||||||
|
hist.un.push(change);
|
||||||
|
|
||||||
|
ref = newtxt;
|
||||||
|
dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length);
|
||||||
|
if (hist.un.length > 0)
|
||||||
|
dbg(static(hist.un.slice(-1)[0]));
|
||||||
|
if (hist.re.length > 0)
|
||||||
|
dbg(static(hist.re.slice(-1)[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
push: push,
|
||||||
|
undo: undo,
|
||||||
|
redo: redo,
|
||||||
|
push: schedule_push,
|
||||||
|
_hist: hist,
|
||||||
|
_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]);
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -21,7 +21,6 @@ html, body {
|
|||||||
#mn {
|
#mn {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin: 1.3em 0 .7em 1em;
|
margin: 1.3em 0 .7em 1em;
|
||||||
font-size: 1.4em;
|
|
||||||
}
|
}
|
||||||
#mn a {
|
#mn a {
|
||||||
color: #444;
|
color: #444;
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ var mde = (function () {
|
|||||||
"save": "Ctrl-S"
|
"save": "Ctrl-S"
|
||||||
},
|
},
|
||||||
insertTexts: ["[](", ")"],
|
insertTexts: ["[](", ")"],
|
||||||
tabSize: 4,
|
indentWithTabs: false,
|
||||||
|
tabSize: 2,
|
||||||
toolbar: tbar,
|
toolbar: tbar,
|
||||||
previewClass: 'mdo',
|
previewClass: 'mdo',
|
||||||
onToggleFullScreen: set_jumpto,
|
onToggleFullScreen: set_jumpto,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ h1 {
|
|||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
margin: 2em 0 .4em 0;
|
margin: 2em 0 .4em 0;
|
||||||
padding: 0 0 .2em 0;
|
padding: 0 0 .2em 0;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
@@ -25,3 +26,28 @@ a {
|
|||||||
border-radius: .2em;
|
border-radius: .2em;
|
||||||
padding: .2em .8em;
|
padding: .2em .8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
html.dark,
|
||||||
|
html.dark body,
|
||||||
|
html.dark #wrap {
|
||||||
|
background: #222;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
html.dark h1 {
|
||||||
|
border-color: #777;
|
||||||
|
}
|
||||||
|
html.dark a {
|
||||||
|
color: #fff;
|
||||||
|
background: #057;
|
||||||
|
border-color: #37a;
|
||||||
|
}
|
||||||
|
html.dark input {
|
||||||
|
color: #fff;
|
||||||
|
background: #624;
|
||||||
|
border: 1px solid #c27;
|
||||||
|
border-width: 1px 0 0 0;
|
||||||
|
border-radius: .5em;
|
||||||
|
padding: .5em .7em;
|
||||||
|
margin: 0 .5em 0 0;
|
||||||
|
}
|
||||||
@@ -36,7 +36,11 @@
|
|||||||
</form>
|
</form>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- script src="/.cpr/splash.js"></script -->
|
<script>
|
||||||
</body>
|
|
||||||
|
|
||||||
|
if (window.localStorage && localStorage.getItem('darkmode') == 1)
|
||||||
|
document.documentElement.setAttribute("class", "dark");
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ echo
|
|||||||
#
|
#
|
||||||
# `no-ogv` saves ~500k by removing the opus/vorbis audio codecs
|
# `no-ogv` saves ~500k by removing the opus/vorbis audio codecs
|
||||||
# (only affects apple devices; everything else has native support)
|
# (only affects apple devices; everything else has native support)
|
||||||
|
#
|
||||||
|
# `no-cm` saves ~90k by removing easymde/codemirror
|
||||||
|
# (the fancy markdown editor)
|
||||||
|
|
||||||
|
|
||||||
command -v gtar >/dev/null &&
|
command -v gtar >/dev/null &&
|
||||||
@@ -21,6 +24,7 @@ command -v gfind >/dev/null && {
|
|||||||
sed() { gsed "$@"; }
|
sed() { gsed "$@"; }
|
||||||
find() { gfind "$@"; }
|
find() { gfind "$@"; }
|
||||||
sort() { gsort "$@"; }
|
sort() { gsort "$@"; }
|
||||||
|
unexpand() { gunexpand "$@"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[ -e copyparty/__main__.py ] || cd ..
|
[ -e copyparty/__main__.py ] || cd ..
|
||||||
@@ -35,9 +39,15 @@ while [ ! -z "$1" ]; do
|
|||||||
[ "$1" = clean ] && clean=1 && shift && continue
|
[ "$1" = clean ] && clean=1 && shift && continue
|
||||||
[ "$1" = re ] && repack=1 && shift && continue
|
[ "$1" = re ] && repack=1 && shift && continue
|
||||||
[ "$1" = no-ogv ] && no_ogv=1 && shift && continue
|
[ "$1" = no-ogv ] && no_ogv=1 && shift && continue
|
||||||
|
[ "$1" = no-cm ] && no_cm=1 && shift && continue
|
||||||
break
|
break
|
||||||
done
|
done
|
||||||
|
|
||||||
|
tmv() {
|
||||||
|
touch -r "$1" t
|
||||||
|
mv t "$1"
|
||||||
|
}
|
||||||
|
|
||||||
rm -rf sfx/*
|
rm -rf sfx/*
|
||||||
mkdir -p sfx build
|
mkdir -p sfx build
|
||||||
cd sfx
|
cd sfx
|
||||||
@@ -62,7 +72,15 @@ cd sfx
|
|||||||
|
|
||||||
tar -zxf $f
|
tar -zxf $f
|
||||||
mv Jinja2-*/jinja2 .
|
mv Jinja2-*/jinja2 .
|
||||||
rm -rf Jinja2-* jinja2/testsuite
|
rm -rf Jinja2-* jinja2/testsuite jinja2/_markupsafe/tests.py jinja2/_stringdefs.py
|
||||||
|
|
||||||
|
f=jinja2/lexer.py
|
||||||
|
sed -r '/.*föö.*/ raise SyntaxError/' <$f >t
|
||||||
|
tmv $f
|
||||||
|
|
||||||
|
f=jinja2/_markupsafe/_constants.py
|
||||||
|
awk '!/: [0-9]+,?$/ || /(amp|gt|lt|quot|apos|nbsp).:/' <$f >t
|
||||||
|
tmv $f
|
||||||
|
|
||||||
# msys2 tar is bad, make the best of it
|
# msys2 tar is bad, make the best of it
|
||||||
echo collecting source
|
echo collecting source
|
||||||
@@ -98,11 +116,28 @@ rm -f copyparty/web/deps/*.full.*
|
|||||||
|
|
||||||
# it's fine dw
|
# it's fine dw
|
||||||
grep -lE '\.full\.(js|css)' copyparty/web/* |
|
grep -lE '\.full\.(js|css)' copyparty/web/* |
|
||||||
while IFS= read -r x; do sed -ri 's/\.full\.(js|css)/.\1/g' "$x"; done
|
while IFS= read -r x; do
|
||||||
|
sed -r 's/\.full\.(js|css)/.\1/g' <"$x" >t
|
||||||
|
tmv "$x"
|
||||||
|
done
|
||||||
|
|
||||||
[ $no_ogv ] &&
|
[ $no_ogv ] &&
|
||||||
rm -rf copyparty/web/deps/{dynamicaudio,ogv}*
|
rm -rf copyparty/web/deps/{dynamicaudio,ogv}*
|
||||||
|
|
||||||
|
[ $no_cm ] && {
|
||||||
|
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
|
||||||
|
echo h > copyparty/web/mde.html
|
||||||
|
f=copyparty/web/md.html
|
||||||
|
sed -r '/edit2">edit \(fancy/d' <$f >t && tmv "$f"
|
||||||
|
}
|
||||||
|
|
||||||
|
# up2k goes from 28k to 22k laff
|
||||||
|
echo entabbening
|
||||||
|
find | grep -E '\.(js|css|html|py)$' | while IFS= read -r f; do
|
||||||
|
unexpand -t 4 --first-only <"$f" >t
|
||||||
|
tmv "$f"
|
||||||
|
done
|
||||||
|
|
||||||
echo creating tar
|
echo creating tar
|
||||||
args=(--owner=1000 --group=1000)
|
args=(--owner=1000 --group=1000)
|
||||||
[ "$OSTYPE" = msys ] &&
|
[ "$OSTYPE" = msys ] &&
|
||||||
@@ -132,19 +167,5 @@ printf "done:\n"
|
|||||||
printf " %s\n" "$(realpath $sfx_out)."{sh,py}
|
printf " %s\n" "$(realpath $sfx_out)."{sh,py}
|
||||||
# rm -rf *
|
# rm -rf *
|
||||||
|
|
||||||
# -rw-r--r-- 1 ed ed 811271 May 5 14:35 tar.bz2
|
# tar -tvf ../sfx/tar | sed -r 's/(.* ....-..-.. ..:.. )(.*)/\2 `` \1/' | sort | sed -r 's/(.*) `` (.*)/\2 \1/'| less
|
||||||
# -rw-r--r-- 1 ed ed 732016 May 5 14:35 tar.xz
|
# 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
|
||||||
|
|
||||||
# -rwxr-xr-x 1 ed ed 830425 May 5 14:35 copyparty-sfx.py*
|
|
||||||
# -rwxr-xr-x 1 ed ed 734088 May 5 14:35 copyparty-sfx.sh*
|
|
||||||
|
|
||||||
# -rwxr-xr-x 1 ed ed 799690 May 5 14:45 copyparty-sfx.py*
|
|
||||||
# -rwxr-xr-x 1 ed ed 735004 May 5 14:45 copyparty-sfx.sh*
|
|
||||||
|
|
||||||
# time pigz -11 -J 34 -I 5730 < tar > tar.gz.5730
|
|
||||||
# real 8m50.622s
|
|
||||||
# user 33m9.821s
|
|
||||||
# -rw-r--r-- 1 ed ed 1136640 May 5 14:50 tar
|
|
||||||
# -rw-r--r-- 1 ed ed 296334 May 5 14:50 tar.bz2
|
|
||||||
# -rw-r--r-- 1 ed ed 324705 May 5 15:01 tar.gz.5730
|
|
||||||
# -rw-r--r-- 1 ed ed 257208 May 5 14:50 tar.xz
|
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ def read_py(binp):
|
|||||||
|
|
||||||
def get_pys():
|
def get_pys():
|
||||||
ver, chk = read_py(sys.executable)
|
ver, chk = read_py(sys.executable)
|
||||||
if chk:
|
if chk or PY2:
|
||||||
return [[chk, ver, sys.executable]]
|
return [[chk, ver, sys.executable]]
|
||||||
|
|
||||||
hits = {sys.executable.lower(): sys.executable}
|
hits = {sys.executable.lower(): sys.executable}
|
||||||
|
|||||||
84
srv/ceditable.html
Normal file
84
srv/ceditable.html
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<!DOCTYPE html><html><head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
|
<style>
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
outline: 0;
|
||||||
|
border: none;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1em;
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
li, #edit {
|
||||||
|
list-style-type: none;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word; /*ie*/
|
||||||
|
}
|
||||||
|
li:nth-child(even) {
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
#edit, #html, #txt1, #txt2 {
|
||||||
|
background: #eee;
|
||||||
|
position: fixed;
|
||||||
|
width: calc(50% - .8em);
|
||||||
|
height: calc(50% - .8em);
|
||||||
|
}
|
||||||
|
#txt1 { top: .5em; left: .5em }
|
||||||
|
#edit { top: .5em; right: .5em }
|
||||||
|
#html { bottom: .5em; left: .5em }
|
||||||
|
#txt2 { bottom: .5em; right: .5em }
|
||||||
|
|
||||||
|
</style></head><body>
|
||||||
|
<pre id="edit" contenteditable="true"></pre>
|
||||||
|
<textarea id="html"></textarea>
|
||||||
|
<ul id="txt1"></ul>
|
||||||
|
<ul id="txt2"></ul>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var edit = document.getElementById('edit'),
|
||||||
|
html = document.getElementById('html'),
|
||||||
|
txt1 = document.getElementById('txt1'),
|
||||||
|
txt2 = document.getElementById('txt2');
|
||||||
|
|
||||||
|
var oh = null;
|
||||||
|
function fun() {
|
||||||
|
var h = edit.innerHTML;
|
||||||
|
if (oh != h) {
|
||||||
|
oh = h;
|
||||||
|
html.value = h;
|
||||||
|
var t = edit.innerText;
|
||||||
|
if (h.indexOf('<div><br></div>') >= 0)
|
||||||
|
t = t.replace(/\n\n/g, "\n");
|
||||||
|
|
||||||
|
t = '<li>' + t.
|
||||||
|
replace(/&/g, "&").
|
||||||
|
replace(/</g, "<").
|
||||||
|
replace(/>/g, ">").
|
||||||
|
split('\n').join('</li>\n<li>') + '</li>';
|
||||||
|
|
||||||
|
t = t.replace(/<li><\/li>/g, '<li> </li>');
|
||||||
|
txt1.innerHTML = t;
|
||||||
|
txt2.innerHTML = t;
|
||||||
|
}
|
||||||
|
setTimeout(fun, 100);
|
||||||
|
}
|
||||||
|
fun();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
srv/test.md
30
srv/test.md
@@ -1,3 +1,33 @@
|
|||||||
|
### hello world
|
||||||
|
|
||||||
|
```
|
||||||
|
[72....................................................................]
|
||||||
|
[80............................................................................]
|
||||||
|
```
|
||||||
|
|
||||||
|
* foo
|
||||||
|
```
|
||||||
|
[72....................................................................]
|
||||||
|
[80............................................................................]
|
||||||
|
```
|
||||||
|
|
||||||
|
* bar
|
||||||
|
```
|
||||||
|
[72....................................................................]
|
||||||
|
[80............................................................................]
|
||||||
|
```
|
||||||
|
|
||||||
|
a123456789b123456789c123456789d123456789e123456789f123456789g123456789h123456789i123456789j123456789k123456789l123456789m123456789n123456789o123456789p123456789q123456789r123456789s123456789t123456789u123456789v123456789w123456789x123456789y123456789z123456789
|
||||||
|
|
||||||
|
<foo> bar & <span>baz</span>
|
||||||
|
<a href="?foo=bar&baz=qwe&rty">?foo=bar&baz=qwe&rty</a>
|
||||||
|
<!-- hidden -->
|
||||||
|
```
|
||||||
|
<foo> bar & <span>baz</span>
|
||||||
|
<a href="?foo=bar&baz=qwe&rty">?foo=bar&baz=qwe&rty</a>
|
||||||
|
<!-- visible -->
|
||||||
|
```
|
||||||
|
|
||||||
*fails marked/showdown/tui/simplemde (just italics), **OK: markdown-it/simplemde:***
|
*fails marked/showdown/tui/simplemde (just italics), **OK: markdown-it/simplemde:***
|
||||||
testing just google.com and underscored _google.com_ also with _google.com,_ trailing comma and _google.com_, comma after
|
testing just google.com and underscored _google.com_ also with _google.com,_ trailing comma and _google.com_, comma after
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user